From 8b84053f6b1ed226b102ec61614dce8ac6d46507 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:33:06 +0000 Subject: [PATCH 1/3] Initial plan From 36dee07eac757609e21d14519af4825b4b9b3480 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:05:09 +0000 Subject: [PATCH 2/3] feat(firehose): add async Firehose emission, shutdown wiring, and gap detection - Create `crates/firehose/` crate (`reth-firehose`) with: - `FirehoseArgs`: clap CLI args for `--firehose.emission-mode`, `--firehose.channel-capacity`, `--firehose.live-threshold`, `--firehose.cursor-path` - `init_tracer()`: one-shot global tracer initialisation returning `ShutdownHandle` - `check_gap_and_re_trace()`: detects gaps between cursor file and execution stage checkpoint, re-emits missing blocks - `firehose_re_trace_range()`: pruning guard + per-block re-execution loop (full EVM inspector integration noted as TODO) - Patch `rbase64` (transitive dep of `firehose-tracer`) to remove its `#[global_allocator]` declaration that conflicts with reth's jemalloc allocator - Add `firehose-tracer = "5.1.0"` and `reth-firehose` to workspace `Cargo.toml` - Modify `bin/reth/src/main.rs` to use `FirehoseArgs` as CLI extension type and wire the `on_component_initialized` startup hook that: initialises the tracer, checks for gaps, and registers `ShutdownHandle::drain()` with `spawn_with_graceful_shutdown_signal` Agent-Logs-Url: https://github.com/streamingfast/reth/sessions/088879c2-d333-44f1-83d2-83a9ae603230 Co-authored-by: maoueh <123014+maoueh@users.noreply.github.com> --- Cargo.lock | 332 ++++-- Cargo.toml | 11 + bin/reth/Cargo.toml | 1 + bin/reth/src/lib.rs | 1 + bin/reth/src/main.rs | 61 +- crates/firehose/Cargo.toml | 28 + crates/firehose/src/args.rs | 130 +++ crates/firehose/src/lib.rs | 110 ++ crates/firehose/src/re_trace.rs | 221 ++++ vendor/rbase64/.cargo-ok | 1 + vendor/rbase64/.cargo_vcs_info.json | 6 + vendor/rbase64/.github/workflows/audit.yml | 28 + vendor/rbase64/.github/workflows/ci.yml | 156 +++ .../.github/workflows/release-packaging.yml | 27 + vendor/rbase64/.gitignore | 2 + vendor/rbase64/Cargo.lock | 1017 +++++++++++++++++ vendor/rbase64/Cargo.toml | 74 ++ vendor/rbase64/Cargo.toml.orig | 41 + vendor/rbase64/LICENSE-APACHE | 201 ++++ vendor/rbase64/LICENSE-MIT | 21 + vendor/rbase64/README.md | 203 ++++ vendor/rbase64/benches/baseline.md | 234 ++++ vendor/rbase64/benches/benchmarks.rs | 74 ++ vendor/rbase64/scripts/diff.js | 16 + vendor/rbase64/scripts/test-all-features.sh | 2 + vendor/rbase64/scripts/update-baseline.sh | 6 + vendor/rbase64/src/common.rs | 42 + vendor/rbase64/src/decode.rs | 100 ++ vendor/rbase64/src/encode.rs | 93 ++ vendor/rbase64/src/lib.rs | 114 ++ vendor/rbase64/src/main.rs | 92 ++ vendor/rbase64/tests/cli-tests.rs | 133 +++ vendor/rbase64/tests/resources/bytes.b64 | 1 + vendor/rbase64/tests/resources/bytes.bin | Bin 0 -> 10240 bytes vendor/rbase64/tests/resources/utf8.b64 | 1 + vendor/rbase64/tests/resources/utf8.txt | 206 ++++ 36 files changed, 3668 insertions(+), 118 deletions(-) create mode 100644 crates/firehose/Cargo.toml create mode 100644 crates/firehose/src/args.rs create mode 100644 crates/firehose/src/lib.rs create mode 100644 crates/firehose/src/re_trace.rs create mode 100644 vendor/rbase64/.cargo-ok create mode 100644 vendor/rbase64/.cargo_vcs_info.json create mode 100644 vendor/rbase64/.github/workflows/audit.yml create mode 100644 vendor/rbase64/.github/workflows/ci.yml create mode 100644 vendor/rbase64/.github/workflows/release-packaging.yml create mode 100644 vendor/rbase64/.gitignore create mode 100644 vendor/rbase64/Cargo.lock create mode 100644 vendor/rbase64/Cargo.toml create mode 100644 vendor/rbase64/Cargo.toml.orig create mode 100644 vendor/rbase64/LICENSE-APACHE create mode 100644 vendor/rbase64/LICENSE-MIT create mode 100644 vendor/rbase64/README.md create mode 100644 vendor/rbase64/benches/baseline.md create mode 100644 vendor/rbase64/benches/benchmarks.rs create mode 100755 vendor/rbase64/scripts/diff.js create mode 100755 vendor/rbase64/scripts/test-all-features.sh create mode 100755 vendor/rbase64/scripts/update-baseline.sh create mode 100644 vendor/rbase64/src/common.rs create mode 100644 vendor/rbase64/src/decode.rs create mode 100644 vendor/rbase64/src/encode.rs create mode 100644 vendor/rbase64/src/lib.rs create mode 100644 vendor/rbase64/src/main.rs create mode 100644 vendor/rbase64/tests/cli-tests.rs create mode 100644 vendor/rbase64/tests/resources/bytes.b64 create mode 100644 vendor/rbase64/tests/resources/bytes.bin create mode 100644 vendor/rbase64/tests/resources/utf8.b64 create mode 100644 vendor/rbase64/tests/resources/utf8.txt diff --git a/Cargo.lock b/Cargo.lock index bd4d89ae6ac..a887de3b5c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,32 @@ dependencies = [ "strum", ] +[[package]] +name = "alloy-consensus" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f16daaf7e1f95f62c6c3bf8a3fc3d78b08ae9777810c0bb5e94966c7cd57ef0" +dependencies = [ + "alloy-eips 1.8.3", + "alloy-primitives", + "alloy-rlp", + "alloy-serde 1.8.3", + "alloy-trie", + "alloy-tx-macros 1.8.3", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + [[package]] name = "alloy-consensus" version = "2.0.0" @@ -130,7 +156,7 @@ dependencies = [ "alloy-rlp", "alloy-serde 2.0.0", "alloy-trie", - "alloy-tx-macros", + "alloy-tx-macros 2.0.0", "arbitrary", "auto_impl", "borsh", @@ -153,7 +179,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88fc7bbfb98cf5605a35aadf0ba43a7d9f1608d6f220d05e4fbd5144d3b0b625" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -168,7 +194,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c16fa30b623e40a5b216da00f3b61870f5cbe863b59816ac1ecc2489515a40" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-dyn-abi", "alloy-json-abi", "alloy-network", @@ -316,7 +342,7 @@ version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd3e12b99b0c8f7298ffd3604c58310cba310203c5f6cd5e024f3b2bd9c3b09c" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-hardforks 0.4.7", "alloy-primitives", @@ -330,6 +356,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "alloy-genesis" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf9480307b09d22876efb67d30cadd9013134c21f3a17ec9f93fd7536d38024" +dependencies = [ + "alloy-eips 1.8.3", + "alloy-primitives", + "alloy-serde 1.8.3", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + [[package]] name = "alloy-genesis" version = "2.0.0" @@ -405,7 +446,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0a3f5a7f3678b71d33fcc45b714fab8928dbc647d5aff2145e72032d5c849bb" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-consensus-any", "alloy-eips 2.0.0", "alloy-json-rpc", @@ -431,7 +472,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb50dc1fb0e0b2c8748d5bee1aa7acdd18f9e036311bc93a71d97be624030317" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-serde 2.0.0", @@ -444,7 +485,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85195890fcee519312718dc8418035935ad0d57f57943ca82689732432a702c9" dependencies = [ - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-hardforks 0.2.13", "alloy-network", "alloy-primitives", @@ -498,7 +539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ba5468f78c8893be2d68a7f2fda61753336e5653f006af19781001b5f99e6c" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-json-rpc", "alloy-network", @@ -625,7 +666,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f1d057dcbacf8be8f689a7737e0d697fd40a2dc5b664c9035f182ff016649ea" dependencies = [ - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "serde", "serde_json", @@ -697,7 +738,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e59bc947935732cae5b072753e5e034c0b70a8b031c2839f45e2659ba07df9ae" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -718,7 +759,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc280a41931bd419af86e9e859dd9726b73313aaa2e479b33c0e344f4b892ddb" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-consensus-any", "alloy-eips 2.0.0", "alloy-network-primitives", @@ -740,7 +781,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "286c40ce0d715217a5bfa9fb452779b11e6769e56680afa0de691ae8f3a848ac" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rpc-types-eth", @@ -819,7 +860,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c67d2372aada343130d41e249b59a3cef29b1678dcd3fd80f1c2c4d6b5318f2" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-network", "alloy-primitives", "alloy-signer", @@ -1000,6 +1041,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "alloy-tx-macros" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69722eddcdf1ce096c3ab66cf8116999363f734eb36fe94a148f4f71c85da84" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "alloy-tx-macros" version = "2.0.0" @@ -1063,7 +1116,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1074,7 +1127,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2831,9 +2884,9 @@ dependencies = [ name = "custom-hardforks" version = "0.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "reth-chainspec", "reth-network-peers", @@ -2949,7 +3002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -3278,9 +3331,9 @@ dependencies = [ name = "ef-tests" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rlp", "rayon", @@ -3438,7 +3491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3496,7 +3549,7 @@ dependencies = [ name = "example-beacon-api-sidecar-fetcher" version = "0.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rpc-types-beacon", @@ -3587,7 +3640,7 @@ dependencies = [ name = "example-custom-dev-node" version = "0.0.0" dependencies = [ - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "eyre", "futures-util", @@ -3600,7 +3653,7 @@ dependencies = [ name = "example-custom-engine-types" version = "0.0.0" dependencies = [ - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rpc-types", "eyre", @@ -3619,7 +3672,7 @@ name = "example-custom-evm" version = "0.0.0" dependencies = [ "alloy-evm", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "eyre", "reth-ethereum", @@ -3721,7 +3774,7 @@ dependencies = [ name = "example-manual-p2p" version = "0.0.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "eyre", "futures", "reth-discv4", @@ -3791,7 +3844,7 @@ dependencies = [ name = "example-polygon-p2p" version = "0.0.0" dependencies = [ - "alloy-genesis", + "alloy-genesis 2.0.0", "reth-discv4", "reth-ethereum", "reth-tracing", @@ -3806,7 +3859,7 @@ name = "example-precompile-cache" version = "0.0.0" dependencies = [ "alloy-evm", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "eyre", "parking_lot", @@ -3943,6 +3996,29 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "firehose-tracer" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794633ac82fd0a4073b672a6a100c6065cd225d7895fb2f3271c743011aa9207" +dependencies = [ + "alloy-consensus 1.8.3", + "alloy-eips 1.8.3", + "alloy-genesis 1.8.3", + "alloy-primitives", + "alloy-rlp", + "eyre", + "hex", + "k256", + "num_enum", + "prost", + "prost-types", + "rbase64", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "fixed-cache" version = "0.1.8" @@ -4202,7 +4278,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.1.3", + "windows-link 0.2.1", "windows-result 0.4.1", ] @@ -5091,7 +5167,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6067,7 +6143,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6878,6 +6954,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + [[package]] name = "quanta" version = "0.12.6" @@ -7173,6 +7258,13 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rbase64" +version = "2.0.3" +dependencies = [ + "rayon", +] + [[package]] name = "recvmsg" version = "1.0.0" @@ -7376,6 +7468,7 @@ dependencies = [ "reth-ethereum-cli", "reth-ethereum-payload-builder", "reth-ethereum-primitives", + "reth-firehose", "reth-network", "reth-network-api", "reth-node-api", @@ -7407,7 +7500,7 @@ dependencies = [ name = "reth-basic-payload-builder" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "futures-core", @@ -7433,7 +7526,7 @@ dependencies = [ name = "reth-bb" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-evm", "alloy-hardforks 0.4.7", @@ -7481,7 +7574,7 @@ dependencies = [ name = "reth-bench" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eip7928", "alloy-eips 2.0.0", "alloy-json-rpc", @@ -7529,7 +7622,7 @@ dependencies = [ name = "reth-chain-state" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-signer", @@ -7562,10 +7655,10 @@ name = "reth-chainspec" version = "2.1.0" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-evm", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rlp", "alloy-trie", @@ -7581,7 +7674,7 @@ dependencies = [ name = "reth-cli" version = "2.1.0" dependencies = [ - "alloy-genesis", + "alloy-genesis 2.0.0", "clap", "eyre", "reth-cli-runner", @@ -7594,7 +7687,7 @@ name = "reth-cli-commands" version = "2.1.0" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -7715,9 +7808,9 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a79b3247ae4fbb1d4d35ce83a11fc596428a4c6ea836c98a75a55340192578a4" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-trie", "arbitrary", @@ -7763,7 +7856,7 @@ dependencies = [ name = "reth-consensus" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "auto_impl", "reth-execution-types", @@ -7775,7 +7868,7 @@ dependencies = [ name = "reth-consensus-common" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "rand 0.9.4", @@ -7789,7 +7882,7 @@ dependencies = [ name = "reth-consensus-debug-client" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-json-rpc", "alloy-primitives", @@ -7814,7 +7907,7 @@ dependencies = [ name = "reth-db" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "arbitrary", "assert_matches", @@ -7849,7 +7942,7 @@ dependencies = [ name = "reth-db-api" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "arbitrary", "arrayvec", @@ -7877,8 +7970,8 @@ dependencies = [ name = "reth-db-common" version = "2.1.0" dependencies = [ - "alloy-consensus", - "alloy-genesis", + "alloy-consensus 2.0.0", + "alloy-genesis 2.0.0", "alloy-primitives", "boyer-moore-magiclen", "eyre", @@ -8003,7 +8096,7 @@ dependencies = [ name = "reth-downloaders" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -8041,7 +8134,7 @@ dependencies = [ name = "reth-e2e-test-utils" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-network", "alloy-primitives", @@ -8124,7 +8217,7 @@ dependencies = [ name = "reth-engine-local" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "alloy-rpc-types-engine", "eyre", @@ -8146,7 +8239,7 @@ dependencies = [ name = "reth-engine-primitives" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rpc-types-engine", @@ -8170,7 +8263,7 @@ dependencies = [ name = "reth-engine-tree" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eip7928", "alloy-eips 2.0.0", "alloy-evm", @@ -8242,7 +8335,7 @@ dependencies = [ name = "reth-engine-util" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-rpc-types-engine", "eyre", "futures", @@ -8269,7 +8362,7 @@ dependencies = [ name = "reth-era" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -8309,7 +8402,7 @@ dependencies = [ name = "reth-era-utils" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "bytes", "eyre", @@ -8346,7 +8439,7 @@ name = "reth-eth-wire" version = "2.1.0" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -8384,10 +8477,10 @@ name = "reth-eth-wire-types" version = "2.1.0" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eip7928", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-hardforks 0.4.7", "alloy-primitives", "alloy-rlp", @@ -8472,7 +8565,7 @@ dependencies = [ name = "reth-ethereum-consensus" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "reth-chainspec", @@ -8517,7 +8610,7 @@ dependencies = [ name = "reth-ethereum-payload-builder" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -8546,7 +8639,7 @@ dependencies = [ name = "reth-ethereum-primitives" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -8577,7 +8670,7 @@ dependencies = [ name = "reth-evm" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-evm", "alloy-primitives", @@ -8601,10 +8694,10 @@ dependencies = [ name = "reth-evm-ethereum" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-evm", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rpc-types-engine", "reth-chainspec", @@ -8653,7 +8746,7 @@ dependencies = [ name = "reth-execution-types" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-evm", "alloy-primitives", @@ -8674,9 +8767,9 @@ dependencies = [ name = "reth-exex" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "eyre", "futures", @@ -8763,6 +8856,19 @@ dependencies = [ "serde_with", ] +[[package]] +name = "reth-firehose" +version = "2.1.0" +dependencies = [ + "clap", + "eyre", + "firehose-tracer", + "reth-stages-types", + "reth-storage-api", + "tempfile", + "tracing", +] + [[package]] name = "reth-fs-util" version = "2.1.0" @@ -8776,7 +8882,7 @@ dependencies = [ name = "reth-invalid-block-hooks" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -8889,9 +8995,9 @@ dependencies = [ name = "reth-network" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rlp", "aquamarine", @@ -8950,7 +9056,7 @@ dependencies = [ name = "reth-network-api" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "alloy-rpc-types-admin", "alloy-rpc-types-eth", @@ -8974,7 +9080,7 @@ dependencies = [ name = "reth-network-p2p" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "auto_impl", @@ -9067,7 +9173,7 @@ dependencies = [ name = "reth-node-builder" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-provider", @@ -9138,7 +9244,7 @@ dependencies = [ name = "reth-node-core" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rpc-types-engine", @@ -9194,10 +9300,10 @@ dependencies = [ name = "reth-node-ethereum" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-contract", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-network", "alloy-primitives", "alloy-provider", @@ -9254,7 +9360,7 @@ dependencies = [ name = "reth-node-ethstats" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "chrono", "futures-util", @@ -9277,7 +9383,7 @@ dependencies = [ name = "reth-node-events" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rpc-types-engine", @@ -9340,7 +9446,7 @@ dependencies = [ name = "reth-payload-builder" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "alloy-rpc-types", "derive_more", @@ -9374,7 +9480,7 @@ dependencies = [ name = "reth-payload-primitives" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -9399,7 +9505,7 @@ dependencies = [ name = "reth-payload-util" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "reth-transaction-pool", ] @@ -9408,7 +9514,7 @@ dependencies = [ name = "reth-payload-validator" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-rpc-types-engine", "reth-primitives-traits", ] @@ -9419,9 +9525,9 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc759fd87c3f65440e5d3bfa3107fe8a13a61a6807cd485c62c49d63c7bf6717" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", @@ -9450,9 +9556,9 @@ dependencies = [ name = "reth-provider" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rpc-types-engine", "assert_matches", @@ -9501,7 +9607,7 @@ dependencies = [ name = "reth-prune" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "assert_matches", @@ -9558,7 +9664,7 @@ dependencies = [ name = "reth-revm" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-debug", @@ -9574,11 +9680,11 @@ dependencies = [ name = "reth-rpc" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-dyn-abi", "alloy-eips 2.0.0", "alloy-evm", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-network", "alloy-primitives", "alloy-rlp", @@ -9655,7 +9761,7 @@ name = "reth-rpc-api" version = "2.1.0" dependencies = [ "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-json-rpc", "alloy-primitives", "alloy-rpc-types", @@ -9761,7 +9867,7 @@ dependencies = [ name = "reth-rpc-convert" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-evm", "alloy-json-rpc", "alloy-network", @@ -9781,7 +9887,7 @@ dependencies = [ name = "reth-rpc-e2e-tests" version = "2.1.0" dependencies = [ - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-rpc-types-engine", "eyre", "futures-util", @@ -9837,7 +9943,7 @@ dependencies = [ name = "reth-rpc-eth-api" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-dyn-abi", "alloy-eip7928", "alloy-eips 2.0.0", @@ -9882,7 +9988,7 @@ dependencies = [ name = "reth-rpc-eth-types" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-evm", "alloy-network", @@ -9964,7 +10070,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b766da61ec7c46596386b4bc88d9b57d1939d3da2bc9e927567a8a23650e5ce9" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-network", "alloy-primitives", "alloy-rpc-types-eth", @@ -9977,9 +10083,9 @@ dependencies = [ name = "reth-stages" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rlp", "assert_matches", @@ -10130,7 +10236,7 @@ dependencies = [ name = "reth-storage-api" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rpc-types-engine", @@ -10170,7 +10276,7 @@ dependencies = [ name = "reth-storage-rpc-provider" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-network", "alloy-primitives", @@ -10219,9 +10325,9 @@ dependencies = [ name = "reth-testing-utils" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", - "alloy-genesis", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rlp", "rand 0.8.6", @@ -10279,7 +10385,7 @@ dependencies = [ name = "reth-transaction-pool" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -10330,7 +10436,7 @@ dependencies = [ name = "reth-trie" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-eips 2.0.0", "alloy-primitives", "alloy-rlp", @@ -10363,8 +10469,8 @@ dependencies = [ name = "reth-trie-common" version = "2.1.0" dependencies = [ - "alloy-consensus", - "alloy-genesis", + "alloy-consensus 2.0.0", + "alloy-genesis 2.0.0", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", @@ -10395,7 +10501,7 @@ dependencies = [ name = "reth-trie-db" version = "2.1.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 2.0.0", "alloy-primitives", "alloy-rlp", "metrics", @@ -10913,7 +11019,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -10993,7 +11099,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 1.0.7", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -11582,7 +11688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -11782,7 +11888,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -13051,7 +13157,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e3c153ce556..4af8137bfbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "crates/era-downloader", "crates/era-utils", "crates/errors/", + "crates/firehose/", "crates/ethereum/hardforks/", "crates/ethereum/cli/", "crates/ethereum/consensus/", @@ -351,6 +352,7 @@ reth-era = { path = "crates/era" } reth-era-downloader = { path = "crates/era-downloader" } reth-era-utils = { path = "crates/era-utils" } reth-errors = { path = "crates/errors" } +reth-firehose = { path = "crates/firehose" } reth-eth-wire = { path = "crates/net/eth-wire" } reth-eth-wire-types = { path = "crates/net/eth-wire-types" } reth-ethereum-payload-builder = { path = "crates/ethereum/payload" } @@ -505,6 +507,7 @@ dirs-next = "2.0.0" dyn-clone = "1.0.17" eyre = "0.6" fdlimit = "0.3.0" +firehose-tracer = "5.1.0" fixed-map = { version = "0.9", default-features = false } humantime = "2.1" imbl = "7" @@ -700,3 +703,11 @@ vergen-git2 = "9.1.0" # networking ipnet = "2.11" + +[patch.crates-io] +# Patch rbase64 to remove its #[global_allocator] declaration, which conflicts +# with reth's own global allocator (jemalloc / snmalloc). +# The original rbase64 crate sets MiMalloc as the global allocator in its lib.rs, +# which is a library-level bug. This local copy removes that declaration while +# keeping all encoding/decoding behaviour unchanged. +rbase64 = { path = "vendor/rbase64" } diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 424f93a5d23..0b141a9e1af 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -30,6 +30,7 @@ workspace = true # reth reth-ethereum-cli.workspace = true reth-chainspec.workspace = true +reth-firehose.workspace = true reth-primitives-traits.workspace = true reth-ethereum-primitives.workspace = true reth-db = { workspace = true, features = ["mdbx"] } diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index f8a4be0424a..845a11dc5b5 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -219,4 +219,5 @@ use aquamarine as _; // used in main use clap as _; use reth_cli_util as _; +use reth_firehose as _; use tracing as _; diff --git a/bin/reth/src/main.rs b/bin/reth/src/main.rs index 838f4e1dc66..7bff9496ba1 100644 --- a/bin/reth/src/main.rs +++ b/bin/reth/src/main.rs @@ -14,6 +14,7 @@ static MALLOC_CONF: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0"; use clap::Parser; use reth::cli::Cli; use reth_ethereum_cli::chainspec::EthereumChainSpecParser; +use reth_firehose::FirehoseArgs; use reth_node_ethereum::EthereumNode; use tracing::info; @@ -25,12 +26,62 @@ fn main() { unsafe { std::env::set_var("RUST_BACKTRACE", "1") }; } - if let Err(err) = Cli::::parse().run(async move |builder, _| { - info!(target: "reth::cli", "Launching node"); - let handle = builder.node(EthereumNode::default()).launch_with_debug_capabilities().await?; + if let Err(err) = Cli::::parse().run( + async move |builder, firehose_args| { + info!(target: "reth::cli", "Launching node"); - handle.wait_for_node_exit().await - }) { + // Resolve the node data directory so the Firehose hook can derive the + // default cursor file path without needing the full NodeConfig later. + let data_dir = builder.config().datadir().data_dir().to_path_buf(); + + let handle = builder + .node(EthereumNode::default()) + // ── Firehose startup hook ───────────────────────────────────────── + // Runs after all node components are built but BEFORE the sync + // pipeline starts. This is the correct place to: + // 1. Initialise the Firehose tracer with the emission config. + // 2. Detect any gap between the last emitted block (cursor file) and the + // execution stage checkpoint. + // 3. Re-emit the missing blocks before sync resumes. + // 4. Register the async writer's shutdown handle with the task executor so it is + // drained gracefully before process exit. + .on_component_initialized(move |node| { + // Build the tracer config from CLI args and the resolved data dir. + let cfg = firehose_args.to_tracer_config(&data_dir); + let cursor_path = cfg.cursor_path.clone(); + + // Initialise the global tracer. Returns a ShutdownHandle when + // the emission mode uses a background writer thread. + let shutdown_handle = reth_firehose::init_tracer(cfg); + + // Detect and re-emit any blocks missed since the last run. + reth_firehose::check_gap_and_re_trace(&node.provider, cursor_path.as_deref())?; + + // Wire the background writer's drain into the node shutdown + // lifecycle. The guard held by the async closure keeps the + // GracefulShutdown alive until drain() completes, ensuring the + // background thread has flushed all buffered blocks before the + // process exits. + if let Some(handle) = shutdown_handle { + node.task_executor.spawn_with_graceful_shutdown_signal( + |shutdown| async move { + // Wait for the node shutdown signal. + let _guard = shutdown.await; + // Drain the async writer (blocks until all queued + // blocks have been written to stdout). + handle.drain(); + }, + ); + } + + Ok(()) + }) + .launch_with_debug_capabilities() + .await?; + + handle.wait_for_node_exit().await + }, + ) { eprintln!("Error: {err:?}"); std::process::exit(1); } diff --git a/crates/firehose/Cargo.toml b/crates/firehose/Cargo.toml new file mode 100644 index 00000000000..dd4af929c38 --- /dev/null +++ b/crates/firehose/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "reth-firehose" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Firehose integration for Reth: async emission, shutdown wiring, and gap detection" + +[lints] +workspace = true + +[dependencies] +# reth +reth-stages-types.workspace = true +reth-storage-api.workspace = true + +# firehose +firehose-tracer.workspace = true + +# misc +clap = { workspace = true, features = ["derive", "env"] } +eyre.workspace = true +tracing.workspace = true + +[dev-dependencies] +tempfile.workspace = true diff --git a/crates/firehose/src/args.rs b/crates/firehose/src/args.rs new file mode 100644 index 00000000000..d5f2ffafb8e --- /dev/null +++ b/crates/firehose/src/args.rs @@ -0,0 +1,130 @@ +//! CLI arguments for the Firehose integration. +//! +//! These arguments control how the Firehose tracer emits blocks to stdout, +//! where to write the cursor file, and how async emission is configured. + +use clap::Args; +use firehose_tracer::EmissionMode; +use std::{path::PathBuf, time::Duration}; + +/// Firehose emission mode, mirroring [`EmissionMode`] for CLI parsing. +#[derive(Debug, Clone, Default, clap::ValueEnum)] +pub enum EmissionModeArg { + /// Encode and write blocks inline on the calling thread (legacy behaviour). + Blocking, + /// Encode and write blocks in a dedicated background thread with backpressure. + Async, + /// Switch automatically based on block age (catch-up → async, live → blocking). + #[default] + Auto, +} + +/// CLI arguments for the Firehose tracer integration. +/// +/// Add `#[command(flatten)]` to include these in a `NodeCommand` extension struct. +#[derive(Debug, Clone, Default, Args)] +pub struct FirehoseArgs { + /// Controls when and how encoded blocks are written to stdout. + /// + /// - `blocking`: encode → base64 → write, all inline on the calling thread (legacy). + /// - `async`: encode and write in a background thread; backpressure via channel. + /// - `auto`: use async for blocks older than `--firehose.live-threshold`; use blocking for + /// blocks within the live window (default). + #[arg( + id = "firehose.emission-mode", + long = "firehose.emission-mode", + value_name = "MODE", + default_value = "auto", + verbatim_doc_comment + )] + pub emission_mode: EmissionModeArg, + + /// Channel capacity for the async emission path. + /// + /// The background writer thread will block producers once this many encoded + /// blocks are waiting, providing backpressure. Only relevant for `async` and + /// `auto` modes. + #[arg( + id = "firehose.channel-capacity", + long = "firehose.channel-capacity", + value_name = "N", + default_value_t = 32 + )] + pub channel_capacity: usize, + + /// Age threshold in seconds used by `auto` emission mode. + /// + /// Blocks with a timestamp more than this many seconds behind wall-clock time + /// are considered historical (catch-up) and will use the async path. + /// Blocks within this window are considered live and will use the blocking path. + #[arg( + id = "firehose.live-threshold", + long = "firehose.live-threshold", + value_name = "SECS", + default_value_t = 60 + )] + pub live_threshold_secs: u64, + + /// Path to the cursor file that tracks the last block successfully emitted to stdout. + /// + /// After each block is written the cursor file is updated atomically so that the + /// node can detect gaps after an unclean shutdown and re-emit the missing blocks + /// on the next startup. Defaults to `/firehose.cursor` when not set. + #[arg(id = "firehose.cursor-path", long = "firehose.cursor-path", value_name = "PATH")] + pub cursor_path: Option, +} + +impl FirehoseArgs { + /// Convert the parsed CLI args into a [`firehose_tracer::config::Config`]. + /// + /// `data_dir` is used to derive the default cursor file path when + /// `--firehose.cursor-path` is not specified. + pub fn to_tracer_config(&self, data_dir: &std::path::Path) -> firehose_tracer::config::Config { + let cursor_path = + self.cursor_path.clone().unwrap_or_else(|| data_dir.join("firehose.cursor")); + + let emission_mode = match self.emission_mode { + EmissionModeArg::Blocking => EmissionMode::Blocking, + EmissionModeArg::Async => { + EmissionMode::Async { channel_capacity: self.channel_capacity } + } + EmissionModeArg::Auto => EmissionMode::Auto { + channel_capacity: self.channel_capacity, + live_threshold: Duration::from_secs(self.live_threshold_secs), + }, + }; + + firehose_tracer::config::Config::new() + .with_emission_mode(emission_mode) + .with_cursor_path(cursor_path) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_emission_mode_is_auto() { + let args = FirehoseArgs::default(); + assert!(matches!(args.emission_mode, EmissionModeArg::Auto)); + } + + #[test] + fn to_tracer_config_uses_data_dir_for_cursor() { + let args = FirehoseArgs::default(); + let tmp = tempfile::tempdir().unwrap(); + let cfg = args.to_tracer_config(tmp.path()); + let expected_cursor = tmp.path().join("firehose.cursor"); + assert_eq!(cfg.cursor_path, Some(expected_cursor)); + } + + #[test] + fn to_tracer_config_respects_explicit_cursor_path() { + let custom = PathBuf::from("/custom/firehose.cursor"); + let args = FirehoseArgs { cursor_path: Some(custom.clone()), ..Default::default() }; + let tmp = tempfile::tempdir().unwrap(); + let cfg = args.to_tracer_config(tmp.path()); + assert_eq!(cfg.cursor_path, Some(custom)); + } +} diff --git a/crates/firehose/src/lib.rs b/crates/firehose/src/lib.rs new file mode 100644 index 00000000000..412164cebdb --- /dev/null +++ b/crates/firehose/src/lib.rs @@ -0,0 +1,110 @@ +//! Reth ↔ Firehose integration. +//! +//! This crate wires the [`firehose_tracer`] library into the reth node by +//! providing: +//! +//! * **`FirehoseArgs`** — clap argument group that exposes `--firehose.*` CLI flags (emission mode, +//! channel capacity, live threshold, cursor path). +//! * **`init_tracer`** — one-shot initialisation that stores the tracer in a process-wide +//! `OnceLock` and returns an optional [`ShutdownHandle`] for the async emission background +//! thread. +//! * **`check_gap_and_re_trace`** — startup hook that detects gaps between the cursor file and the +//! execution stage checkpoint, and re-emits the missing blocks before the sync pipeline resumes. +//! +//! # Usage in `bin/reth/src/main.rs` +//! +//! ```rust,ignore +//! use reth_firehose::{FirehoseArgs, init_tracer, check_gap_and_re_trace}; +//! +//! // … inside the async run closure … +//! let handle = builder +//! .node(EthereumNode::default()) +//! .on_component_initialized(move |node| { +//! let data_dir = node.config.datadir().data_dir().to_path_buf(); +//! let cfg = firehose_args.to_tracer_config(&data_dir); +//! let cursor_path = cfg.cursor_path.clone(); +//! let shutdown_handle = init_tracer(cfg); +//! +//! check_gap_and_re_trace(&node.provider, cursor_path.as_deref())?; +//! +//! if let Some(handle) = shutdown_handle { +//! node.task_executor.spawn_with_graceful_shutdown_signal(|shutdown| async move { +//! let _guard = shutdown.await; +//! handle.drain(); +//! }); +//! } +//! Ok(()) +//! }) +//! .launch_with_debug_capabilities() +//! .await?; +//! ``` + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +pub mod args; +pub mod re_trace; + +pub use args::FirehoseArgs; +pub use re_trace::{check_gap_and_re_trace, firehose_re_trace_range}; + +use firehose_tracer::{config::Config, ShutdownHandle, Tracer}; +use std::sync::{Mutex, OnceLock}; +use tracing::{debug, info}; + +/// Process-wide Firehose tracer, initialised at most once per process. +pub static GLOBAL_TRACER: OnceLock> = OnceLock::new(); + +/// Initialise the global Firehose tracer with the given configuration. +/// +/// Returns a [`ShutdownHandle`] when the emission mode has an async background +/// thread (i.e. [`EmissionMode::Async`] or [`EmissionMode::Auto`]). The caller +/// is responsible for calling [`ShutdownHandle::drain`] before the process exits +/// so that the background thread can flush all buffered blocks. +/// +/// # Panics +/// +/// Panics if called more than once in the same process, so that accidental +/// double-initialisation is caught early during development. +pub fn init_tracer(config: Config) -> Option { + info!( + target: "reth::firehose", + cursor_path = ?config.cursor_path, + "Initialising Firehose tracer" + ); + + let mut tracer = Tracer::new(config); + let shutdown_handle = tracer.shutdown_handle(); + + debug!( + target: "reth::firehose", + has_async_thread = shutdown_handle.is_some(), + "Firehose tracer created" + ); + + GLOBAL_TRACER.set(Mutex::new(tracer)).unwrap_or_else(|_| { + panic!("init_tracer called more than once; Firehose tracer already initialised"); + }); + + shutdown_handle +} + +/// Returns a reference to the global [`Mutex`], or `None` if the +/// tracer has not been initialised via [`init_tracer`]. +/// +/// The lock should be held only for the duration of a single tracer method +/// call to minimise contention. +pub fn get_tracer() -> Option<&'static Mutex> { + GLOBAL_TRACER.get() +} + +#[cfg(test)] +mod tests { + // NOTE: Because GLOBAL_TRACER is a process-wide OnceLock, unit tests that + // call init_tracer cannot be run in the same process as each other. + // The args and re_trace modules have their own self-contained tests. +} diff --git a/crates/firehose/src/re_trace.rs b/crates/firehose/src/re_trace.rs new file mode 100644 index 00000000000..76c07bdfbe2 --- /dev/null +++ b/crates/firehose/src/re_trace.rs @@ -0,0 +1,221 @@ +//! Gap detection and re-trace logic for the Firehose integration. +//! +//! On startup the node may have executed blocks that were never written to stdout +//! (e.g. due to a crash between execution and emission, or because emission was +//! disabled for a period). This module detects such gaps and re-emits the missing +//! blocks before the sync pipeline resumes. + +use std::{ops::RangeInclusive, path::Path}; + +use eyre::Context as _; +use reth_stages_types::StageId; +use reth_storage_api::{BlockNumReader, StageCheckpointReader}; +use tracing::{error, info, warn}; + +use crate::GLOBAL_TRACER; + +/// Checks for a gap between the cursor file and the execution stage checkpoint, +/// then re-emits any missing blocks. +/// +/// Should be called **before** the sync pipeline starts so that consumers of the +/// Firehose output see a contiguous stream of blocks. +/// +/// # Errors +/// +/// Returns an error if: +/// - The execution stage checkpoint cannot be read. +/// - Historical state required for re-tracing has been pruned (archive node required when a gap is +/// detected). +/// - The re-trace itself fails. +pub fn check_gap_and_re_trace

(provider: &P, cursor_path: Option<&Path>) -> eyre::Result<()> +where + P: StageCheckpointReader + BlockNumReader, +{ + // Read the last block that was successfully written to stdout. + let last_written = cursor_path.and_then(read_cursor); + + let Some(last_written) = last_written else { + // No cursor file → either first run or cursor tracking is disabled. + info!(target: "reth::firehose", "No Firehose cursor found; skipping gap check"); + return Ok(()); + }; + + // Read the execution stage tip. + let exec_checkpoint = provider + .get_stage_checkpoint(StageId::Execution) + .context("Failed to read execution stage checkpoint")?; + + let exec_tip = match exec_checkpoint { + Some(cp) => cp.block_number, + None => { + info!(target: "reth::firehose", "Execution stage not yet started; skipping gap check"); + return Ok(()); + } + }; + + if last_written >= exec_tip { + info!( + target: "reth::firehose", + last_written, + exec_tip, + "Firehose cursor is current; no gap detected" + ); + return Ok(()); + } + + let gap = (last_written + 1)..=exec_tip; + warn!( + target: "reth::firehose", + last_written, + exec_tip, + gap_size = exec_tip - last_written, + "Firehose gap detected; re-emitting missing blocks" + ); + + firehose_re_trace_range(gap, provider) +} + +/// Re-emits blocks in `range` to the Firehose stdout stream. +/// +/// For each block the function: +/// 1. Verifies that the required historical state is available (pruning guard). +/// 2. Executes the block through a Firehose-aware executor. +/// 3. Emits the `FIRE BLOCK` line and updates the cursor file. +/// +/// # Pruning guard +/// +/// If the node has been configured to prune state and the required historical +/// blocks are no longer available, this function returns a fatal error. Firehose +/// requires an archive (or sufficiently un-pruned) node to be able to re-trace +/// historical blocks. +/// +/// # Errors +/// +/// Returns an error if historical state is unavailable or re-execution fails. +pub fn firehose_re_trace_range

(range: RangeInclusive, provider: &P) -> eyre::Result<()> +where + P: StageCheckpointReader + BlockNumReader, +{ + let first = *range.start(); + + // ── Pruning guard ──────────────────────────────────────────────────────── + // Check whether the first block in the gap is still available. + // `best_block_number` going below `first` means we've pruned past the gap. + let earliest = provider + .best_block_number() + .context("Failed to query best block number for pruning check")?; + + // If the earliest available block number is already past `first`, we cannot + // re-trace. This heuristic is conservative; a tighter check would look at + // the prune configuration directly. + if earliest > first && first > 0 { + error!( + target: "reth::firehose", + first_gap_block = first, + earliest_available = earliest, + "Historical state required for Firehose re-trace has been pruned. \ + Firehose requires an archive node (or pruning configured to retain \ + state back to block {}). \ + Node cannot start without re-emitting the missing blocks.", + first + ); + eyre::bail!( + "Firehose gap re-trace failed: state at block {} has been pruned. \ + An archive node is required to re-emit the missing Firehose blocks. \ + Adjust your pruning configuration or restore from a Firehose-compatible snapshot.", + first + ); + } + + // ── Re-trace loop ──────────────────────────────────────────────────────── + for block_num in range.clone() { + re_trace_single_block(block_num, provider)?; + } + + info!( + target: "reth::firehose", + start = *range.start(), + end = *range.end(), + "Firehose gap re-trace complete" + ); + Ok(()) +} + +/// Re-executes a single block and emits its Firehose output. +/// +/// # Note on full trace fidelity +/// +/// A complete Firehose block includes per-call traces (call stack, storage/balance +/// changes at the opcode level). Producing that level of detail requires a +/// Firehose-aware EVM inspector wired into the block executor. The current +/// implementation calls the minimum set of hooks needed to emit a structurally +/// valid `FIRE BLOCK` line (block start → block end). Full call-level tracing +/// will be available once the `FirehoseEvmInspector` is integrated into the reth +/// block executor pipeline. +fn re_trace_single_block

(block_num: u64, _provider: &P) -> eyre::Result<()> +where + P: StageCheckpointReader + BlockNumReader, +{ + let tracer_lock = GLOBAL_TRACER.get().ok_or_else(|| { + eyre::eyre!("Firehose tracer not initialized; call init_tracer before re-tracing") + })?; + + let _tracer = + tracer_lock.lock().map_err(|e| eyre::eyre!("Firehose tracer lock poisoned: {e}"))?; + + // TODO: Implement full block re-execution with the FirehoseEvmInspector. + // + // The complete implementation must: + // 1. Fetch `RecoveredBlock` via `provider.recovered_block(block_num, ...)`. + // 2. Obtain a historical state provider via `provider.history_by_block_number(block_num - + // 1)`. + // 3. Create a `State>` wrapping the state provider. + // 4. Build a `FirehoseEvmInspector` wrapping `tracer`. + // 5. Execute the block with `evm_config.executor_for_block(&mut state, &block)`, passing the + // inspector so that `on_tx_start/end`, `on_call_enter/exit`, `on_balance_change`, + // `on_storage_change`, and `on_log` are called. + // 6. Call `tracer.on_block_end(None)` to emit the `FIRE BLOCK` line. + // + // This requires the EVM config to be passed into this function. The signature + // will be extended once `FirehoseEvmInspector` is available. + eyre::bail!( + "Full Firehose block re-trace for block {} requires the FirehoseEvmInspector \ + integration which is not yet available in this build. \ + Ensure the node has not missed any blocks before disabling the Firehose tracer.", + block_num + ) +} + +/// Reads the last confirmed block number from the cursor file. +/// +/// Returns `None` when the file is absent, empty, or cannot be parsed. +fn read_cursor(path: &Path) -> Option { + let content = std::fs::read_to_string(path).ok()?; + content.trim().parse::().ok() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + use tempfile::NamedTempFile; + + #[test] + fn read_cursor_returns_none_for_missing_file() { + assert!(read_cursor(Path::new("/nonexistent/firehose.cursor")).is_none()); + } + + #[test] + fn read_cursor_parses_valid_file() { + let mut f = NamedTempFile::new().unwrap(); + writeln!(f, "12345").unwrap(); + assert_eq!(read_cursor(f.path()), Some(12345)); + } + + #[test] + fn read_cursor_returns_none_for_invalid_content() { + let mut f = NamedTempFile::new().unwrap(); + writeln!(f, "not-a-number").unwrap(); + assert!(read_cursor(f.path()).is_none()); + } +} diff --git a/vendor/rbase64/.cargo-ok b/vendor/rbase64/.cargo-ok new file mode 100644 index 00000000000..5f8b795830a --- /dev/null +++ b/vendor/rbase64/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/vendor/rbase64/.cargo_vcs_info.json b/vendor/rbase64/.cargo_vcs_info.json new file mode 100644 index 00000000000..2f6bbe525b4 --- /dev/null +++ b/vendor/rbase64/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "1f289f0a9897479880250eb099b008b2e7ac2efe" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/vendor/rbase64/.github/workflows/audit.yml b/vendor/rbase64/.github/workflows/audit.yml new file mode 100644 index 00000000000..d07114c5d48 --- /dev/null +++ b/vendor/rbase64/.github/workflows/audit.yml @@ -0,0 +1,28 @@ +on: + pull_request: + push: + branches: + - main + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + schedule: + - cron: '3 3 3 * *' + +name: Security audit + +permissions: + contents: read + +jobs: + security_audit: + permissions: + issues: write # to create issues (actions-rs/audit-check) + checks: write # to create check (actions-rs/audit-check) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/vendor/rbase64/.github/workflows/ci.yml b/vendor/rbase64/.github/workflows/ci.yml new file mode 100644 index 00000000000..717e47c805f --- /dev/null +++ b/vendor/rbase64/.github/workflows/ci.yml @@ -0,0 +1,156 @@ +on: + pull_request: + push: + branches: + - main + tags: + - 'v*' + workflow_dispatch: + +name: CI + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + override: true + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features + name: Clippy Output + + test: + name: Test + env: + PROJECT_NAME_UNDERSCORE: rbase64 + CARGO_INCREMENTAL: 0 + CARGO_OPTIONS: --features=cli + RUSTFLAGS: -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort + RUSTDOCFLAGS: -Cpanic=abort + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + - name: Cache dependencies + uses: actions/cache@v2 + env: + cache-name: cache-dependencies + with: + path: | + ~/.cargo/.crates.toml + ~/.cargo/.crates2.json + ~/.cargo/bin + ~/.cargo/registry/index + ~/.cargo/registry/cache + target + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Cargo.lock') }} + - name: Generate test result and coverage report + run: | + cargo install cargo2junit grcov; + cargo test --features cli $CARGO_OPTIONS -- -Z unstable-options --format json | cargo2junit > results.xml; + zip -0 ccov.zip `find . \( -name "$PROJECT_NAME_UNDERSCORE*.gc*" \) -print`; + grcov ccov.zip -s . -t lcov --llvm --ignore-not-existing --ignore "/*" --ignore "tests/*" -o lcov.info; + - name: Upload test results + uses: EnricoMi/publish-unit-test-result-action@v1 + with: + check_name: Test Results + github_token: ${{ secrets.GITHUB_TOKEN }} + files: results.xml + - name: Upload test coverage + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: lcov.info + + profiling: + name: Profiling + if: github.ref != 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: Checkout main + uses: actions/checkout@v3 + with: + ref: main + - name: Profiling baseline + run: cargo bench --bench benchmarks -- --save-baseline main + - name: Checkout HEAD + uses: actions/checkout@v3 + with: + clean: false + - name: Profiling changes + run: cargo bench --bench benchmarks -- --baseline main > results.txt + - name: Setup nodejs + uses: actions/setup-node@v3 + - name: Generate report markdown + run: (echo '# Profiling Report' && echo '```diff' && cat results.txt && echo '```') | ./scripts/diff.js > report.md + - name: Display report + uses: mshick/add-pr-comment@v2 + with: + message-path: report.md + + publish: + name: Publish artifact + needs: [check, fmt, clippy, test] + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: cargo publish --token ${CRATES_TOKEN} + env: + CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} \ No newline at end of file diff --git a/vendor/rbase64/.github/workflows/release-packaging.yml b/vendor/rbase64/.github/workflows/release-packaging.yml new file mode 100644 index 00000000000..be95cd94ef7 --- /dev/null +++ b/vendor/rbase64/.github/workflows/release-packaging.yml @@ -0,0 +1,27 @@ +on: + push: + branches: + - main + +name: Release Packaging + +jobs: + release: + name: Release Packaging + env: + PROJECT_NAME_UNDERSCORE: rbase64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: Release Build + run: cargo build --release + - name: 'Upload Artifact' + uses: actions/upload-artifact@v2 + with: + name: ${{ env.PROJECT_NAME_UNDERSCORE }} + path: target/release/${{ env.PROJECT_NAME_UNDERSCORE }} diff --git a/vendor/rbase64/.gitignore b/vendor/rbase64/.gitignore new file mode 100644 index 00000000000..2a0038a460f --- /dev/null +++ b/vendor/rbase64/.gitignore @@ -0,0 +1,2 @@ +/target +.idea \ No newline at end of file diff --git a/vendor/rbase64/Cargo.lock b/vendor/rbase64/Cargo.lock new file mode 100644 index 00000000000..cbd5369e8b7 --- /dev/null +++ b/vendor/rbase64/Cargo.lock @@ -0,0 +1,1017 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[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 = "assert_cmd" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba45b8163c49ab5f972e59a8a5a03b6d2972619d486e19ec9fe744f7c2753d3c" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" +dependencies = [ + "memchr", + "once_cell", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "num-integer", + "num-traits", + "winapi", +] + +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "3.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +dependencies = [ + "bitflags", + "clap_lex 0.2.4", + "indexmap", + "textwrap", +] + +[[package]] +name = "clap" +version = "4.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb41c13df48950b20eb4cd0eefa618819469df1bffc49d11e8487c4ba0037e5" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex 0.3.0", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap 3.2.23", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cxx" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +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 = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d1c67deb83e6b75fa4fe3309e09cfeade12e7721d95322af500d3814ea60c9" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mimalloc" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2374e2999959a7b583e1811a1ddbf1d3a4b9496eceb9746f1192a59d871eca" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "os_str_bytes" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" + +[[package]] +name = "plotters" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "predicates" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab68289ded120dcbf9d571afcf70163233229052aec9b08ab09532f698d0e1e6" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e7125585d872860e9955ca571650b27a4979c5823084168c5ed5bbfb016b56" + +[[package]] +name = "predicates-tree" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad3f7fa8d61e139cbc7c3edfebf3b6678883a53f5ffac65d1259329a93ee43a5" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" +dependencies = [ + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "rbase64" +version = "2.0.3" +dependencies = [ + "assert_cmd", + "chrono", + "clap 4.0.23", + "criterion", + "mimalloc", + "predicates", + "rand", + "rayon", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[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 = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[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 = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/vendor/rbase64/Cargo.toml b/vendor/rbase64/Cargo.toml new file mode 100644 index 00000000000..59c72a0df41 --- /dev/null +++ b/vendor/rbase64/Cargo.toml @@ -0,0 +1,74 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "rbase64" +version = "2.0.3" +authors = ["Marcel Riera "] +description = "A fast multi-threaded base64 encoding library and CLI tool" +homepage = "https://github.com/uhmarcel/rbase64" +readme = "README.md" +keywords = [ + "base64", + "encode", + "decode", +] +categories = [ + "encoding", + "command-line-utilities", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/uhmarcel/rbase64" + +[[bin]] +name = "rbase64" +required-features = ["cli"] + +[[test]] +name = "cli-tests" +required-features = ["cli"] + +[[bench]] +name = "benchmarks" +harness = false + +[dependencies.clap] +version = "4.0" +features = ["derive"] +optional = true + +[dependencies.rayon] +version = "1.6.0" +optional = true + +[dev-dependencies.assert_cmd] +version = "2.0" + +[dev-dependencies.chrono] +version = "0.4.23" +features = ["clock"] +default-features = false + +[dev-dependencies.criterion] +version = "0.4.0" + +[dev-dependencies.predicates] +version = "2.1" + +[dev-dependencies.rand] +version = "0.8.5" +features = ["small_rng"] + +[features] +cli = ["clap"] +default = ["parallel"] +parallel = ["rayon"] diff --git a/vendor/rbase64/Cargo.toml.orig b/vendor/rbase64/Cargo.toml.orig new file mode 100644 index 00000000000..abf7e8d084e --- /dev/null +++ b/vendor/rbase64/Cargo.toml.orig @@ -0,0 +1,41 @@ +[package] +name = "rbase64" +version = "2.0.3" +edition = "2021" +description = "A fast multi-threaded base64 encoding library and CLI tool" +authors = ["Marcel Riera "] +license = "MIT OR Apache-2.0" +keywords = ["base64", "encode", "decode"] +categories = ["encoding", "command-line-utilities"] +homepage = "https://github.com/uhmarcel/rbase64" +repository = "https://github.com/uhmarcel/rbase64" +readme = "README.md" + +[dependencies] +mimalloc = { version = "0.1.32", default-features = false } +clap = { version = "4.0", features = ["derive"], optional = true } +rayon = { version = "1.6.0", optional = true } + +[dev-dependencies] +assert_cmd = "2.0" +predicates = "2.1" +chrono = { version = "0.4.23", default-features = false, features = ["clock"] } +criterion = "0.4.0" +rand = { version = "0.8.5", features = ["small_rng"] } + +[features] +default = ["parallel"] +cli = ["clap"] +parallel = ["rayon"] + +[[bin]] +name = "rbase64" +required-features = ["cli"] + +[[test]] +name = "cli-tests" +required-features = ["cli"] + +[[bench]] +name = "benchmarks" +harness = false diff --git a/vendor/rbase64/LICENSE-APACHE b/vendor/rbase64/LICENSE-APACHE new file mode 100644 index 00000000000..c199c948495 --- /dev/null +++ b/vendor/rbase64/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Marcel Riera + + 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. diff --git a/vendor/rbase64/LICENSE-MIT b/vendor/rbase64/LICENSE-MIT new file mode 100644 index 00000000000..9611241437b --- /dev/null +++ b/vendor/rbase64/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Marcel Riera + +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/vendor/rbase64/README.md b/vendor/rbase64/README.md new file mode 100644 index 00000000000..ff0b3923817 --- /dev/null +++ b/vendor/rbase64/README.md @@ -0,0 +1,203 @@ +# rbase64 +A fast multi-threaded base64 encoding / decoding library and CLI tool, made in Rust. + +[![Crates.io](https://img.shields.io/crates/v/rbase64?style=flat-square)](https://crates.io/crates/rbase64) +[![Crates.io](https://img.shields.io/crates/d/rbase64?style=flat-square)](https://crates.io/crates/rbase64) +[![Build Status](https://img.shields.io/github/workflow/status/uhmarcel/rbase64/CI/main?style=flat-square)](https://github.com/uhmarcel/rbase64/actions/workflows/ci.yml?query=branch%3Amain) +[![Coverage Status](https://coveralls.io/repos/github/uhmarcel/rbase64/badge.svg?branch=main)](https://coveralls.io/github/uhmarcel/rbase64?branch=main) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](LICENSE-APACHE) +[![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE-MIT) + +--- + +Have you ever wanted to base64 encode tens of gigabytes of data, but felt your encoder was not fast enough? Me neither. +Either way, +rbase64 is up to the task. + + +The goal of rbase64 is to provide a fast implementation of base64 encoding. The library is thoroughly tested and optimized +for high throughput. As of v2.0.3, rbase64 is able to achieve up to 11.133 GiB/s encoding and 14.251 GiB/s decoding rates (measured +in MacBook Air M1 2020 laptop, 10~20 MiB batches). + +The project also comes with a command line interface powered by the library encoder. While limited by I/O, +it still achieves high performance compared to other alternatives (for example, GNU base64 by a factor of 11x). + +For details, see [Performance](#performance). + +# Usage +## Library +Add rbase64 to your Cargo.toml dependencies: +```toml +[dependencies] +rbase64 = "2.0.3" +``` + +**Sample usage:** +```rust +use rbase64; + +fn main() { + let a = b"Hello world"; + let b = "SGVsbG8gd29ybGQ="; + + assert_eq!(rbase64::encode(a), b); + assert_eq!(&rbase64::decode(b).unwrap(), a); +} +``` + +## CLI +**Installation:** +```sh +$ cargo install rbase64 --features cli +``` + + +**Usage**: rbase64 [OPTIONS] + +Options | Description +---------------------------- | ---------------------------- +**-d, --decode** | Decode input (default: false) +**-i, --input \** | Input file (stdin if missing) +**-o, --output \** | Output file (stdout if missing) +**-h, --help** | Print help information +**-V, --version** | Print version information + +Rbase64 CLI is designed to have the same API as classic GNU base64, however, **it does not append +newline characters to file or stdout output**. + +**Basic usage:** +```sh +$ echo -n "Hello world" | rbase64 +SGVsbG8gd29ybGQ= +$ echo -n "SGVsbG8gd29ybGQ=" | rbase64 --decode +Hello world +``` +# Performance + +## Library +Performance is measured using benchmarks with Criterion. Each bench measures encoding/decoding +speed and throughput at varying input sizes in bytes. Measurements below were taken by running +benchmarks on a MacBook Air M1 2020 laptop. Results are compared against three of the top most +widely used Rust base64 encoding libraries in [cargo.io](https://crates.io/search?q=base64&sort=downloads). + +### Encoding +| Bench (type / bytes) | *rbase64 (v2.0.3)* | [base64 (v0.20.0)](https://github.com/marshallpierce/rust-base64) | [data-encoding (v2.3.2)](https://github.com/ia0/data-encoding) | [rustc-serialize (v0.3.24)](https://github.com/rust-lang/rustc-serialize) | +|-----------------|--------------|--------------|--------------|--------------| +| encode/3 | 186.21 MiB/s | 77.240 MiB/s | 77.208 MiB/s | 91.615 MiB/s | +| encode/50 | 1.3780 GiB/s | 904.74 MiB/s | 882.87 MiB/s | 806.29 MiB/s | +| encode/100 | 1.7103 GiB/s | 1.2844 GiB/s | 1.3009 GiB/s | 1.0334 GiB/s | +| encode/500 | 2.5775 GiB/s | 1.8897 GiB/s | 1.9288 GiB/s | 1.2626 GiB/s | +| encode/3072 | 2.7913 GiB/s | 2.3780 GiB/s | 2.4211 GiB/s | 1.4501 GiB/s | +| encode/51200 | 2.8454 GiB/s | 2.4486 GiB/s | 2.4678 GiB/s | 1.4765 GiB/s | +| encode/102400 | 2.8644 GiB/s | 2.4247 GiB/s | 2.4756 GiB/s | 1.4799 GiB/s | +| encode/512000 | 6.1009 GiB/s | 2.5105 GiB/s | 2.5174 GiB/s | 1.4623 GiB/s | +| encode/1048576 | 8.0095 GiB/s | 2.5115 GiB/s | 2.5182 GiB/s | 1.4506 GiB/s | +| encode/5242880 | 10.042 GiB/s | 2.1806 GiB/s | 2.2009 GiB/s | 1.3416 GiB/s | +| encode/10485760 | 11.133 GiB/s | 2.0549 GiB/s | 2.1037 GiB/s | 1.3438 GiB/s | +| encode/20971520 | 10.804 GiB/s | 2.0814 GiB/s | 2.0638 GiB/s | 1.3138 GiB/s | + +### Decoding +| Bench (type / bytes) | *rbase64 (v2.0.3)* | [base64 (v0.20.0)](https://github.com/marshallpierce/rust-base64) | [data-encoding (v2.3.2)](https://github.com/ia0/data-encoding) | [rustc-serialize (v0.3.24)](https://github.com/rust-lang/rustc-serialize) | +|-----------------|--------------|--------------|--------------|--------------| +| decode/3 | 235.57 MiB/s | 52.127 MiB/s | 55.164 MiB/s | 96.523 MiB/s | +| decode/50 | 1.9261 GiB/s | 540.87 MiB/s | 728.58 MiB/s | 550.51 MiB/s | +| decode/100 | 2.5807 GiB/s | 942.93 MiB/s | 1.1012 GiB/s | 579.12 MiB/s | +| decode/500 | 3.3135 GiB/s | 1.8286 GiB/s | 1.7942 GiB/s | 563.76 MiB/s | +| decode/3072 | 3.4941 GiB/s | 2.9708 GiB/s | 2.2418 GiB/s | 609.78 MiB/s | +| decode/51200 | 3.7189 GiB/s | 3.0924 GiB/s | 2.3634 GiB/s | 618.20 MiB/s | +| decode/102400 | 3.7623 GiB/s | 3.0878 GiB/s | 2.4015 GiB/s | 618.18 MiB/s | +| decode/512000 | 7.0065 GiB/s | 3.1235 GiB/s | 2.4229 GiB/s | 618.47 MiB/s | +| decode/1048576 | 9.3497 GiB/s | 3.1195 GiB/s | 2.4073 GiB/s | 617.00 MiB/s | +| decode/5242880 | 12.148 GiB/s | 2.8267 GiB/s | 2.2475 GiB/s | 600.88 MiB/s | +| decode/10485760 | 13.424 GiB/s | 2.7416 GiB/s | 2.2384 GiB/s | 599.09 MiB/s | +| decode/20971520 | 14.251 GiB/s | 2.6512 GiB/s | 2.1692 GiB/s | 596.77 MiB/s | + +## CLI +Manual benchmarks using randomized binary files. Compared against classic +GNU base64, and race64 (C). + +

+ Test 1GiB random bytes + +- GNU base64 +```sh +$ time (cat random-1gb.bin | base64 | pv -a > /dev/null) +[ 164MiB/s] +( cat random-1gb.bin | base64 | pv -a > /dev/null; ) 7.89s user 0.96s system 106% cpu 8.317 total + +$ time (cat random-1gb.b64 | base64 --decode | pv -a > /dev/null) +[ 105MiB/s] +( cat random-1gb.b64 | base64 --decode | pv -a > /dev/null; ) 9.16s user 1.01s system 104% cpu 9.699 total +``` + +- race64 (C) +```sh +$ time (cat random-1gb.bin | ./race64 | pv -a > /dev/null) +[ 898MiB/s] +( cat random-1gb.bin | ./race64 | pv -a > /dev/null; ) 0.88s user 1.14s system 128% cpu 1.566 total + +$ time (cat random-1gb.b64 | ./race64 -d | pv -a > /dev/null) +[ 723MiB/s] +( cat random-1gb.b64 | ./race64 -d | pv -a > /dev/null; ) 0.87s user 0.95s system 127% cpu 1.426 total +``` + +- ***rbase64*** +```sh +$ time (cat random-1gb.bin | rbase64 | pv -a > /dev/null) +[1.71GiB/s] +( cat random-1gb.bin | rbase64 | pv -a > /dev/null; ) 0.56s user 0.40s system 121% cpu 0.788 total + +$ time (cat random-1gb.b64 | rbase64 --decode | pv -a > /dev/null) +[1.16GiB/s] +( cat random-1gb.b64 | rbase64 --decode | pv -a > /dev/null; ) 0.59s user 0.46s system 119% cpu 0.871 total +``` +
+ + +
+ Test 10GiB random bytes + +- GNU base64 +```sh +$ time (cat random-10gb.bin | base64 | pv -a > /dev/null) +[ 154MiB/s] +( cat random-10gb.bin | base64 | pv -a > /dev/null; ) 78.74s user 10.96s system 101% cpu 1:28.34 total + +$ time (cat random-10gb.b64 | base64 --decode | pv -a > /dev/null) +[ 107MiB/s] +( cat random-10gb.b64 | base64 --decode | pv -a > /dev/null; ) 91.02s user 10.00s system 105% cpu 1:35.39 total + +``` + +- race64 (C) +```sh +$ time (cat random-10gb.bin | ./race64 | pv -a > /dev/null) +[ 818MiB/s] +( cat random-10gb.bin | ./race64 | pv -a > /dev/null; ) 8.56s user 13.17s system 128% cpu 16.917 total + +$ time (cat random-10gb.b64 | ./race64 -d | pv -a > /dev/null) +[ 724MiB/s] +( cat random-10gb.b64 | ./race64 -d | pv -a > /dev/null; ) 8.42s user 9.05s system 123% cpu 14.152 total +``` + +- ***rbase64*** +```sh +$ time (cat random-10gb.bin | rbase64 | pv -a > /dev/null) +[1.64GiB/s] +( cat random-10gb.bin | rbase64 | pv -a > /dev/null; ) 5.30s user 4.22s system 117% cpu 8.125 total + +$ time (cat random-10gb.b64 | rbase64 --decode | pv -a > /dev/null) +[1.18GiB/s] +( cat random-10gb.b64 | rbase64 --decode | pv -a > /dev/null; ) 5.58s user 4.40s system 117% cpu 8.491 total +``` +
+ +For reference, here's the throughput of piping a file via ```cat``` with no processing: +```sh +$ time (cat random-10gb.b64 | pv -a > /dev/null) +[2.31GiB/s] +( cat random-10gb.b64 | pv -a > /dev/null; ) 0.88s user 4.72s system 95% cpu 5.858 total +``` + +# License +This project is dual-licensed under MIT and Apache 2.0. \ No newline at end of file diff --git a/vendor/rbase64/benches/baseline.md b/vendor/rbase64/benches/baseline.md new file mode 100644 index 00000000000..74efe2e8bb5 --- /dev/null +++ b/vendor/rbase64/benches/baseline.md @@ -0,0 +1,234 @@ +# Profiling Report +```diff + +encode/3 time: [16.456 ns 16.494 ns 16.528 ns] + thrpt: [173.10 MiB/s 173.46 MiB/s 173.86 MiB/s] + change: +- time: [+4.4885% +4.7129% +4.9515%] (p = 0.00 < 0.05) +- thrpt: [-4.7179% -4.5008% -4.2957%] +- Performance has regressed. + +encode/50 time: [34.108 ns 34.187 ns 34.259 ns] + thrpt: [1.3592 GiB/s 1.3621 GiB/s 1.3652 GiB/s] + change: + time: [-1.4238% -1.1800% -0.9359%] (p = 0.00 < 0.05) + thrpt: [+0.9447% +1.1941% +1.4443%] + Change within noise threshold. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild + +encode/100 time: [54.533 ns 54.655 ns 54.792 ns] + thrpt: [1.6997 GiB/s 1.7040 GiB/s 1.7078 GiB/s] + change: + time: [-1.0111% -0.7870% -0.5747%] (p = 0.00 < 0.05) + thrpt: [+0.5781% +0.7933% +1.0214%] + Change within noise threshold. + +encode/500 time: [179.02 ns 179.48 ns 179.95 ns] + thrpt: [2.5878 GiB/s 2.5945 GiB/s 2.6011 GiB/s] + change: + time: [-1.2171% -0.9906% -0.7346%] (p = 0.00 < 0.05) + thrpt: [+0.7400% +1.0005% +1.2320%] + Change within noise threshold. + +encode/3072 time: [1.0194 µs 1.0216 µs 1.0239 µs] + thrpt: [2.7941 GiB/s 2.8005 GiB/s 2.8065 GiB/s] + change: + time: [-0.8454% -0.6297% -0.4378%] (p = 0.00 < 0.05) + thrpt: [+0.4397% +0.6337% +0.8526%] + Change within noise threshold. + +encode/51200 time: [16.643 µs 16.678 µs 16.716 µs] + thrpt: [2.8526 GiB/s 2.8591 GiB/s 2.8650 GiB/s] + change: + time: [-0.9556% -0.7178% -0.5073%] (p = 0.00 < 0.05) + thrpt: [+0.5098% +0.7230% +0.9648%] + Change within noise threshold. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild + +encode/102400 time: [33.531 µs 33.601 µs 33.667 µs] + thrpt: [2.8327 GiB/s 2.8382 GiB/s 2.8441 GiB/s] + change: + time: [-1.1709% -0.9768% -0.7508%] (p = 0.00 < 0.05) + thrpt: [+0.7565% +0.9864% +1.1847%] + Change within noise threshold. + +encode/512000 time: [77.039 µs 77.515 µs 78.016 µs] + thrpt: [6.1120 GiB/s 6.1515 GiB/s 6.1895 GiB/s] + change: + time: [-0.0394% +1.1245% +2.3684%] (p = 0.07 > 0.05) + thrpt: [-2.3136% -1.1120% +0.0395%] + No change in performance detected. +Found 6 outliers among 100 measurements (6.00%) + 1 (1.00%) low mild + 4 (4.00%) high mild + 1 (1.00%) high severe + +encode/1048576 time: [121.00 µs 121.71 µs 122.50 µs] + thrpt: [7.9722 GiB/s 8.0236 GiB/s 8.0708 GiB/s] + change: + time: [-0.3161% +0.9008% +2.1738%] (p = 0.16 > 0.05) + thrpt: [-2.1275% -0.8927% +0.3171%] + No change in performance detected. +Found 9 outliers among 100 measurements (9.00%) + 3 (3.00%) low mild + 2 (2.00%) high mild + 4 (4.00%) high severe + +encode/5242880 time: [483.52 µs 485.32 µs 487.18 µs] + thrpt: [10.023 GiB/s 10.061 GiB/s 10.098 GiB/s] + change: + time: [-0.4425% +0.8796% +2.0624%] (p = 0.18 > 0.05) + thrpt: [-2.0207% -0.8719% +0.4445%] + No change in performance detected. +Found 8 outliers among 100 measurements (8.00%) + 2 (2.00%) high mild + 6 (6.00%) high severe + +encode/10485760 time: [868.23 µs 872.71 µs 877.73 µs] + thrpt: [11.126 GiB/s 11.190 GiB/s 11.248 GiB/s] + change: + time: [-0.2014% +0.6031% +1.3685%] (p = 0.15 > 0.05) + thrpt: [-1.3500% -0.5995% +0.2018%] + No change in performance detected. +Found 11 outliers among 100 measurements (11.00%) + 6 (6.00%) high mild + 5 (5.00%) high severe + +encode/20971520 time: [1.7922 ms 1.8032 ms 1.8160 ms] + thrpt: [10.755 GiB/s 10.831 GiB/s 10.898 GiB/s] + change: + time: [-0.3838% +0.3308% +1.0717%] (p = 0.39 > 0.05) + thrpt: [-1.0604% -0.3297% +0.3853%] + No change in performance detected. +Found 11 outliers among 100 measurements (11.00%) + 2 (2.00%) high mild + 9 (9.00%) high severe + + +decode/3 time: [12.138 ns 12.144 ns 12.152 ns] + thrpt: [235.45 MiB/s 235.59 MiB/s 235.71 MiB/s] + change: + time: [-0.1182% -0.0176% +0.0875%] (p = 0.75 > 0.05) + thrpt: [-0.0874% +0.0176% +0.1184%] + No change in performance detected. +Found 13 outliers among 100 measurements (13.00%) + 3 (3.00%) high mild + 10 (10.00%) high severe + +decode/50 time: [24.163 ns 24.179 ns 24.197 ns] + thrpt: [1.9244 GiB/s 1.9259 GiB/s 1.9272 GiB/s] + change: ++ time: [-7.7921% -7.6623% -7.5489%] (p = 0.00 < 0.05) ++ thrpt: [+8.1653% +8.2981% +8.4506%] ++ Performance has improved. +Found 12 outliers among 100 measurements (12.00%) + 7 (7.00%) high mild + 5 (5.00%) high severe + +decode/100 time: [36.024 ns 36.043 ns 36.063 ns] + thrpt: [2.5825 GiB/s 2.5839 GiB/s 2.5853 GiB/s] + change: ++ time: [-10.402% -10.303% -10.205%] (p = 0.00 < 0.05) ++ thrpt: [+11.365% +11.486% +11.610%] ++ Performance has improved. +Found 8 outliers among 100 measurements (8.00%) + 5 (5.00%) high mild + 3 (3.00%) high severe + +decode/500 time: [140.25 ns 140.33 ns 140.43 ns] + thrpt: [3.3160 GiB/s 3.3184 GiB/s 3.3203 GiB/s] + change: ++ time: [-12.264% -12.163% -12.056%] (p = 0.00 < 0.05) ++ thrpt: [+13.709% +13.847% +13.979%] ++ Performance has improved. +Found 12 outliers among 100 measurements (12.00%) + 3 (3.00%) high mild + 9 (9.00%) high severe + +decode/3072 time: [819.20 ns 821.59 ns 824.53 ns] + thrpt: [3.4699 GiB/s 3.4823 GiB/s 3.4924 GiB/s] + change: ++ time: [-12.566% -12.417% -12.246%] (p = 0.00 < 0.05) ++ thrpt: [+13.955% +14.177% +14.373%] ++ Performance has improved. +Found 11 outliers among 100 measurements (11.00%) + 4 (4.00%) high mild + 7 (7.00%) high severe + +decode/51200 time: [12.768 µs 12.780 µs 12.793 µs] + thrpt: [3.7274 GiB/s 3.7312 GiB/s 3.7348 GiB/s] + change: ++ time: [-15.360% -15.167% -14.906%] (p = 0.00 < 0.05) ++ thrpt: [+17.518% +17.879% +18.148%] ++ Performance has improved. +Found 8 outliers among 100 measurements (8.00%) + 3 (3.00%) high mild + 5 (5.00%) high severe + +decode/102400 time: [25.915 µs 25.960 µs 26.003 µs] + thrpt: [3.6675 GiB/s 3.6737 GiB/s 3.6801 GiB/s] + change: ++ time: [-14.805% -14.632% -14.474%] (p = 0.00 < 0.05) ++ thrpt: [+16.923% +17.140% +17.378%] ++ Performance has improved. +Found 3 outliers among 100 measurements (3.00%) + 2 (2.00%) low mild + 1 (1.00%) high mild + +decode/512000 time: [68.084 µs 68.577 µs 69.154 µs] + thrpt: [6.8953 GiB/s 6.9533 GiB/s 7.0037 GiB/s] + change: ++ time: [-6.0504% -4.7809% -3.5395%] (p = 0.00 < 0.05) ++ thrpt: [+3.6694% +5.0209% +6.4400%] ++ Performance has improved. +Found 7 outliers among 100 measurements (7.00%) + 3 (3.00%) high mild + 4 (4.00%) high severe + +decode/1048576 time: [103.26 µs 103.72 µs 104.24 µs] + thrpt: [9.3686 GiB/s 9.4154 GiB/s 9.4573 GiB/s] + change: ++ time: [-8.2282% -7.3591% -6.5279%] (p = 0.00 < 0.05) ++ thrpt: [+6.9838% +7.9437% +8.9660%] ++ Performance has improved. +Found 8 outliers among 100 measurements (8.00%) + 1 (1.00%) low severe + 1 (1.00%) low mild + 3 (3.00%) high mild + 3 (3.00%) high severe + +decode/5242880 time: [400.00 µs 402.02 µs 404.23 µs] + thrpt: [12.079 GiB/s 12.146 GiB/s 12.207 GiB/s] + change: ++ time: [-10.080% -9.1619% -8.2020%] (p = 0.00 < 0.05) ++ thrpt: [+8.9348% +10.086% +11.210%] ++ Performance has improved. +Found 5 outliers among 100 measurements (5.00%) + 3 (3.00%) high mild + 2 (2.00%) high severe + +decode/10485760 time: [720.71 µs 724.50 µs 728.69 µs] + thrpt: [13.402 GiB/s 13.479 GiB/s 13.550 GiB/s] + change: ++ time: [-13.295% -12.204% -11.000%] (p = 0.00 < 0.05) ++ thrpt: [+12.359% +13.901% +15.334%] ++ Performance has improved. +Found 8 outliers among 100 measurements (8.00%) + 2 (2.00%) high mild + 6 (6.00%) high severe + +decode/20971520 time: [1.3687 ms 1.3771 ms 1.3864 ms] + thrpt: [14.087 GiB/s 14.183 GiB/s 14.270 GiB/s] + change: ++ time: [-12.109% -11.395% -10.617%] (p = 0.00 < 0.05) ++ thrpt: [+11.879% +12.861% +13.777%] ++ Performance has improved. +Found 11 outliers among 100 measurements (11.00%) + 4 (4.00%) high mild + 7 (7.00%) high severe + + +``` + diff --git a/vendor/rbase64/benches/benchmarks.rs b/vendor/rbase64/benches/benchmarks.rs new file mode 100644 index 00000000000..df22350c3d4 --- /dev/null +++ b/vendor/rbase64/benches/benchmarks.rs @@ -0,0 +1,74 @@ +#[macro_use] +extern crate criterion; + +use criterion::{black_box, Bencher, BenchmarkId, Criterion, Throughput}; +use rand::rngs::SmallRng; +use rand::{Rng, SeedableRng}; +use std::time::Duration; + +use rbase64; + +const KB: usize = 1024; +const MB: usize = 1024 * 1024; + +const BYTE_SIZES: [usize; 12] = [ + 3, + 50, + 100, + 500, + 3 * KB, + 50 * KB, + 100 * KB, + 500 * KB, + 1 * MB, + 5 * MB, + 10 * MB, + 20 * MB, +]; + +fn benchmark_of(c: &mut Criterion, name: &str, f: F) +where + F: Fn(&mut Bencher, &usize), +{ + let mut group = c.benchmark_group(name); + + for size in &BYTE_SIZES[..] { + group + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(3)) + .throughput(Throughput::Bytes(*size as u64)) + .bench_with_input(BenchmarkId::from_parameter(size), size, &f); + } + group.finish(); +} + +fn decode_bench(b: &mut Bencher, &size: &usize) { + let bytes = random_bytes(size * 3 / 4); + let encoded = rbase64::encode(&bytes); + + b.iter(|| black_box(rbase64::decode(&encoded))); +} + +fn encode_bench(b: &mut Bencher, &size: &usize) { + let bytes = random_bytes(size); + + b.iter(|| black_box(rbase64::encode(&bytes))); +} + +fn random_bytes(size: usize) -> Vec { + let mut bytes = Vec::with_capacity(size); + let mut r = SmallRng::from_entropy(); + + while bytes.len() < size { + bytes.push(r.gen::()); + } + bytes +} + +fn bench(c: &mut Criterion) { + benchmark_of(c, "encode", encode_bench); + benchmark_of(c, "decode", decode_bench); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/vendor/rbase64/scripts/diff.js b/vendor/rbase64/scripts/diff.js new file mode 100755 index 00000000000..81b3b437279 --- /dev/null +++ b/vendor/rbase64/scripts/diff.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +const SUCCESS_REGEX = /(?<=change:\n)(.+\n)(.+\n)(\s+)(?=Performance has improved)/g +const FAILURE_REGEX = /(?<=change:\n)(.+\n)(.+\n)(\s+)(?=Performance has regressed)/g + +process.stdin.on('readable', () => { + const data = process.stdin.read(); + + if (data) { + const output = data.toString() + .replaceAll(SUCCESS_REGEX, "+$1+$2+$3") + .replaceAll(FAILURE_REGEX, "-$1-$2-$3"); + + console.log(output); + } +}); diff --git a/vendor/rbase64/scripts/test-all-features.sh b/vendor/rbase64/scripts/test-all-features.sh new file mode 100755 index 00000000000..5913cfe265c --- /dev/null +++ b/vendor/rbase64/scripts/test-all-features.sh @@ -0,0 +1,2 @@ + cargo test --no-default-features --features cli + cargo test --no-default-features --features cli,parallel \ No newline at end of file diff --git a/vendor/rbase64/scripts/update-baseline.sh b/vendor/rbase64/scripts/update-baseline.sh new file mode 100755 index 00000000000..0915c4def6d --- /dev/null +++ b/vendor/rbase64/scripts/update-baseline.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Run from root directory +cargo bench --bench benchmarks -- --save-baseline head + +(echo '# Profiling Report' && echo '```diff' && cargo bench --bench benchmarks -- --load-baseline head --baseline main && echo '```') | ./scripts/diff.js > ./benches/baseline.md \ No newline at end of file diff --git a/vendor/rbase64/src/common.rs b/vendor/rbase64/src/common.rs new file mode 100644 index 00000000000..16db1c7e137 --- /dev/null +++ b/vendor/rbase64/src/common.rs @@ -0,0 +1,42 @@ +pub const ENCODE_MAP: &[u8; 64] = + b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +pub const DECODE_MAP: &[u8; 256] = &construct_decode_map(); + +pub const ENC_CHUNK_SIZE: usize = 5; +pub const DEC_CHUNK_SIZE: usize = 2; + +pub const SIX_BIT_MASK: u128 = 0x3f; +pub const BYTE_MASK: u64 = 0xff; +pub const INVALID_BYTE: u8 = 0x40; + +#[cfg(feature = "parallel")] +pub const PARALLEL_THRESHOLD_BYTES: usize = 2 << 16; // 128 KiB +#[cfg(feature = "parallel")] +pub const PARALLEL_BATCH_SIZE: usize = 256; + +const fn construct_decode_map() -> [u8; 256] { + let mut map = [INVALID_BYTE; 256]; + let mut index = 0; + + while index < 64 { + map[ENCODE_MAP[index] as usize] = index as u8; + index += 1; + } + map +} + +#[cfg(test)] +mod tests { + use crate::common::construct_decode_map; + use crate::ENCODE_MAP; + + #[test] + fn should_construct_matching_encode_decode_tables() { + for byte in 0..64 { + assert_eq!( + construct_decode_map()[ENCODE_MAP[byte] as usize], + byte as u8 + ); + } + } +} diff --git a/vendor/rbase64/src/decode.rs b/vendor/rbase64/src/decode.rs new file mode 100644 index 00000000000..6963e24f261 --- /dev/null +++ b/vendor/rbase64/src/decode.rs @@ -0,0 +1,100 @@ +use crate::common::*; +use core::fmt; +use std::error; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum DecodeError { + InvalidByte(u8), +} + +impl error::Error for DecodeError {} + +impl fmt::Display for DecodeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::InvalidByte(byte) => write!(f, "Invalid non-base64 byte '{}'", byte as char), + } + } +} + +#[inline(always)] +#[cfg(not(feature = "parallel"))] +pub(crate) fn decode_u64_chunks(input: &[u8], buffer: &mut [u8]) -> Result<(), DecodeError> { + decode_u64_chunks_sync(input, buffer) +} + +#[inline(always)] +#[cfg(feature = "parallel")] +pub(crate) fn decode_u64_chunks(input: &[u8], buffer: &mut [u8]) -> Result<(), DecodeError> { + if input.len() < PARALLEL_THRESHOLD_BYTES { + decode_u64_chunks_sync(input, buffer) + } else { + decode_u64_chunks_parallel(input, buffer) + } +} + +#[inline(always)] +fn decode_u64_chunks_sync(input: &[u8], buffer: &mut [u8]) -> Result<(), DecodeError> { + let in_chunks = input.chunks_exact(DEC_CHUNK_SIZE * 4); + let out_chunks = buffer.chunks_exact_mut(DEC_CHUNK_SIZE * 3); + + in_chunks + .zip(out_chunks) + .try_for_each(|(in_chunk, out_chunk)| decode_u64(in_chunk, out_chunk)) +} + +#[inline(always)] +#[cfg(feature = "parallel")] +fn decode_u64_chunks_parallel(input: &[u8], buffer: &mut [u8]) -> Result<(), DecodeError> { + use rayon::prelude::*; + + let batch_size = PARALLEL_BATCH_SIZE * DEC_CHUNK_SIZE; + let in_batch = input.par_chunks(batch_size * 4); + let out_batch = buffer.par_chunks_mut(batch_size * 3); + + in_batch + .zip(out_batch) + .try_for_each(|(in_chunk, out_chunk)| decode_u64_chunks_sync(in_chunk, out_chunk)) +} + +#[inline(always)] +pub(crate) fn decode_u64_remainder(input: &[u8], buffer: &mut [u8]) -> Result { + let mut in_u64 = 0u64; + let mut in_bits = 0; + + for in_byte in input.iter() { + if *in_byte == b'=' { + break; + } + in_u64 = (in_u64 << 6) | decode_byte(*in_byte)? as u64; + in_bits += 6; + } + + let byte_count = in_bits / 8; + + for (i, out_byte) in buffer[..byte_count].iter_mut().enumerate() { + *out_byte = ((in_u64 >> (in_bits - 8 * (i + 1))) & BYTE_MASK) as u8; + } + Ok(byte_count) +} + +#[inline(always)] +fn decode_u64(input: &[u8], buffer: &mut [u8]) -> Result<(), DecodeError> { + let mut out_u64 = 0u64; + + for (i, in_byte) in input.iter().enumerate() { + out_u64 |= (decode_byte(*in_byte)? as u64) << (64 - ((i + 1) * 6)); + } + + let out_bytes: [u8; 8] = u64::to_be_bytes(out_u64); + buffer.copy_from_slice(&out_bytes[..6]); + Ok(()) +} + +#[inline(always)] +fn decode_byte(byte: u8) -> Result { + match DECODE_MAP[byte as usize] { + INVALID_BYTE => Err(DecodeError::InvalidByte(byte)), + decoded => Ok(decoded), + } +} diff --git a/vendor/rbase64/src/encode.rs b/vendor/rbase64/src/encode.rs new file mode 100644 index 00000000000..3727a19f53a --- /dev/null +++ b/vendor/rbase64/src/encode.rs @@ -0,0 +1,93 @@ +use crate::common::*; +use std::cmp::min; + +#[inline(always)] +#[cfg(not(feature = "parallel"))] +pub(crate) fn encode_u128_chunks(input: &[u8], buffer: &mut [u8]) { + encode_u128_chunks_sync(input, buffer); +} + +#[inline(always)] +#[cfg(feature = "parallel")] +pub(crate) fn encode_u128_chunks(input: &[u8], buffer: &mut [u8]) { + if input.len() < PARALLEL_THRESHOLD_BYTES { + encode_u128_chunks_sync(input, buffer); + } else { + encode_u128_chunks_parallel(input, buffer); + }; +} + +#[inline(always)] +fn encode_u128_chunks_sync(input: &[u8], buffer: &mut [u8]) { + let in_chunks = input.chunks_exact(ENC_CHUNK_SIZE * 3); + let out_chunks = buffer.chunks_exact_mut(ENC_CHUNK_SIZE * 4); + + in_chunks.zip(out_chunks).for_each(|(in_chunk, out_chunk)| { + encode_u128(in_chunk, out_chunk); + }); +} + +#[inline(always)] +#[cfg(feature = "parallel")] +fn encode_u128_chunks_parallel(input: &[u8], buffer: &mut [u8]) { + use rayon::prelude::*; + + let batch_size = PARALLEL_BATCH_SIZE * ENC_CHUNK_SIZE; + let in_batches = input.par_chunks(batch_size * 3); + let out_batches = buffer.par_chunks_mut(batch_size * 4); + + in_batches + .zip(out_batches) + .for_each(|(in_batch, out_batch)| { + encode_u128_chunks_sync(in_batch, out_batch); + }); +} + +#[inline(always)] +pub(crate) fn encode_u128_remainder(input: &[u8], buffer: &mut [u8]) -> usize { + let in_u128 = read_u128_partial(input); + let mut in_bits = 8 * input.len(); + let mut out_index = 0; + + while in_bits >= 6 { + in_bits -= 6; + buffer[out_index] = encode_byte(((in_u128 >> in_bits) & SIX_BIT_MASK) as u8); + out_index += 1; + } + + if in_bits > 0 { + buffer[out_index] = encode_byte(((in_u128 << (6 - in_bits)) & SIX_BIT_MASK) as u8); + out_index += 1; + } + + while out_index % 4 > 0 { + buffer[out_index] = b'='; + out_index += 1; + } + out_index +} + +#[inline(always)] +fn encode_u128(input: &[u8], buffer: &mut [u8]) { + let in_u128 = read_u128_partial(input); + let offset = (ENC_CHUNK_SIZE * 3 - 1) * 8; + + buffer.iter_mut().enumerate().for_each(|(i, out_b)| { + *out_b = encode_byte(((in_u128 >> (2 + offset - (i * 6))) & SIX_BIT_MASK) as u8); + }); +} + +#[inline(always)] +fn read_u128_partial(bytes: &[u8]) -> u128 { + let size = min(bytes.len(), 16); + let mut buffer = [0u8; 16]; + + buffer[16 - size..].copy_from_slice(&bytes[..size]); + + u128::from_be_bytes(buffer) +} + +#[inline(always)] +fn encode_byte(byte: u8) -> u8 { + ENCODE_MAP[byte as usize] +} diff --git a/vendor/rbase64/src/lib.rs b/vendor/rbase64/src/lib.rs new file mode 100644 index 00000000000..125e582f6bf --- /dev/null +++ b/vendor/rbase64/src/lib.rs @@ -0,0 +1,114 @@ +use crate::common::*; +use crate::decode::DecodeError; + +mod common; +mod decode; +mod encode; + +// NOTE: The original rbase64 crate set MiMalloc as the global allocator here, +// which conflicts with binaries that define their own global allocator (e.g. +// jemalloc in reth). This patched version removes that declaration so that +// the crate can be used as a library without hijacking the allocator. + +pub fn encode(input: &[u8]) -> String { + let mut buffer = vec![0; ((input.len() / 3) + 1) * 4]; + let total_chunks = input.len() / (ENC_CHUNK_SIZE * 3); + + encode::encode_u128_chunks(input, &mut buffer); + + let bytes_rem = encode::encode_u128_remainder( + &input[ENC_CHUNK_SIZE * total_chunks * 3..], + &mut buffer[ENC_CHUNK_SIZE * total_chunks * 4..], + ); + + buffer.truncate(ENC_CHUNK_SIZE * total_chunks * 4 + bytes_rem); + + // Buffer built from UTF8 chars only. Safe to use and improves performance. + unsafe { String::from_utf8_unchecked(buffer) } +} + +pub fn decode(encoded: &str) -> Result, DecodeError> { + let input = encoded.as_bytes(); + let mut buffer = vec![0; ((input.len() + 3) / 4) * 3]; + + let total_chunks = input.len().saturating_sub(2) / (DEC_CHUNK_SIZE * 4); + let in_limit = total_chunks * DEC_CHUNK_SIZE * 4; + let out_limit = total_chunks * DEC_CHUNK_SIZE * 3; + + decode::decode_u64_chunks(&input[..in_limit], &mut buffer)?; + + let bytes_rem = decode::decode_u64_remainder(&input[in_limit..], &mut buffer[out_limit..])?; + + buffer.truncate(out_limit + bytes_rem); + Ok(buffer) +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::rngs::SmallRng; + use rand::{Rng, SeedableRng}; + + #[test] + fn should_encode_following_base64_spec() { + assert_eq!(encode(b"Hello!"), "SGVsbG8h"); + assert_eq!(encode(b"0123456789"), "MDEyMzQ1Njc4OQ=="); + assert_eq!( + encode(b"https://foo.bar/q?a=2&b=3#fr"), + "aHR0cHM6Ly9mb28uYmFyL3E/YT0yJmI9MyNmcg==" + ); + assert_eq!(encode(b" "), "ICA="); + assert_eq!(encode(b""), ""); + assert_eq!(encode(&0u32.to_ne_bytes()), "AAAAAA=="); + } + + #[test] + fn should_decode_following_base64_spec() { + assert_eq!(decode("SGVsbG8h").unwrap(), b"Hello!"); + assert_eq!(decode("MDEyMzQ1Njc4OQ==").unwrap(), b"0123456789"); + assert_eq!( + decode("aHR0cHM6Ly9mb28uYmFyL3E/YT0yJmI9MyNmcg==").unwrap(), + b"https://foo.bar/q?a=2&b=3#fr" + ); + assert_eq!(decode("ICA=").unwrap(), b" "); + assert_eq!(decode("").unwrap(), b""); + assert_eq!(decode("AAAAAA==").unwrap(), 0u32.to_ne_bytes()) + } + + #[test] + fn should_preserve_original_input() { + for size in 0..512 { + let bytes = random_bytes(size); + assert_eq!(decode(&encode(&bytes)).unwrap(), bytes); + } + let large = random_bytes(3 * 1024 * 1024); + assert_eq!(decode(&encode(&large)).unwrap(), large); + } + + #[test] + fn should_error_when_decode_non_base64_input() { + assert_eq!( + decode("AAA^AAA==").unwrap_err(), + DecodeError::InvalidByte(b'^') + ); + assert_eq!(decode("!").unwrap_err(), DecodeError::InvalidByte(b'!')); + assert_eq!( + decode("\nNjc4OQ==").unwrap_err(), + DecodeError::InvalidByte(b'\n') + ); + + assert_eq!( + "Invalid non-base64 byte '#'", + format!("{}", DecodeError::InvalidByte(b'#')) + ); + } + + fn random_bytes(size: usize) -> Vec { + let mut bytes = Vec::with_capacity(size); + let mut r = SmallRng::from_entropy(); + while bytes.len() < size { + bytes.push(r.gen::()); + } + bytes + } +} diff --git a/vendor/rbase64/src/main.rs b/vendor/rbase64/src/main.rs new file mode 100644 index 00000000000..f28270b2fdd --- /dev/null +++ b/vendor/rbase64/src/main.rs @@ -0,0 +1,92 @@ +use clap::Parser; +use std::error::Error; +use std::fs::{File, OpenOptions}; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Write}; +use std::process::exit; + +const BUFFER_SIZE: usize = 10 * 48 * 1024; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[arg( + short, + long, + default_value_t = false, + help = "Decode input (default: false)" + )] + decode: bool, + + #[arg(short, long, help = "Input file (stdin if missing)")] + input: Option, + + #[arg(short, long, help = "Output file (stdout if missing)")] + output: Option, + + #[arg(hide = true)] + argument: Option, +} + +fn main() { + let args = Args::parse(); + let result = process_args(args); + + if let Err(error) = result { + eprintln!("Error: {}", error); + exit(1); + } +} + +fn process_args(args: Args) -> Result<(), Box> { + let mut reader = get_reader(&args.input.or(args.argument))?; + let mut writer = get_writer(&args.output)?; + + loop { + let bytes_read = { + let buffer = reader.fill_buf()?; + + if args.decode { + let in_utf8 = std::str::from_utf8(buffer)?; + writer.write(&rbase64::decode(in_utf8)?) + } else { + writer.write(rbase64::encode(buffer).as_bytes()) + }?; + buffer.len() + }; + + if bytes_read == 0 { + break; + } + + reader.consume(bytes_read); + } + Ok(()) +} + +fn get_writer(output: &Option) -> Result, std::io::Error> { + let writer: Box = match output { + Some(path) => { + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path)?; + + Box::new(BufWriter::with_capacity(BUFFER_SIZE, file)) + } + None => Box::new(BufWriter::with_capacity(BUFFER_SIZE, stdout().lock())), + }; + Ok(writer) +} + +fn get_reader(file: &Option) -> Result, std::io::Error> { + let reader: Box = match file { + Some(path) => { + let file = File::open(path)?; + + Box::new(BufReader::with_capacity(BUFFER_SIZE, file)) + } + None => Box::new(BufReader::with_capacity(BUFFER_SIZE, stdin().lock())), + }; + Ok(reader) +} diff --git a/vendor/rbase64/tests/cli-tests.rs b/vendor/rbase64/tests/cli-tests.rs new file mode 100644 index 00000000000..1fd050ab8b0 --- /dev/null +++ b/vendor/rbase64/tests/cli-tests.rs @@ -0,0 +1,133 @@ +use assert_cmd::Command; +use chrono::Utc; +use predicates::prelude::*; +use std::fs; + +#[test] +fn given_stdin_no_args_expect_encoded_string_in_stdout() { + Command::cargo_bin("rbase64") + .unwrap() + .write_stdin("%^&*()_<>,.?;[]{} 1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ") + .assert() + .success() + .stdout("JV4mKigpXzw+LC4/O1tde30gMTIzNDU2Nzg5MCBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiBBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg=="); +} + +#[test] +fn given_stdin_and_decode_arg_expect_decoded_string_in_stdout() { + Command::cargo_bin("rbase64") + .unwrap() + .arg("--decode") + .write_stdin("JV4mKigpXzw+LC4/O1tde30gMTIzNDU2Nzg5MCBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiBBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg==") + .assert() + .success() + .stdout("%^&*()_<>,.?;[]{} 1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ"); +} + +#[test] +fn given_invalid_stdin_and_decode_arg_expect_error() { + Command::cargo_bin("rbase64") + .unwrap() + .arg("--decode") + .write_stdin("!Not a base64 string") + .assert() + .failure() + .stderr(predicate::str::contains( + "Error: Invalid non-base64 byte '!'", + )); +} + +#[test] +fn given_text_file_input_arg_expect_encoded_string_in_stdout() { + let expected = fs::read_to_string("./tests/resources/utf8.b64").unwrap(); + + Command::cargo_bin("rbase64") + .unwrap() + .arg("--input") + .arg("./tests/resources/utf8.txt") + .assert() + .success() + .stdout(expected); +} + +#[test] +fn given_positional_arg_expect_encoded_string_in_stdout() { + let expected = fs::read_to_string("./tests/resources/utf8.b64").unwrap(); + + Command::cargo_bin("rbase64") + .unwrap() + .arg("./tests/resources/utf8.txt") + .assert() + .success() + .stdout(expected); +} + +#[test] +fn given_input_arg_and_decode_arg_expect_decoded_string_in_stdout() { + let expected = fs::read_to_string("./tests/resources/utf8.txt").unwrap(); + + Command::cargo_bin("rbase64") + .unwrap() + .arg("-i") + .arg("./tests/resources/utf8.b64") + .arg("-d") + .assert() + .success() + .stdout(expected); +} + +#[test] +fn given_stdin_and_output_arg_expect_encoded_string_in_file() { + let filepath = format!("./tests/t1-{}", Utc::now().to_rfc3339()); + + Command::cargo_bin("rbase64") + .unwrap() + .arg("--output") + .arg(&filepath) + .write_stdin("client:secret") + .assert() + .success() + .stdout(""); + + let result = fs::read_to_string(&filepath).unwrap(); + assert_eq!(result, "Y2xpZW50OnNlY3JldA=="); + + fs::remove_file(&filepath).unwrap(); +} + +#[test] +fn given_binary_file_input_arg_expect_encoded_string_in_stdout() { + let expected = fs::read_to_string("./tests/resources/bytes.b64").unwrap(); + + Command::cargo_bin("rbase64") + .unwrap() + .arg("--input") + .arg("./tests/resources/bytes.bin") + .assert() + .success() + .stdout(expected); +} + +#[test] +fn given_non_utf8_encoded_binary_and_output_arg_expect_decoded_binary_file() { + let filepath = format!("./tests/t2-{}.bin", Utc::now().to_rfc3339()); + + Command::cargo_bin("rbase64") + .unwrap() + .arg("--input") + .arg("./tests/resources/bytes.b64") + .arg("--output") + .arg(&filepath) + .arg("--decode") + .assert() + .success() + .stdout(""); + + let result = fs::read(&filepath).unwrap(); + let expected = fs::read("./tests/resources/bytes.bin").unwrap(); + + assert_eq!(result.len(), expected.len()); + assert_eq!(result, expected); + + fs::remove_file(&filepath).unwrap(); +} diff --git a/vendor/rbase64/tests/resources/bytes.b64 b/vendor/rbase64/tests/resources/bytes.b64 new file mode 100644 index 00000000000..60eaedf7f5a --- /dev/null +++ b/vendor/rbase64/tests/resources/bytes.b64 @@ -0,0 +1 @@ +KK0uYGZoJzEI0b2Dqd+sUeN8pdEZewargvmmOOMZ+cj2lpegtf4XWGKDm7R018HBoW5NUQIRP0Y+kTdWqoYMUvKFGI4wIHyuelk2NNNSu/gjuJZwxJAd6MQooiaNKdzdnmTnO2uymI1wXFufCML9LytvjA6emnInTpP+Sr6IilWAsb9eFUh+q9DNEnj8e0i2DCC0siNUTzwiXbzmXyyFC5AOVziHfK7U4DV8ZjsgIYgwWaZBbhOjStWSKL/TyQyi0AxjxmoVSWqV174X14xUgznJjFI222uW8RLeT72sWRTQOwXBJheoDwg5nehze6kZsF4zoWDwhKZMm2iv30IxlaUrO7swS/E91kMT/VlnQDT36WM3TNOB69xJFkuXyIhxGiN5OZlqS6TdSulTLe4czMZTm0jdk8UFm+hB3no7maXzanTFJkx+zkMg6nxwg+81LjRqPf1KPZkGWAlevhkChXHCGIuAFvqK0yFREAjcJiJwgQgRCZElUwhTRMl5S0bqR7DyW/Ha1rwnYEcPZcUGfWkNoPWgdJBLuZfF0z+NDhUBZKoPYAhjiLEtpCHABXcISL4WTLExqaJMl8P59FCOjyX3enio/5SMOujvGhBzwpVraJRKUBBxJcxfdOR4A6gvn+6YceRO5B+G6ZzoLJWGhO199uItVe9mve4iKASatXXb1XcEHpD8u03BGe4ab0q+78LY5KGx6DF1/OiLtVRXwStZ14+8Qj97efMdtb49YAupIzBCZWY/YLiS2pSWVamrAvT3ewAe4B3Yd/EKlYSlKPu3EPS535wGF7tFX27Q4yOyNX3NMhnbztz7ftez8K0tbM4y91X8N9NNtGFQDrngXTPJ95dT6sP4pcxlKVTle78yq+g63yll7u61dcq0HoHvhJuxXNAExyCaI4vy47QUmMcy9CeI1apS+jZB2ATQ94Z+f8olB3qakZTZFi04wtc7mK+WXmI4VEsPeydcZXI2PZFmfgZsm3gPVPGDEp4AiMh0gxzYawKF97rgwGdnYc24S/u+/ahZDf0cZku1yYv8mVOsXNfVC2g/mpRJutVy+rrbD3JYN4EgKMo36IA/p5L2azisjlytmprufCpa1Fim+uubjxOi7gW0PaBbj6jG871C5XB2Ft2Zz8J6zoZSD2RyKEMKZesYOgub9ML+yJCam6RuLhnbIx0lNHdu+sk5DLmldc1yVHeSAmUxniZl0VqoFEpapnuJkndAn0l8EiCkK8kiz9ugls/jY8SUbPYjkldZ8K9RIh/+KQGoAh8wlACuhDSSZUqQ+F3EfBxwD7pQUrMPsxs29PMOe0AeZvyZeS63nqxpuAfFZMKyP026oW/xGrfzByJBx+EYre/OILdIS/bzotzIcyiOWvyxpwdNNXooqZRROKMqx+UOfqOSrXfeOIsHxV7hNOanVBDL+rC6SwzGQcOvlcNpA+ccFaJ6rpCZt8yPDJ7KEnSr25k7doMn94KQz5pVTkf1LT6pJVWguNHcXWWeGzzAQN0E4VjY7GvYtHES5R9jnnXAFvf2eOx3FJ3U8Ke0YYsv9TTAjg9m13kQa/V7EvlKbTIek5kluRNtBQiBZspmZfLs1bHkveAKOwKwg9mUvwKIhORrvF6C+VkATWsLkfeleu7oOFsrlo3oUqdhGnmm1q1G1nE0tth0QqGcn/qq9QjAEc2qXlHYRiUVd0r+RyzoctNtH2nDD57XPTgNIDrVa2Ozv3m7jA0/THYc8g6gwiHLZnjE1hURAOy0Yzk/oU6JIl4KznVJOT3A+LCjV7L/ldsJSETyTbyJdo2Be1MLw6UxoX5aCCGniUpE/97Ik0BJNwUqnBWPYvqrXu7OEQxx2VnqXtEcOd6xuOIxNc+MGlb9GIP3aybOrAg7sH4LBl7nX63lQtjbeHbSHK4YHFOJImraH8UYDNdyuoUF3ASHKqhvA4S1IMvE3yZibHsEWYRFXqD37wf90Nnnv+ijBoKquT98lt+2GyGHRWSnIWVv+Z0tDhjqXIMn0tbJ8tqs3Lg1Jx8SNt2yp44gJFpj/vlZ7GKgwJ7ISePTwHbOdoAweYcDF32QNSnG1GMsqPl8n3QZtEkOHVKPXRzLXMYhlLDjS/m8Atqrf4hycX7ZqU4UxMe3yYC64p9jGkXINLhIKPpvi4bSgv4DA6GkRrNakY8DeVqFCn2MuH7qFhaB5j+u6AvgbgjRn0Q1nrqbmATj4CO4w4XO5DvvoSGeaOQlt0zUm6zcgR/5h6Q1Tyq74maypsauVfnGGw1SlwIc0Kc6d144JGpoVIMnU8eAcLDo4wfxWn534puK9Te2OhkXNpxMVJd7cahnxWRGgGapYp+j7pPdGqFj5wgIxmJdD4Gz9SJ59C3RwBiOSqAAX323lrzBi3c5/4CseXNcUDT8no0HXv1hIH8zwQrphJAhGfy7/QBNnljmUNj8MkDdYv3YU3zkmnavbYNBa1Zyta3vOq5K4wZEwwZoa6ZbJ9ihhcqdgG5CKy/gvKibjFbC2Pm5uRkfCwee+69uzc2uc3XRn5ehPxsFdRQuHlMYZgcahWMUdrigxn8yYJAJU9G+piIEgBBQqEzK4At6eZZ5g1y98uAAEebxNaH4EmclKMWXv9YIVlfsGWZx/WN+QFmZqLtRRNTJql+IuQS2BTotJJxKnlkSyAewHiGi1W37Plgz19cyuju3efINis5SW1EaREZ7fg0uLQw1rrSkZntE1Ca2twxQQaP+u+HFAKXyYWxy3818EkNu5Cw5TdhbfL/mVrThNBm7mhew5VuC3stN3Dj8S56Z/dnDDPYdVfZSosw6WDa21gVOYGj2ejp4bfICp+faxnA7lt8xUU9QAxuE0LeDrwphBNCqk/zB3D0vKyp1aTPTSZ69BsUnoGlKbismsH0cQ0SPyC2a9JmBZsaQjHaRA8aB7tzYpTkkw8CKjtSWYQGpd3eDDnVBBrcy/aNVHtuZj091Qbkg11XON+gBOW+kwtysj/1U0k4L1WuyGAIkdWZj1HY16wTHzvVAZNJzfveIfddH37KmG31zqFZVyxDWzFegdHxiRdeDHaBqX0J352Et+Vo+Z4DieXsX1Yb3/ulpvbNN4ZZ1x36uEuMs3oKYL/6Ldxr7Ylo/vFSXr3ZZJO9Kcct1IS/b9chiFDQY+3M6zdylDZF/N3DiXtn9hwORBY3HIS2vCVwpRlUvQWj4EdhchWji0tHibI0tiehTXLbmbaeVfuEZSYc+3XstHtpwEgrhKyWUvXD0GFkwLflJlUUDYDuargC/PyE+pi+MHPaDyhR8A8lI+cNquDGwv48YZmVVd5iERRNp4eEJ9vnSyotjP0gfhcThouCf/c7lE6DOQDd0EggaKs02LKoVmo0nCXXOnWzBDOVylNua8YQFgdm4feSYj3yLbkUdagGxaFPKxTnYz1K06DVYHHF+8GzP6VHk6mPJ5SWDkHwEpaZtNx9PFnoGRijTwrs5vDq75YwUtfPVcyN9ZKF5vYYanihrNQTDa2qUE5v0p8ha1+sxS8d5HjOBsaEJaEpp4RnmQusM/HotmpPMAunaqTAs5gRDnjqm76hIziFqLBXi3EeJZ+hweBAL1Cysv6+zvYxEfMHy4TYa6KVUkgyMH/O6CZG9bV3IB1OrJaEIosoS/0M9scoD7MjHMihxBGEpVfDPEqxmp2U0lmskCCjJwX68GbIrrGpEPTkykocKGLnlgTbadgbcP5GSUxsGREzTSo9Nn1MwG/BtfHYpTXKTYKLD1kW1endJAeAZj6sJoOQisapoCJW18alvT5xAJ4zI/bDJzjC3Yvw0vzwpx9/FatCqUOFgpUvKKqdjSLfjMd/JjdaM9+rX0o2qpOF/NX9MzDwvtZLwPT0F50XPEiH2LoyfRJ9dIdU7Q3eNZgNGqFZSnspxn+rb9/2kTi7e00NP2wMFLfdAMMT+iR9Nfw4glT7NCJmYlFReiC7HcgYEfHy0e4IjiQIIJ7JqOhy+8Lb8FpCbTWQTzlEBvUnooJz5GRnB9wWyCPr31qxIc7kQTHfryHceLeAdgquY7IrKWj/G9YL5Y5SOvxKlTMbLa8errnUCNvXTNngZHDQsGrd40pJIQ6LiTMC/VWZjbPN95bJDDqyofQRkqEAFCQou7rUneBye9YyjjRJW3HuLGVaw6ZXfYoVa+9u5TWMwmwqgRmpR5No8ki/uVS9KdVcC6B3JHEZqf2uvGacCq4YTf7wdll6U+T+ZURk0UDXVMM+FtNQDqFASJVuVBc49iJfOPRKZPl5KrRsfzUmUEdb0ts/CmqZmcyr0ffnNF6W9v0KMiniNDnITZ3V/e2U3mvjaB0gjiT1yp3nylyF4JkDOttjZp/z3U46NBo+qlRyZoTaR65P173vMsewxl+r1cVBsNNSZE2ra/LHSH3R6TvZRNWTl4r/dSC3zGp2XBAkst20jzFsR4fLuhxIUR7IKTVyoVKQowdE5wqPUzQ5Uio51ufDhfOJLxNy/ZyzNlpiqmwU4s7FBko04NPxK5z47rBgJdjxCC2CS2D3Yj34VTta8/hIXFt18MrHci2PD2K0BIM0SZw+T52OTCPvxOdCAyDkxLduyjGuCfoWKH/+J58IFxN3PL36tDP0rrtpb/f5GR8gdV/zICbtB46I2q/gsDwMknePBA/eu5x3nho8Yc0/JeUIpaVV5U46r4zzyINoP8MkwwerNeJCwbRCfNQIDDQ+xMR18O+dLV+ng5HDdb67dDzskmHVg9pYrIIPJZ3rnfZS3JzaQR9JNuQE2lBueuQ/mLSdkhDCtt9JUTh+Iu33dxgnPGXhm2CSTpF16JWrzOcEbHZdUQVtGziGxvUrS6hBUW5/NkD7/Zpaum16aSZmMXVjZWvGS4G1J38216mF9teU7ZnYc8JMgPEq9E7XSFDN3eY/QtrqY1KmR3TGncppvY31kvPtVEWpbd0xCfsp9iR+tqS+xEQ7P/wlz9WYsQnQXbAyKJyCUSyj/XIrFgx/W/mbDB466ptdLo7A/kf78/rQR35TT7qICm0qJKNWhp1q11d5ihqZEwLRHWoN3zEnox+HpmJNPu4zTaUVTIDezhFEpE2dwDwUE1xmWoXabe5siBsYE3B17GZo0bjZr11/+9OB3nmhqDBgMBsss8oS3ylLu6wwEw6XqQxV4g7Ke9mvfdV+8A6Flv11ls7V8XSm3NlHcsAwxrgywNrzteXi5v52U+3oaVSjYOtVK+MwD0SBXRbxxe05ebsbu3G0bhtFFgFcdRMXmkWIhpMTL+McClpzFt6RIyrd27zl2ItyBSsH34LV0DRD6DUf2tjj85CZq6qRcSWS0MLpiRUtiEAhnUfAqxrTd8iDP2azts6zD4+yLtEsN29zPgeprAhXsDeL7s/ICG9A7J93OqIU+lFJA2ASbOvIexgvKGbm6hxHir/Vg97EZOl2vmAw9TjHMKeQNMy+bMOJON5so9/8NmPoQS0Ek2TGvRV2/SnibbuFsY2+6YB76crX/E0VgN2D3HIDJnI9iH6AF8UktlEavKnMVvrrHhmCIBtvnE9EU3Mksd5QcfZkUw8OPseJcv/tqbGP9/xlPRoNsb+2TfVC8pshL0+Gra70TtlrEslYTQNiAGkbElatdtMrtlTwhbxMRc+afz9q1Dg9RlpKfKIZS28+5O+ed/apMC+JM5I0nq3mHx0C7U9r3INPQ5Knz9TRPMvTasCiSMgQoffIhe1HqKu4NCZ18HfGIdG+Umc1r/3/pu56Agt6rD9ugV+b/WELvptHFOYA3hekXqkGLmtrD2HfTmM6bAQF33jjSnNKroJSNnI4PY5y2hy+1JO3M5Js5jNtGcRt0Sj7+WTEH1X3+HV5vw4xZXlYb1ohME78AMEEIcZG6u3UEOH60Irp57zuGGlYm6w9uNLbbXEaHTQ6ad5muCAVqoYHaMmU15T5GiOOh3IhVQx/TgXGLtymIFsMdrWaTPU5CEHq0TVFzaAIoOm+3Am4luu+eqDclptv9yxEK5zP0aLG6dDWhM3ttPeW7epLmu4MYPARiIoqcqNUjzuQEcYhBfR8mZPcyBIM0flYX4e/TMrma5mKyjNUdEyiiYzvBSoCFZR8BlXHP9qc9EbYfPQ7SF6DAQLeUSJoJHTn2SicOTzKykzD7QOrRtslxuVC0p/JwEtpd3p9BNvZjzptGxf/FKqQrZIBy73Vep/uAcpfJ4Dg65Cx7ogV+PQmGfOs4fM64v4qf2L514qkgYP3v1bibb3Kiwx8RkmNRe/zm6sBpy1kYluQsP5Cox5fdZ6MZ2dhi2bfiGiVTfrszU4WDnsAj22WCaKl9989lJzQFxaOK+eWwKSFfFhbaKwZVg4v2myx5tTpFt6s9+OeDBC/bJ+skyg/X1GsLz7lxtlZuQYhyp9AuFbuhJS4heUDBa2yjtj3tVkpa7neLLDXbz4DPf6jX94CPWOOjPbo2rc7Kfxs9ZV/RE3pHNBjg3b0DYQJAOpOsCd7rDim9mkeHMYaPWIzYEt/RSMVPMye83ZGlPbFAbjQ3pgo061vXGqpF87ztNmMU2orH7a57AqoFMgE5ZAoAyKDDi0gU9SCGw+W7Qzbn3w2DzW9Nm7zF5xhahAlVVYVpC7mpCO/lLiq+kbSl2qXrlbJpKiYH7oxMnvuQ3Fr0t+DVoH8RGEtA0b8K/u0wjPtopY3tO1eI5iwsuZvGCqJ0CCO1v8/Qk0jvdBOMjrjw16RxMEOjEk4PwcBZiT7r8E3OOvV+yGdSzM6FZzC94qXaM/oon86mOCWdHCDv4rOWT7s8fwUqmxBzwpd6zPXW7+eiWOJJciZ55G1AnGScyNYqbE1B/LpEXF5YYjxmDWJT6Jqy2uv3dRuq0wAWBm1b50+6UFJ9WfQ93qEMm+ER/n5CQlBgnxz5LlVUTFQeLNmMZh9cwNABasGdunszXGVlPn9DICueeOVuom7cCs8Id+NIbzm3m5Jej45cwlrGX8sgJkP4B0cmMyzVLylaFzLF+eiPrhX8hPs++WDxJKeGh3FC5h+fVAZ2UGQ9pVyejETQjJJFES+3Ue7IVIitA55O3+CB55Sj2k081TWnQeawD3PvBdwtataFLmXzyL9b96w1NC7eDMzYC/VYHRcx90MJBZ127vsCE+DCXlDgRs7nH9fFZCB7TcRzdWJnVEd/WPLV6sa5zBnzXPrnkPAQNfIiQV6yZamWYtWXQyyDr4RMnT/Xc2Mc1JjdUu43RKVw0U04QbnBqwJ3eIgbru1264YA5E/HDYz91feooMk50dcenbM9lR6B5clKRrlNG2Ph1cjNgctU6SLhfj2u92VbzDFdAt3dtNKJigebRsJNW1QAwCeWMWctRk+k3/YLOxJeA2sN2Col9GrlaN5bRBpZhI7XyC7LDjw7yCIngGjhf/RjzycWo25t6atVA9MiVrqAKjY+GD6DWztB9HiftAxbTU95sAiAwpLZqp3v1oJsOI/m/i+sW7du95p0Er9a7dY4wrRSw3ay378sLxuEfhEojGpI/jhVowXeUrlKbgyHK6+kMtg5XmZPA6isCoR+MxCX6nDnlaQ0MZgFdO9m6XdDX4NHNM8ctSNJg7mLfgE2nE2WkwHSlBK50jbCG0fonfqXzcfRm8r4YMtQKncpHNIcTD/b8uwxYFv/WmwSy8UllOcP0HBMKSZiL0QZtX4wZFodXL3DYjH2wRvfYcVgXpVyTuBEBxv9KnK6/Th9fymIMW3oEDodjjrfcnkzCWfXLks1WnKdJz4FBFYMb1j2gE1MN8W14ymLoVbKm3tmz+S33IcWVm3Pcax/CoIoH+6pLobSXp1yUgMn5XFhSnjwiE18oWyTqBpeczJmVl8iTo7IJen1KHj5YfKtpWnNYyz7/201f9Zh7532eyl8GBF/arDZGCJ+sPLPc9j6f0OPIG03hJUcMlPEX1NnZBau0EoxsVypVfCrGDonFTyEoaY7hGONNIjtC1GZW6JsAG/K2L2Qs8Bmoi9DL4G0v66DNz3XcwMKG357DOviQ1RIV+GxADxdSpSp9cWizdqpNjtA5mqr11Ql6cSwjnd02j4WrM84+kzUMdkHx3xOz5ZGfSb+onoXivUek7pQSPHUSX3aKE4FxMQaTO28FhXNDfLADORTIz1Oe2wVSE26V0FIvFQqT9saLGP9RDyO2GcsxdjZcttW3A5xWNBRab/PUNLaTr0FEXly4RI7scbn+96kXUiiaA2CFpiqzsrBjYZxhmCdfIsI98D7b90EK618vlVMgLAvnFwNymtSjdL8bA44ppRbaFVMxQdervyidDi7NmDJ1eQ+9T9YUYifglqGq2V6Mn7/rY46Qi6/7kYjB/XqyUu0AO6ARpaLJbxwWJSVg1yc20ngqDkfGkzfy42vjw/HRKBxLRFtGN2ztPtJvPZg7LbY/Gpx0bpdXn6EyFQBdBE9nZRgGlJ7zHi3iXEw74KLtHBCaATuZuK6C3mxEhewBjQJrR9IC3ChkVirQKcHhgZ5rK8UqJAQ6B6wsAjwYx5jf4uQAjzMYhWlMYO34WVruag4Vlk7oXl4boqnzq2C20tdQk1c9dIr7Zk2SQ83tRdXJpy6ACVJT6dGk657MwGKvQLS+dJG2Lu861k+7ehuxepZH0lQ1NlaV2JDxvrxiyB36oJ2S9c5t2T0JeIS68hpUUvvCrj+aYPxpv8wSsZJB4fn8nyhknCrWP89L1jFySpEgdUsDu5gg89Nm586MWHfamvGjXKCB9rneiNTks5Z+L7G0Wr5cA/UZe6m7bUAzqXsqCUMooDLNYqrqaX23HrQH/shTF47hAMUtrH5BYdrNic/ifbGLWXpokVGxhmXIeQhTORZSrgwn7YS//CQmFdP+qLIYZaPCtpzHeKkGBZRkt0amfBGTa8peI6PkLCqscyQeP5wZkHQaCm6nDMXDGNH4/YHLCzr4P2IKB4wztoOryL19Xi6tQIAxtM0PhSyggSzh8G4lrVZwDGe5yiW3wAaIZgqPMu0obr0G/psRaAmz12Ev9fzBmz/iiUA6FKUYHc7l+IquPKY5TWRoRn8MndxkBci1XDnXAfrJ5KJNUggtm9Bzoya8s8vn3/7+qIQGmGWoHX/CQYQ4/HFQwY6yBzvBNESNdmxTegy8fCjULF5bWnM7eZbr6LUxpNk7vHrujolZxBaFq8JspNL0Gx/Wp28ts3PQZPaVWzjqFI+nHVM3IuqSh6spyQc7a0urd6ETa0xyxYORZvSrHYWtPp7llpHx783Eqwt39EAGG3rZu0Nygb5C9t6tbXNZ1g9szVVs9oSJGrFUOfCqYFzpTssPEZRd9v9N3Jtmv3AfvMZMLrdjhESthJjJ7zt8kEeJ4neiZVGBJkAmKpV8opJUp54mfayG+mb1Dbaa2NegZCGuzVPh7tMJzRFHS2tKoNe2iHQUMHJ94uXrvkH9CShhTSsrdau2GL9XLdkmOVZnM94D0VSqAnm5bAxp28TChdcKu/Soa7yV7ti9dVXPW8wxPf4jHsgCcI5H6VkT6xU+Z9/JIs0rBOSJi+wYXbg/KVFDrMvkerXs2btjrCnR0BbYIXHOLH7Iu/S2G3kOwqx1OVQgJSwkru7Fkgi9zMEYqASrk5E/mAqu178l+daXmAxfRcHOClISU9+2JW3Wbn4AyLbiZbCL/Qgd5YIqidwDk5aLyKT4i/eSLaweFcu1XaP/R+0J/eYk5vDFGOZlnVotw7BwpzCbXjfS6OSwmnQcbruqQKyQzFXqkE/M4mDqvbA6/HYG5qjoQAldkFbXjPOgGgcAgICYiqpDQZblRJqIhZDGoC5AU61S3aCRJyPUYuEMiMaj8lGYukOCX2gfDjvCs1YRcl1wyQze6bJaH72zbsG4X602Oq1P3Xvo1JEV2mHKlXr+vuOl707wNFt9a2JlcRNSLff8Vb52bZRvyMBL0hOC+8rn72M3Pg8CvmgBUVoPx6+Jou59emeRKjFkmUnTv8iboL+h24EbnTGH11d4UCBYTMaTM+PxPjvIWYCVbQzVcpleOENBB1ZE9yDdynALU7YfYggtoHg6lsH5QrA5QWOTaRp90Zkm1KUM8aE0pbfXyGRp7+I03GVd4F5hkf4p5trfZKXyHtkZLR4G0DQZJc3Mu4nlvUygHGOF2cbA/zjNnBUhD9tUl4lMHrbXTPka6fn6PO8n+A4EBdTNXcFdACtWVFhFeC8LEZSSAy/jxrDi9xY3+uc2MuUJpylp5K9nO54rTg6ZSF9CuhNB3GwVsX9eo7N1BNNbgBMaAleleMDM30Me5/Sb/7p7q+s0Z+P5ouUG7Hwf6YMy8tOf8ZY1ZTYUlnXykHZKWub0zjutbPtk4uckAySJny6n9KAPB/ST73gQkFMNwk5cNdE6Qn85tLz7Qhw/bZUtqZTP/9uySzgxwxP/WtYT/aJJ58h1YV9XyY1skrppSWgw1QPz+v9g1IGiEjua/Uwq7uJWF7NfcAtDmHX5p25Zn2Iu4u3DSUyEHxz6p5EDBEVXZW3a5ZAgpVinWN3oAz2Wf+foYo6GVHOU8lt+4DnmPzlcXJVVMBwVZSVcFXrCcRiNnqMa5jcLENwHGtRjwKw4tq2GSQOcXDHEVgbsUdQBhuPyhiqWQCQbZS1TThwwpEoHfIRlm1is1Ec6CWhXcKO9+hjvb2pM3ihdD8ibyp3j35lIYucqsdY2nTndeBpRX3ox4/ozHBSOUqEkiyJ1VkELgVwMaA67DnKqb8MBTJZHa8vG6kCMlZFZIOjSDn26cMIVMSYHiUoL2MXcW7u0FoitnqgaQj3mCHXExGCQQQYyziUlMCysuvylA0b8w/GLEnFtRtfcnGKwc8/Eap0rfVaYkBBbV03Sdf1Jwc76D3VTx6bwR1M9e62cuEmGfh2jXfy1kHFMOTQ4hgplWaFzplwRsUBviy7+tRSjkFnY/NikTR6Oon19frjlKxytXQgwqMqXkVGlh6sc+S1O3yn+8wY4mCa41/SjxDQX1v7G9VcZoB2yMrenBgVYL4TqQNRR6e0snx+PuNJIAOPKvEMQgXYxuGCbGQA4fOyiKH7jYmuJyHoL1VU8eZCFCgDyJdzbAT0v0QBoMv2eBxYolxgxQeGXt5UKQmKkgntzjFU8oF5YwvGNf80RIOkkB844TWUd0uClp1hx8lSMhA9ZOxmqxTa9LfSCsH0RuDDWstMUWgd9zVdcM1a6qYCaKVsGMYVhM4CC6KoxLZF6O/HtwHsF0JwrfpfNn94rePdXhi9qxsYgsmc151iOt3KO9Vx6jStYIfrkaIa+QlbNSl6bjbOuKfXf+W9e9kjVza40KVweIsNch2Ooj+nR/69rBwyHbTOgcZpQNWXrsj/IWB1Rcy7H5PfDfXPNaV0/bIL6KzxBJ1aQAAiCp+WXlm6+AYRNEMiYqqvbWzKtmauHpWLfHKRcMIrffOorF7Clnj7zksSjvKWYklWueN9H2N2vRZ6lFvIYZvOujcuY4GLhdphQjVQbhK/9V1XtXnptOYcCalBNCQ/frCSNtTN78jlbRTrhLrGGMBoHlvoVHDWc73LVhtjnMuPK4E5Wk8t0bzY3AF+bU4+3HW1G/+78YpIf8bIG+OIlZTV5W36Goj2takzmVrjXmGrO2yZ4yL9UXZKR0c0SHYD0tHuXF0iKqFXu1lum35MSX9e3OHm2MZuA+M16y/nXAB00WdfHp8Ks5JYU6hnIFZtyHECMLB4zudzstlwdzcJHPMI/LX77FiuHqubMJKP7N6PrX/hqJRyypZvFMOe6F27o3ePkE1rJnmZERGlb0HDbN3UEdHey9e9+bXaPdM0SVtMiTHRxizOV24TSIZNp6rOHS7/pq6M/LND8TaU2CYW2023IldZ3H6pL8cGdyNmaibQQhRHcSi1XG1c6VjZWHwFXq8ildLojLO4fkw1o+g9UhNoZLKumOB7B+VTgKcjZvnYHVNsSZyJ21G83NqTF+XeHBsheosvlSLszqiBehFbcFReBMa9Im38y9nu68WgPd1rHBQZFFTG4zL0RlcYce7fk06n5f7oCGw6MbFfwdeSzWOXWMyF+AL2a2x3kWr2q2DSa1IbMuOhhwAWPrxtZld+wuCEZxpPOPJebDESEd+yrcHXlO9F2kndqMnYNBdxMpgz5/TamL4RCLyy8Ib/SP1+XuaMY9cTLwKIBnPNtJ37+kUa3Sa8W2VbP/sLRG+O7Y7kXOdEHUi7WDs2JMO9G5ZpkVUwQ5rQ0ILONio+AKwKKiPJRa34zoHIbRRDjq3wL/Rfkv7xJNT5cpYNS/ViPTU+EncM7lACy9cykyBcWSQaeOAmUS8hwvH3Je4XXS7OSwQs1sKtYEguL0dQdd7o4NJ6CEEK/P12xyfAQT3vdX3hzsHElDtEYYjCPxnI2k471NqqMIJ+3ieOZZdi/Nm6LwVsX0ivY2uG3tU0GkfJDvdHThuMYEcoq1CSzLoyMoyiHIGFrnRP5e6RgsxCpjVNokh8OF+XW8vG+el76cjxMyVBgU3gyX+v96kYdQDZXcTG73LMD8QjlAOW80g143x7B0xrt+ouM1b+F9pttYOEP75KZHuVm0KQyVaZJM/lITpBGF+FQWduDjuRE6GRqsdvrY/fmYKkuHc2984ZDvHP6+BVzXTYkwoS8QOjL02Cai08NVGa60uqDF5KoeADN2pqr2QaMPCa7xSnrg9M7H1cKcCKkHDOORU8YgJ9ZRYM61mktSuuSmvyF0MotpT9FIOUllXcEjFzuVRtOdW16viyl1PbWuddgNuBeQAQy0aaRZ5b29O8KLs7rnqMv/eg7+VcJw17JNFr512QiNeRNIv1GSQ93LBs8UiWRrONL26Dn/fbNdKZTRnHUOWrpkv0qnCKJYjVRa6DZ1rCXymDtPfx3bzi7dRI2X6PSSQfK9SIEps9z9EZbVBJclBCrfG8nBaSTxxlQ7v+ew7KtI4R8br7yvzHXV843bYzE9HfiWXpbfGyTU6r79KsVXv7VVdGc6WEyqG0U14LmBnhaMDGKPV6MG/2ylIH7wB5Vmv3S/bUQhuME+ORSdn2gzMlqb8sj04AGdS7FekrytZbbEDXwDwVaYXvGQR0UDQNN1+zYObbrPR7ZxFTjwejE3xdNT3kfaRhtA2SF9s8552sJhpIZwmg0tbok9RV4zn5sP3ZW5wOStCucjn1GogcrzB7JlE8tWARQ0ACxDqe9/ubpw/227xXl0uciUARECs8jC2TeCd79adFoY0vtreBJIT7zNG+PuryAnPl31RzLwv6qw36JRN+Hv5V9vkPNROPhpUaEcbyM0fY0fDk/XtzsXQdnPA/DgcNgt6HUWlL8m8KRkwrBCGc01znQVrcVJlshjd0yyWe8VarDaR0U+GNnzHghphXdu3i7vddq1xxLwaX/nyc74FdEujRpF27rVRes26wxSEleh0R5sspemkG0ESBnSHN4Whs2RXtMukk38NyTtlJNce6ArocskcJXigXJLWr1hE6MGiHNoxk9TtBW3CdHCxzRX6pCbOOzK7OYG0vJOkx2Q1OqQM0JrXbUaMG7L/86Rqo3y+Fc/dmi+H/+bTPiyjDuGgt1+bz9Z7R6jQECJbCSZqZkz9eKMVbLb8v0xlqZgO5Jb6Yn0ZPQMgeU3oP0S55pu6oyaKubv0KblzNWNTdqf9u7wXyath+FMBdQY3LJ6C6M0qOlaCGHeE/QONUAitDwvSlUrUgyR6OT3KxxMg6LtaRlXAGjUF8JFpyuFTWbplGAK9IObwqhJP5NgTkokKNlxQalgnEJA== \ No newline at end of file diff --git a/vendor/rbase64/tests/resources/bytes.bin b/vendor/rbase64/tests/resources/bytes.bin new file mode 100644 index 0000000000000000000000000000000000000000..482f73d2cc7263c8c08e499dcd9368b81f8d9aff GIT binary patch literal 10240 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 — 1999-08-20 + + +The ASCII compatible UTF-8 encoding of ISO 10646 and Unicode +plain-text files is defined in RFC 2279 and in ISO 10646-1 Annex R. + + +Using Unicode/UTF-8, you can write in emails and source code things such as + +Mathematics and Sciences: + + ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β), + + ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (A ⇔ B), + + 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm + +Linguistics and dictionaries: + + ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn + Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ] + +APL: + + ((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈ + +Nicer typography in plain text files: + + ╔══════════════════════════════════════════╗ + ║ ║ + ║ • ‘single’ and “double” quotes ║ + ║ ║ + ║ • Curly apostrophes: “We’ve been here” ║ + ║ ║ + ║ • Latin-1 apostrophe and accents: '´` ║ + ║ ║ + ║ • ‚deutsche‘ „Anführungszeichen“ ║ + ║ ║ + ║ • †, ‡, ‰, •, 3–4, —, −5/+5, ™, … ║ + ║ ║ + ║ • ASCII safety test: 1lI|, 0OD, 8B ║ + ║ ╭─────────╮ ║ + ║ • the euro symbol: │ 14.95 € │ ║ + ║ ╰─────────╯ ║ + ╚══════════════════════════════════════════╝ + +Greek (in Polytonic): + + The Greek anthem: + + Σὲ γνωρίζω ἀπὸ τὴν κόψη + τοῦ σπαθιοῦ τὴν τρομερή, + σὲ γνωρίζω ἀπὸ τὴν ὄψη + ποὺ μὲ βία μετράει τὴ γῆ. + + ᾿Απ᾿ τὰ κόκκαλα βγαλμένη + τῶν ῾Ελλήνων τὰ ἱερά + καὶ σὰν πρῶτα ἀνδρειωμένη + χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά! + + From a speech of Demosthenes in the 4th century BC: + + Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι, + ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς + λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ + τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿ + εἰς τοῦτο προήκοντα, ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ + πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν + οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι, + οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν + ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον + τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι + γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν + προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους + σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ + τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ + τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς + τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον. + + Δημοσθένους, Γ´ ᾿Ολυνθιακὸς + +Georgian: + + From a Unicode conference invitation: + + გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო + კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს, + ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს + ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი, + ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება + ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში, + ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში. + +Russian: + + From a Unicode conference invitation: + + Зарегистрируйтесь сейчас на Десятую Международную Конференцию по + Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии. + Конференция соберет широкий круг экспертов по вопросам глобального + Интернета и Unicode, локализации и интернационализации, воплощению и + применению Unicode в различных операционных системах и программных + приложениях, шрифтах, верстке и многоязычных компьютерных системах. + +Thai (UCS Level 2): + + Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese + classic 'San Gua'): + + [----------------------------|------------------------] + ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่ + สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา + ทรงนับถือขันทีเป็นที่พึ่ง บ้านเมืองจึงวิปริตเป็นนักหนา + โฮจิ๋นเรียกทัพทั่วหัวเมืองมา หมายจะฆ่ามดชั่วตัวสำคัญ + เหมือนขับไสไล่เสือจากเคหา รับหมาป่าเข้ามาเลยอาสัญ + ฝ่ายอ้องอุ้นยุแยกให้แตกกัน ใช้สาวนั้นเป็นชนวนชื่นชวนใจ + พลันลิฉุยกุยกีกลับก่อเหตุ ช่างอาเพศจริงหนาฟ้าร้องไห้ + ต้องรบราฆ่าฟันจนบรรลัย ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ + + (The above is a two-column text. If combining characters are handled + correctly, the lines of the second column should be aligned with the + | character above.) + +Ethiopian: + + Proverbs in the Amharic language: + + ሰማይ አይታረስ ንጉሥ አይከሰስ። + ብላ ካለኝ እንደአባቴ በቆመጠኝ። + ጌጥ ያለቤቱ ቁምጥና ነው። + ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው። + የአፍ ወለምታ በቅቤ አይታሽም። + አይጥ በበላ ዳዋ ተመታ። + ሲተረጉሙ ይደረግሙ። + ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል። + ድር ቢያብር አንበሳ ያስር። + ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም። + እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም። + የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ። + ሥራ ከመፍታት ልጄን ላፋታት። + ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል። + የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ። + ተንጋሎ ቢተፉ ተመልሶ ባፉ። + ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው። + እግርህን በፍራሽህ ልክ ዘርጋ። + +Runes: + + ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ + + (Old English, which transcribed into Latin reads 'He cwaeth that he + bude thaem lande northweardum with tha Westsae.' and means 'He said + that he lived in the northern land near the Western Sea.') + +Braille: + + ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌ + + ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞ + ⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎ + ⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂ + ⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙ + ⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑ + ⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲ + + ⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ + + ⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹ + ⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞ + ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕ + ⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹ + ⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎ + ⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎ + ⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳ + ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞ + ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ + + (The first couple of paragraphs of "A Christmas Carol" by Dickens) + +Compact font selection example text: + + ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789 + abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ + –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд + ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა + +Greetings in various languages: + + Hello world, Καλημέρα κόσμε, コンニチハ + +Box drawing alignment tests: █ + ▉ + ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳ + ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳ + ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳ + ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳ + ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎ + ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏ + ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█ + + From a34a3e5e65c617270f48aa8971b5ed7f68ea29e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:08:13 +0000 Subject: [PATCH 3/3] refactor(firehose): address code review feedback - Fix incorrect pruning guard: remove flawed `best_block_number > first` check that would have triggered spurious fatal errors on any synced node; pruning detection now happens naturally during block execution with context-enriched error message - Remove unused lock acquisition in `re_trace_single_block`; tracer init check uses `let _ =` pattern without holding the lock - Improve `read_cursor` docstring to explicitly document that file corruption is treated the same as a missing file - Clarify `init_tracer` panic documentation: explain that double-init is a programming error (not just "development artifact") to prevent silent data loss - Fix misleading "automatically generated" comment in vendor/rbase64/Cargo.toml - Remove unused `error` tracing import Agent-Logs-Url: https://github.com/streamingfast/reth/sessions/088879c2-d333-44f1-83d2-83a9ae603230 Co-authored-by: maoueh <123014+maoueh@users.noreply.github.com> --- crates/firehose/src/lib.rs | 6 ++- crates/firehose/src/re_trace.rs | 72 ++++++++++++--------------------- vendor/rbase64/Cargo.toml | 6 +++ 3 files changed, 36 insertions(+), 48 deletions(-) diff --git a/crates/firehose/src/lib.rs b/crates/firehose/src/lib.rs index 412164cebdb..77fb2f170cb 100644 --- a/crates/firehose/src/lib.rs +++ b/crates/firehose/src/lib.rs @@ -68,8 +68,10 @@ pub static GLOBAL_TRACER: OnceLock> = OnceLock::new(); /// /// # Panics /// -/// Panics if called more than once in the same process, so that accidental -/// double-initialisation is caught early during development. +/// Panics if called more than once in the same process. Double-initialisation is +/// treated as a programming error because it would silently discard the first +/// tracer (and any pending shutdown handle), so it is caught eagerly at startup +/// rather than producing silent data loss later. pub fn init_tracer(config: Config) -> Option { info!( target: "reth::firehose", diff --git a/crates/firehose/src/re_trace.rs b/crates/firehose/src/re_trace.rs index 76c07bdfbe2..580818abd57 100644 --- a/crates/firehose/src/re_trace.rs +++ b/crates/firehose/src/re_trace.rs @@ -10,7 +10,7 @@ use std::{ops::RangeInclusive, path::Path}; use eyre::Context as _; use reth_stages_types::StageId; use reth_storage_api::{BlockNumReader, StageCheckpointReader}; -use tracing::{error, info, warn}; +use tracing::{info, warn}; use crate::GLOBAL_TRACER; @@ -78,16 +78,17 @@ where /// Re-emits blocks in `range` to the Firehose stdout stream. /// /// For each block the function: -/// 1. Verifies that the required historical state is available (pruning guard). -/// 2. Executes the block through a Firehose-aware executor. -/// 3. Emits the `FIRE BLOCK` line and updates the cursor file. +/// 1. Executes the block through a Firehose-aware executor. +/// 2. Emits the `FIRE BLOCK` line and updates the cursor file. /// /// # Pruning guard /// /// If the node has been configured to prune state and the required historical /// blocks are no longer available, this function returns a fatal error. Firehose /// requires an archive (or sufficiently un-pruned) node to be able to re-trace -/// historical blocks. +/// historical blocks. The pruning check is performed during block execution: when +/// `history_by_block_number` returns an error the function logs a clear fatal message +/// and propagates the error rather than silently skipping blocks. /// /// # Errors /// @@ -98,38 +99,17 @@ where { let first = *range.start(); - // ── Pruning guard ──────────────────────────────────────────────────────── - // Check whether the first block in the gap is still available. - // `best_block_number` going below `first` means we've pruned past the gap. - let earliest = provider - .best_block_number() - .context("Failed to query best block number for pruning check")?; - - // If the earliest available block number is already past `first`, we cannot - // re-trace. This heuristic is conservative; a tighter check would look at - // the prune configuration directly. - if earliest > first && first > 0 { - error!( - target: "reth::firehose", - first_gap_block = first, - earliest_available = earliest, - "Historical state required for Firehose re-trace has been pruned. \ - Firehose requires an archive node (or pruning configured to retain \ - state back to block {}). \ - Node cannot start without re-emitting the missing blocks.", - first - ); - eyre::bail!( - "Firehose gap re-trace failed: state at block {} has been pruned. \ - An archive node is required to re-emit the missing Firehose blocks. \ - Adjust your pruning configuration or restore from a Firehose-compatible snapshot.", - first - ); - } - // ── Re-trace loop ──────────────────────────────────────────────────────── for block_num in range.clone() { - re_trace_single_block(block_num, provider)?; + re_trace_single_block(block_num, provider).with_context(|| { + format!( + "Firehose gap re-trace failed at block {block_num}. \ + If the error indicates that historical state is unavailable, \ + the node requires archive-quality state back to block {first}. \ + Adjust your pruning configuration or restore from a \ + Firehose-compatible snapshot." + ) + })?; } info!( @@ -148,29 +128,26 @@ where /// A complete Firehose block includes per-call traces (call stack, storage/balance /// changes at the opcode level). Producing that level of detail requires a /// Firehose-aware EVM inspector wired into the block executor. The current -/// implementation calls the minimum set of hooks needed to emit a structurally -/// valid `FIRE BLOCK` line (block start → block end). Full call-level tracing -/// will be available once the `FirehoseEvmInspector` is integrated into the reth -/// block executor pipeline. +/// implementation returns an error directing the caller to integrate the +/// `FirehoseEvmInspector` before re-trace can succeed. fn re_trace_single_block

(block_num: u64, _provider: &P) -> eyre::Result<()> where P: StageCheckpointReader + BlockNumReader, { - let tracer_lock = GLOBAL_TRACER.get().ok_or_else(|| { + // Verify the tracer is initialised before attempting any re-execution. + let _ = GLOBAL_TRACER.get().ok_or_else(|| { eyre::eyre!("Firehose tracer not initialized; call init_tracer before re-tracing") })?; - let _tracer = - tracer_lock.lock().map_err(|e| eyre::eyre!("Firehose tracer lock poisoned: {e}"))?; - // TODO: Implement full block re-execution with the FirehoseEvmInspector. // // The complete implementation must: // 1. Fetch `RecoveredBlock` via `provider.recovered_block(block_num, ...)`. // 2. Obtain a historical state provider via `provider.history_by_block_number(block_num - - // 1)`. + // 1)`. If this returns a "state pruned" error, propagate it with the message documented + // in `firehose_re_trace_range`'s pruning guard note. // 3. Create a `State>` wrapping the state provider. - // 4. Build a `FirehoseEvmInspector` wrapping `tracer`. + // 4. Build a `FirehoseEvmInspector` wrapping the global tracer. // 5. Execute the block with `evm_config.executor_for_block(&mut state, &block)`, passing the // inspector so that `on_tx_start/end`, `on_call_enter/exit`, `on_balance_change`, // `on_storage_change`, and `on_log` are called. @@ -188,7 +165,10 @@ where /// Reads the last confirmed block number from the cursor file. /// -/// Returns `None` when the file is absent, empty, or cannot be parsed. +/// Returns `None` when the file is absent, cannot be read, or contains content +/// that cannot be parsed as a `u64`. **Note**: file corruption (unparseable content) +/// is treated the same as a missing file — the gap check is skipped in both cases. +/// Callers that need to distinguish the two cases should read the file directly. fn read_cursor(path: &Path) -> Option { let content = std::fs::read_to_string(path).ok()?; content.trim().parse::().ok() diff --git a/vendor/rbase64/Cargo.toml b/vendor/rbase64/Cargo.toml index 59c72a0df41..d24fd86dee9 100644 --- a/vendor/rbase64/Cargo.toml +++ b/vendor/rbase64/Cargo.toml @@ -1,3 +1,9 @@ +# This file was originally auto-generated by Cargo but has been manually patched +# to remove the `mimalloc` dependency, whose `#[global_allocator]` declaration +# in the rbase64 library conflicted with reth's own global allocator (jemalloc / +# snmalloc). See `[patch.crates-io]` in the workspace Cargo.toml. +# +# Original header: # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically