Merge branch 'main' into epee-32-bit

This commit is contained in:
Boog900 2025-01-07 02:50:16 +00:00
commit d32002bdff
No known key found for this signature in database
GPG key ID: 42AB1287CB0041C2
191 changed files with 6545 additions and 1948 deletions

View file

@ -1,34 +0,0 @@
# This runs `cargo audit` on all dependencies (only if Cargo deps changed)
name: Audit
on:
push:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Cache
uses: actions/cache@v4
with:
path: |
~/.cargo
target
key: audit
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: cargo install cargo-audit --locked
- name: Audit
run: cargo audit

1274
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,37 +1,61 @@
[workspace]
resolver = "2"
members = [
# Binaries
"binaries/cuprated",
"constants",
# Benchmarks
"benches/benchmark/bin",
"benches/benchmark/lib",
"benches/benchmark/example",
"benches/criterion/example",
"benches/criterion/cuprate-json-rpc",
# Consensus
"consensus",
"consensus/context",
"consensus/fast-sync",
"consensus/rules",
"cryptonight",
"helper",
# Net
"net/epee-encoding",
"net/fixed-bytes",
"net/levin",
"net/wire",
# P2P
"p2p/p2p",
"p2p/p2p-core",
"p2p/bucket",
"p2p/dandelion-tower",
"p2p/async-buffer",
"p2p/address-book",
# Storage
"storage/blockchain",
"storage/service",
"storage/txpool",
"storage/database",
"pruning",
"test-utils",
"types",
# RPC
"rpc/json-rpc",
"rpc/types",
"rpc/interface",
# ZMQ
"zmq/types",
# Misc
"constants",
"cryptonight",
"helper",
"pruning",
"test-utils",
"types",
]
[profile.release]
panic = "abort"
lto = true # Build with LTO
strip = "none" # Keep panic stack traces
codegen-units = 1 # Optimize for binary speed over compile times
@ -51,82 +75,90 @@ opt-level = 3
[workspace.dependencies]
# Cuprate members
cuprate-fast-sync = { path = "consensus/fast-sync" ,default-features = false}
cuprate-consensus-rules = { path = "consensus/rules" ,default-features = false}
cuprate-constants = { path = "constants" ,default-features = false}
cuprate-consensus = { path = "consensus" ,default-features = false}
cuprate-consensus-context = { path = "consensus/context" ,default-features = false}
cuprate-cryptonight = { path = "cryptonight" ,default-features = false}
cuprate-helper = { path = "helper" ,default-features = false}
cuprate-epee-encoding = { path = "net/epee-encoding" ,default-features = false}
cuprate-fixed-bytes = { path = "net/fixed-bytes" ,default-features = false}
cuprate-levin = { path = "net/levin" ,default-features = false}
cuprate-wire = { path = "net/wire" ,default-features = false}
cuprate-p2p = { path = "p2p/p2p" ,default-features = false}
cuprate-p2p-core = { path = "p2p/p2p-core" ,default-features = false}
cuprate-dandelion-tower = { path = "p2p/dandelion-tower" ,default-features = false}
cuprate-async-buffer = { path = "p2p/async-buffer" ,default-features = false}
cuprate-address-book = { path = "p2p/address-book" ,default-features = false}
cuprate-blockchain = { path = "storage/blockchain" ,default-features = false}
cuprate-database = { path = "storage/database" ,default-features = false}
cuprate-database-service = { path = "storage/service" ,default-features = false}
cuprate-txpool = { path = "storage/txpool" ,default-features = false}
cuprate-pruning = { path = "pruning" ,default-features = false}
cuprate-test-utils = { path = "test-utils" ,default-features = false}
cuprate-types = { path = "types" ,default-features = false}
cuprate-json-rpc = { path = "rpc/json-rpc" ,default-features = false}
cuprate-rpc-types = { path = "rpc/types" ,default-features = false}
cuprate-rpc-interface = { path = "rpc/interface" ,default-features = false}
cuprate-benchmark-lib = { path = "benches/benchmark/lib", default-features = false }
cuprate-benchmark-example = { path = "benches/benchmark/example", default-features = false }
cuprate-fast-sync = { path = "consensus/fast-sync", default-features = false }
cuprate-consensus-rules = { path = "consensus/rules", default-features = false }
cuprate-constants = { path = "constants", default-features = false }
cuprate-consensus = { path = "consensus", default-features = false }
cuprate-consensus-context = { path = "consensus/context", default-features = false }
cuprate-cryptonight = { path = "cryptonight", default-features = false }
cuprate-helper = { path = "helper", default-features = false }
cuprate-epee-encoding = { path = "net/epee-encoding", default-features = false }
cuprate-fixed-bytes = { path = "net/fixed-bytes", default-features = false }
cuprate-levin = { path = "net/levin", default-features = false }
cuprate-wire = { path = "net/wire", default-features = false }
cuprate-p2p = { path = "p2p/p2p", default-features = false }
cuprate-p2p-core = { path = "p2p/p2p-core", default-features = false }
cuprate-p2p-bucket = { path = "p2p/p2p-bucket", default-features = false }
cuprate-dandelion-tower = { path = "p2p/dandelion-tower", default-features = false }
cuprate-async-buffer = { path = "p2p/async-buffer", default-features = false }
cuprate-address-book = { path = "p2p/address-book", default-features = false }
cuprate-blockchain = { path = "storage/blockchain", default-features = false }
cuprate-database = { path = "storage/database", default-features = false }
cuprate-database-service = { path = "storage/service", default-features = false }
cuprate-txpool = { path = "storage/txpool", default-features = false }
cuprate-pruning = { path = "pruning", default-features = false }
cuprate-test-utils = { path = "test-utils", default-features = false }
cuprate-types = { path = "types", default-features = false }
cuprate-json-rpc = { path = "rpc/json-rpc", default-features = false }
cuprate-rpc-types = { path = "rpc/types", default-features = false }
cuprate-rpc-interface = { path = "rpc/interface", default-features = false }
cuprate-zmq-types = { path = "zmq/types", default-features = false }
# External dependencies
anyhow = { version = "1.0.89", default-features = false }
async-trait = { version = "0.1.82", default-features = false }
bitflags = { version = "2.6.0", default-features = false }
anyhow = { version = "1", default-features = false }
arrayvec = { version = "0.7", default-features = false }
async-trait = { version = "0.1", default-features = false }
bitflags = { version = "2", default-features = false }
blake3 = { version = "1", default-features = false }
borsh = { version = "1.5.1", default-features = false }
bytemuck = { version = "1.18.0", default-features = false }
bytes = { version = "1.7.2", default-features = false }
cfg-if = { version = "1.0.0", default-features = false }
clap = { version = "4.5.17", default-features = false }
chrono = { version = "0.4.38", default-features = false }
crypto-bigint = { version = "0.5.5", default-features = false }
crossbeam = { version = "0.8.4", default-features = false }
const_format = { version = "0.2.33", default-features = false }
curve25519-dalek = { version = "4.1.3", default-features = false }
dashmap = { version = "5.5.3", default-features = false }
dirs = { version = "5.0.1", default-features = false }
futures = { version = "0.3.30", default-features = false }
hex = { version = "0.4.3", default-features = false }
borsh = { version = "1", default-features = false }
bytemuck = { version = "1", default-features = false }
bytes = { version = "1", default-features = false }
cfg-if = { version = "1", default-features = false }
clap = { version = "4", default-features = false }
chrono = { version = "0.4", default-features = false }
crypto-bigint = { version = "0.5", default-features = false }
crossbeam = { version = "0.8", default-features = false }
const_format = { version = "0.2", default-features = false }
curve25519-dalek = { version = "4", default-features = false }
dashmap = { version = "6", default-features = false }
dirs = { version = "5", default-features = false }
futures = { version = "0.3", default-features = false }
hex = { version = "0.4", default-features = false }
hex-literal = { version = "0.4", default-features = false }
indexmap = { version = "2.5.0", default-features = false }
indexmap = { version = "2", default-features = false }
monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce", default-features = false }
paste = { version = "1.0.15", default-features = false }
pin-project = { version = "1.1.5", default-features = false }
paste = { version = "1", default-features = false }
pin-project = { version = "1", default-features = false }
randomx-rs = { git = "https://github.com/Cuprate/randomx-rs.git", rev = "0028464", default-features = false }
rand = { version = "0.8.5", default-features = false }
rand_distr = { version = "0.4.3", default-features = false }
rayon = { version = "1.10.0", default-features = false }
serde_bytes = { version = "0.11.15", default-features = false }
serde_json = { version = "1.0.128", default-features = false }
serde = { version = "1.0.210", default-features = false }
strum = { version = "0.26.3", default-features = false }
thiserror = { version = "1.0.63", default-features = false }
thread_local = { version = "1.1.8", default-features = false }
tokio-util = { version = "0.7.12", default-features = false }
tokio-stream = { version = "0.1.16", default-features = false }
tokio = { version = "1.40.0", default-features = false }
rand = { version = "0.8", default-features = false }
rand_distr = { version = "0.4", default-features = false }
rayon = { version = "1", default-features = false }
serde_bytes = { version = "0.11", default-features = false }
serde_json = { version = "1", default-features = false }
serde = { version = "1", default-features = false }
strum = { version = "0.26", default-features = false }
thiserror = { version = "1", default-features = false }
thread_local = { version = "1", default-features = false }
tokio-util = { version = "0.7", default-features = false }
tokio-stream = { version = "0.1", default-features = false }
tokio = { version = "1", default-features = false }
tower = { git = "https://github.com/Cuprate/tower.git", rev = "6c7faf0", default-features = false } # <https://github.com/tower-rs/tower/pull/796>
tracing-subscriber = { version = "0.3.18", default-features = false }
tracing = { version = "0.1.40", default-features = false }
toml = { version = "0.8", default-features = false }
tracing-subscriber = { version = "0.3", default-features = false }
tracing = { version = "0.1", default-features = false }
## workspace.dev-dependencies
criterion = { version = "0.5" }
function_name = { version = "0.3" }
monero-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
tempfile = { version = "3" }
pretty_assertions = { version = "1.4.1" }
pretty_assertions = { version = "1" }
proptest = { version = "1" }
proptest-derive = { version = "0.4.0" }
tokio-test = { version = "0.4.4" }
proptest-derive = { version = "0.5" }
tokio-test = { version = "0.4" }
## TODO:
## Potential dependencies.
@ -249,6 +281,9 @@ rest_pat_in_fully_bound_structs = "deny"
redundant_type_annotations = "deny"
infinite_loop = "deny"
zero_repeat_side_effects = "deny"
non_zero_suggestions = "deny"
manual_is_power_of_two = "deny"
used_underscore_items = "deny"
# Warm
cast_possible_truncation = "deny"

View file

@ -1 +1,5 @@
# TODO
# Benches
This directory contains Cuprate's benchmarks and benchmarking utilities.
See the [`Benchmarking` section in the Architecture book](https://architecture.cuprate.org/benchmarking/intro.html)
to see how to create and run these benchmarks.

View file

@ -0,0 +1,43 @@
[package]
name = "cuprate-benchmark"
version = "0.0.0"
edition = "2021"
description = "Cuprate's benchmarking binary"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin"
keywords = ["cuprate", "benchmarking", "binary"]
[features]
# All new benchmarks should be added here!
all = ["example"]
# Non-benchmark features.
default = []
json = []
trace = []
debug = []
warn = []
info = []
error = []
# Benchmark features.
# New benchmarks should be added here!
example = [
"dep:cuprate-benchmark-example"
]
[dependencies]
cuprate-benchmark-lib = { workspace = true }
cuprate-benchmark-example = { workspace = true, optional = true }
cfg-if = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["std"] }
tracing = { workspace = true, features = ["std", "attributes"] }
tracing-subscriber = { workspace = true, features = ["fmt", "std", "env-filter"] }
[dev-dependencies]
[lints]
workspace = true

View file

@ -0,0 +1,27 @@
## `cuprate-benchmark`
This crate links all benchmarks together into a single binary that can be run as: `cuprate-benchmark`.
`cuprate-benchmark` will run all enabled benchmarks sequentially and print data at the end.
## Benchmarks
Benchmarks are opt-in and enabled via features.
| Feature | Enables which benchmark crate? |
|----------|--------------------------------|
| example | cuprate-benchmark-example |
| database | cuprate-benchmark-database |
## Features
These are features that aren't for enabling benchmarks, but rather for other things.
Since `cuprate-benchmark` is built right before it is ran,
these features almost act like command line arguments.
| Features | Does what |
|----------|-----------|
| json | Prints JSON timings instead of a markdown table
| trace | Use the `trace` log-level
| debug | Use the `debug` log-level
| warn | Use the `warn` log-level
| info | Use the `info` log-level (default)
| error | Use the `error` log-level

View file

@ -0,0 +1,29 @@
use cfg_if::cfg_if;
use tracing::{info, instrument, Level};
use tracing_subscriber::FmtSubscriber;
/// Initializes the `tracing` logger.
#[instrument]
pub(crate) fn init_logger() {
const LOG_LEVEL: Level = {
cfg_if! {
if #[cfg(feature = "trace")] {
Level::TRACE
} else if #[cfg(feature = "debug")] {
Level::DEBUG
} else if #[cfg(feature = "warn")] {
Level::WARN
} else if #[cfg(feature = "info")] {
Level::INFO
} else if #[cfg(feature = "error")] {
Level::ERROR
} else {
Level::INFO
}
}
};
FmtSubscriber::builder().with_max_level(LOG_LEVEL).init();
info!("Log level: {LOG_LEVEL}");
}

View file

@ -0,0 +1,49 @@
#![doc = include_str!("../README.md")]
#![allow(
unused_crate_dependencies,
reason = "this crate imports many potentially unused dependencies"
)]
mod log;
mod print;
mod run;
mod timings;
use cfg_if::cfg_if;
/// What `main()` does:
/// 1. Run all enabled benchmarks
/// 2. Record benchmark timings
/// 3. Print timing data
///
/// To add a new benchmark to be ran here:
/// 1. Copy + paste a `cfg_if` block
/// 2. Change it to your benchmark's feature flag
/// 3. Change it to your benchmark's type
#[allow(
clippy::allow_attributes,
unused_variables,
unused_mut,
unreachable_code,
reason = "clippy does not account for all cfg()s"
)]
fn main() {
log::init_logger();
let mut timings = timings::Timings::new();
cfg_if! {
if #[cfg(not(any(feature = "example")))] {
println!("No feature specified. Use `--features $BENCHMARK_FEATURE` when building.");
return;
}
}
cfg_if! {
if #[cfg(feature = "example")] {
run::run_benchmark::<cuprate_benchmark_example::Example>(&mut timings);
}
}
print::print_timings(&timings);
}

View file

@ -0,0 +1,38 @@
#![expect(dead_code, reason = "code hidden behind feature flags")]
use cfg_if::cfg_if;
use crate::timings::Timings;
/// Print the final the final markdown table of benchmark timings.
pub(crate) fn print_timings(timings: &Timings) {
println!("\nFinished all benchmarks, printing results:");
cfg_if! {
if #[cfg(feature = "json")] {
print_timings_json(timings);
} else {
print_timings_markdown(timings);
}
}
}
/// Default timing formatting.
pub(crate) fn print_timings_markdown(timings: &Timings) {
let mut s = String::new();
s.push_str("| Benchmark | Time (seconds) |\n");
s.push_str("|------------------------------------|----------------|");
#[expect(clippy::iter_over_hash_type)]
for (k, v) in timings {
s += &format!("\n| {k:<34} | {v:<14} |");
}
println!("\n{s}");
}
/// Enabled via `json` feature.
pub(crate) fn print_timings_json(timings: &Timings) {
let json = serde_json::to_string_pretty(timings).unwrap();
println!("\n{json}");
}

View file

@ -0,0 +1,36 @@
use tracing::{info, instrument, trace};
use cuprate_benchmark_lib::Benchmark;
use crate::timings::Timings;
/// Run a [`Benchmark`] and record its timing.
#[instrument(skip_all)]
pub(crate) fn run_benchmark<B: Benchmark>(timings: &mut Timings) {
// Get the benchmark name.
let name = B::name();
trace!("Running benchmark: {name}");
// Setup the benchmark input.
let input = B::SETUP();
// Sleep before running the benchmark.
trace!("Pre-benchmark, sleeping for: {:?}", B::POST_SLEEP_DURATION);
std::thread::sleep(B::PRE_SLEEP_DURATION);
// Run/time the benchmark.
let now = std::time::Instant::now();
B::MAIN(input);
let time = now.elapsed().as_secs_f32();
// Print the benchmark timings.
info!("{name:>34} ... {time}");
assert!(
timings.insert(name, time).is_none(),
"There were 2 benchmarks with the same name - this collides the final output: {name}",
);
// Sleep for a cooldown period after the benchmark run.
trace!("Post-benchmark, sleeping for: {:?}", B::POST_SLEEP_DURATION);
std::thread::sleep(B::POST_SLEEP_DURATION);
}

View file

@ -0,0 +1,5 @@
/// Benchmark timing data.
///
/// - Key = benchmark name
/// - Value = benchmark time in seconds
pub(crate) type Timings = std::collections::HashMap<&'static str, f32>;

View file

@ -0,0 +1,17 @@
[package]
name = "cuprate-benchmark-example"
version = "0.0.0"
edition = "2021"
description = "Example showcasing Cuprate's benchmarking harness"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/example"
keywords = ["cuprate", "benchmarking", "example"]
[dependencies]
cuprate-benchmark-lib = { path = "../lib" }
[dev-dependencies]
[lints]
workspace = true

View file

@ -0,0 +1,3 @@
## `cuprate-benchmark-example`
This crate contains a short example benchmark that shows how to implement and use
`cuprate-benchmark-lib` so that it can be ran by `cuprate-benchmark`.

View file

@ -0,0 +1,42 @@
#![doc = include_str!("../README.md")]
use std::hint::black_box;
use cuprate_benchmark_lib::Benchmark;
/// Marker struct that implements [`Benchmark`]
pub struct Example;
/// The input to our benchmark function.
pub type ExampleBenchmarkInput = u64;
/// The setup function that creates the input.
pub const fn example_benchmark_setup() -> ExampleBenchmarkInput {
1
}
/// The main benchmarking function.
#[expect(clippy::unit_arg)]
pub fn example_benchmark_main(input: ExampleBenchmarkInput) {
// In this case, we're simply benchmarking the
// performance of simple arithmetic on the input data.
fn math(input: ExampleBenchmarkInput, number: u64) {
let x = input;
let x = black_box(x * number);
let x = black_box(x / number);
let x = black_box(x + number);
let _ = black_box(x - number);
}
for number in 1..100_000_000 {
black_box(math(input, number));
}
}
// This implementation will be run by `cuprate-benchmark`.
impl Benchmark for Example {
type Input = ExampleBenchmarkInput;
const SETUP: fn() -> Self::Input = example_benchmark_setup;
const MAIN: fn(Self::Input) = example_benchmark_main;
}

View file

@ -0,0 +1,18 @@
[package]
name = "cuprate-benchmark-lib"
version = "0.0.0"
edition = "2021"
description = "Cuprate's benchmarking library"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib"
keywords = ["cuprate", "benchmarking", "library"]
[features]
[dependencies]
[dev-dependencies]
[lints]
workspace = true

View file

@ -0,0 +1,15 @@
## `cuprate-benchmark-lib`
This crate is the glue between
[`cuprate-benchmark`](https://github.com/Cuprate/cuprate/tree/benches/benches/benchmark/bin)
and all the benchmark crates.
It defines the [`crate::Benchmark`] trait, which is the behavior of all benchmarks.
See the [`cuprate-benchmark-example`](https://github.com/Cuprate/cuprate/tree/benches/benches/benchmark/example)
crate to see an example implementation of this trait.
After implementing this trait, a few steps must
be done such that the `cuprate-benchmark` binary
can actually run your benchmark crate; see the
[`Benchmarking` section in the Architecture book](https://architecture.cuprate.org/benchmarking/intro.html)
to see how to do this.

View file

@ -0,0 +1,45 @@
//! Benchmarking trait.
use std::time::Duration;
/// A benchmarking function and its inputs.
pub trait Benchmark {
/// The benchmark's name.
///
/// This is automatically implemented
/// as the name of the [`Self`] type.
//
// FIXME: use `const` instead of `fn` when stable
// <https://github.com/rust-lang/rust/issues/63084>
fn name() -> &'static str {
std::any::type_name::<Self>()
}
/// Input to the main benchmarking function.
///
/// This is passed to [`Self::MAIN`].
type Input;
/// Setup function to generate the input.
///
/// This function is not timed.
const SETUP: fn() -> Self::Input;
/// The main function to benchmark.
///
/// The start of the timer begins right before
/// this function is called and ends after the
/// function returns.
const MAIN: fn(Self::Input);
/// `cuprate-benchmark` will sleep for this [`Duration`] after
/// creating the [`Self::Input`], but before starting [`Self::MAIN`].
///
/// 1 second by default.
const PRE_SLEEP_DURATION: Duration = Duration::from_secs(1);
/// `cuprate-benchmark` will sleep for this [`Duration`] after [`Self::MAIN`].
///
/// 1 second by default.
const POST_SLEEP_DURATION: Duration = Duration::from_secs(1);
}

View file

@ -0,0 +1,5 @@
#![doc = include_str!("../README.md")]
mod benchmark;
pub use benchmark::Benchmark;

View file

@ -0,0 +1,23 @@
[package]
name = "cuprate-criterion-json-rpc"
version = "0.0.0"
edition = "2021"
description = "Criterion benchmarking for cuprate-json-rpc"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-json-rpc"
keywords = ["cuprate", "json-rpc", "criterion", "benchmark"]
[dependencies]
cuprate-json-rpc = { workspace = true }
criterion = { workspace = true }
function_name = { workspace = true }
serde_json = { workspace = true, features = ["default"] }
[[bench]]
name = "main"
harness = false
[lints]
workspace = true

View file

@ -0,0 +1,8 @@
//! Benchmarks for `cuprate-json-rpc`.
#![allow(unused_crate_dependencies)]
mod response;
criterion::criterion_main! {
response::serde,
}

View file

@ -0,0 +1,110 @@
//! Benchmarks for [`Response`].
#![allow(unused_attributes, unused_crate_dependencies)]
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use function_name::named;
use serde_json::{from_str, to_string_pretty};
use cuprate_json_rpc::{Id, Response};
// `serde` benchmarks on `Response`.
//
// These are benchmarked as `Response` has a custom serde implementation.
criterion_group! {
name = serde;
config = Criterion::default();
targets =
response_from_str_u8,
response_from_str_u64,
response_from_str_string_5_len,
response_from_str_string_10_len,
response_from_str_string_100_len,
response_from_str_string_500_len,
response_to_string_pretty_u8,
response_to_string_pretty_u64,
response_to_string_pretty_string_5_len,
response_to_string_pretty_string_10_len,
response_to_string_pretty_string_100_len,
response_to_string_pretty_string_500_len,
response_from_str_bad_field_1,
response_from_str_bad_field_5,
response_from_str_bad_field_10,
response_from_str_bad_field_100,
response_from_str_missing_field,
}
criterion_main!(serde);
/// Generate `from_str` deserialization benchmark functions for [`Response`].
macro_rules! impl_from_str_benchmark {
(
$(
$fn_name:ident => $request_type:ty => $request_string:literal,
)*
) => {
$(
#[named]
fn $fn_name(c: &mut Criterion) {
let request_string = $request_string;
c.bench_function(function_name!(), |b| {
b.iter(|| {
let _r = from_str::<Response<$request_type>>(
black_box(request_string)
);
});
});
}
)*
};
}
impl_from_str_benchmark! {
response_from_str_u8 => u8 => r#"{"jsonrpc":"2.0","id":123,"result":0}"#,
response_from_str_u64 => u64 => r#"{"jsonrpc":"2.0","id":123,"result":0}"#,
response_from_str_string_5_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"hello"}"#,
response_from_str_string_10_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"hellohello"}"#,
response_from_str_string_100_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld"}"#,
response_from_str_string_500_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld"}"#,
// The custom serde currently looks at all fields.
// These are for testing the performance if the serde
// has to parse through a bunch of unrelated fields.
response_from_str_bad_field_1 => u8 => r#"{"bad_field":0,"jsonrpc":"2.0","id":123,"result":0}"#,
response_from_str_bad_field_5 => u8 => r#"{"bad_field_1":0,"bad_field_2":0,"bad_field_3":0,"bad_field_4":0,"bad_field_5":0,"jsonrpc":"2.0","id":123,"result":0}"#,
response_from_str_bad_field_10 => u8 => r#"{"bad_field_1":0,"bad_field_2":0,"bad_field_3":0,"bad_field_4":0,"bad_field_5":0,"bad_field_6":0,"bad_field_7":0,"bad_field_8":0,"bad_field_9":0,"bad_field_10":0,"jsonrpc":"2.0","id":123,"result":0}"#,
response_from_str_bad_field_100 => u8 => r#"{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"jsonrpc":"2.0","id":123,"result":0}"#,
// These are missing the `jsonrpc` field.
response_from_str_missing_field => u8 => r#"{"id":123,"result":0}"#,
}
/// Generate `to_string_pretty` serialization benchmark functions for [`Response`].
macro_rules! impl_to_string_pretty_benchmark {
(
$(
$fn_name:ident => $request_constructor:expr_2021,
)*
) => {
$(
#[named]
fn $fn_name(c: &mut Criterion) {
let request = $request_constructor;
c.bench_function(function_name!(), |b| {
b.iter(|| {
let _s = to_string_pretty(black_box(&request)).unwrap();
});
});
}
)*
};
}
impl_to_string_pretty_benchmark! {
response_to_string_pretty_u8 => Response::<u8>::ok(Id::Null, 0),
response_to_string_pretty_u64 => Response::<u64>::ok(Id::Null, 0),
response_to_string_pretty_string_5_len => Response::ok(Id::Null, String::from("hello")),
response_to_string_pretty_string_10_len => Response::ok(Id::Null, String::from("hellohello")),
response_to_string_pretty_string_100_len => Response::ok(Id::Null, String::from("helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld")),
response_to_string_pretty_string_500_len => Response::ok(Id::Null, String::from("helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld")),
}

View file

@ -0,0 +1,2 @@
//! Benchmark lib for `cuprate-json-rpc`.
#![allow(unused_crate_dependencies, reason = "used in benchmarks")]

View file

@ -0,0 +1,21 @@
[package]
name = "cuprate-criterion-example"
version = "0.0.0"
edition = "2021"
description = "Criterion benchmarking example for Cuprate"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/example"
keywords = ["cuprate", "criterion", "benchmark", "example"]
[dependencies]
criterion = { workspace = true }
function_name = { workspace = true }
serde_json = { workspace = true, features = ["default"] }
[[bench]]
name = "main"
harness = false
[lints]
workspace = true

View file

@ -0,0 +1,14 @@
## `cuprate-criterion-example`
An example of using Criterion for benchmarking Cuprate crates.
Consider copy+pasting this crate to use as a base when creating new Criterion benchmark crates.
## `src/`
Benchmark crates have a `benches/` ran by `cargo bench`, but they are also crates themselves,
as in, they have a `src` folder that `benches/` can pull code from.
The `src` directories in these benchmarking crates are usually filled with
helper functions, types, etc, that are used repeatedly in the benchmarks.
## `benches/`
These are the actual benchmarks ran by `cargo bench`.

View file

@ -0,0 +1,48 @@
//! Benchmarks.
#![allow(unused_attributes, unused_crate_dependencies)]
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use function_name::named;
use cuprate_criterion_example::SomeHardToCreateObject;
// This is how you register criterion benchmarks.
criterion_group! {
name = benches;
config = Criterion::default();
targets = benchmark_1, benchmark_range,
}
criterion_main!(benches);
/// Benchmark a single input.
///
/// <https://bheisler.github.io/criterion.rs/book/user_guide/benchmarking_with_inputs.html#benchmarking-with-one-input>
#[named]
fn benchmark_1(c: &mut Criterion) {
// It is recommended to use `function_name!()` as a benchmark
// identifier instead of manually re-typing the function name.
c.bench_function(function_name!(), |b| {
b.iter(|| {
black_box(SomeHardToCreateObject::from(1));
});
});
}
/// Benchmark a range of inputs.
///
/// <https://bheisler.github.io/criterion.rs/book/user_guide/benchmarking_with_inputs.html#benchmarking-with-a-range-of-values>
#[named]
fn benchmark_range(c: &mut Criterion) {
let mut group = c.benchmark_group(function_name!());
for i in 0..4 {
group.throughput(Throughput::Elements(i));
group.bench_with_input(BenchmarkId::from_parameter(i), &i, |b, &i| {
b.iter(|| {
black_box(SomeHardToCreateObject::from(i));
});
});
}
group.finish();
}

View file

@ -0,0 +1,10 @@
//! Benchmarks examples.
#![allow(unused_crate_dependencies)]
// All modules within `benches/` are `mod`ed here.
mod example;
// And all the Criterion benchmarks are registered like so:
criterion::criterion_main! {
example::benches,
}

View file

@ -0,0 +1,13 @@
#![doc = include_str!("../README.md")] // See the README for crate documentation.
#![allow(unused_crate_dependencies, reason = "used in benchmarks")]
/// Shared type that all benchmarks can use.
#[expect(dead_code)]
pub struct SomeHardToCreateObject(u64);
impl From<u64> for SomeHardToCreateObject {
/// Shared function that all benchmarks can use.
fn from(value: u64) -> Self {
Self(value)
}
}

View file

@ -2,19 +2,19 @@
name = "cuprated"
version = "0.0.1"
edition = "2021"
description = "The Cuprate Monero Rust node."
description = "The Cuprate Rust Monero node."
license = "AGPL-3.0-only"
authors = ["Boog900", "hinto-janai", "SyntheticBird45"]
repository = "https://github.com/Cuprate/cuprate/tree/main/binaries/cuprated"
[dependencies]
# TODO: after v1.0.0, remove unneeded dependencies.
cuprate-consensus = { workspace = true }
cuprate-fast-sync = { workspace = true }
cuprate-consensus = { workspace = true }
cuprate-fast-sync = { workspace = true }
cuprate-consensus-context = { workspace = true }
cuprate-consensus-rules = { workspace = true }
cuprate-cryptonight = { workspace = true }
cuprate-helper = { workspace = true }
cuprate-helper = { workspace = true, features = ["serde"] }
cuprate-epee-encoding = { workspace = true }
cuprate-fixed-bytes = { workspace = true }
cuprate-levin = { workspace = true }
@ -25,9 +25,9 @@ cuprate-dandelion-tower = { workspace = true, features = ["txpool"] }
cuprate-async-buffer = { workspace = true }
cuprate-address-book = { workspace = true }
cuprate-blockchain = { workspace = true }
cuprate-database-service = { workspace = true }
cuprate-database-service = { workspace = true, features = ["serde"] }
cuprate-txpool = { workspace = true }
cuprate-database = { workspace = true }
cuprate-database = { workspace = true, features = ["serde"] }
cuprate-pruning = { workspace = true }
cuprate-test-utils = { workspace = true }
cuprate-types = { workspace = true }
@ -35,6 +35,7 @@ cuprate-json-rpc = { workspace = true }
cuprate-rpc-interface = { workspace = true }
cuprate-rpc-types = { workspace = true }
# TODO: after v1.0.0, remove unneeded dependencies.
anyhow = { workspace = true }
async-trait = { workspace = true }
@ -43,7 +44,7 @@ borsh = { workspace = true }
bytemuck = { workspace = true }
bytes = { workspace = true }
cfg-if = { workspace = true }
clap = { workspace = true, features = ["cargo"] }
clap = { workspace = true, features = ["cargo", "help", "wrap_help"] }
chrono = { workspace = true }
crypto-bigint = { workspace = true }
crossbeam = { workspace = true }
@ -70,15 +71,10 @@ thread_local = { workspace = true }
tokio-util = { workspace = true }
tokio-stream = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true, features = ["parse", "display"]}
tower = { workspace = true }
tracing-subscriber = { workspace = true, features = ["std", "fmt", "default"] }
tracing = { workspace = true }
tracing = { workspace = true, features = ["default"] }
[lints]
workspace = true
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"

View file

@ -0,0 +1,67 @@
# ____ _
# / ___| _ _ __ _ __ __ _| |_ ___
# | | | | | | '_ \| '__/ _` | __/ _ \
# | |__| |_| | |_) | | | (_| | || __/
# \____\__,_| .__/|_| \__,_|\__\___|
# |_|
#
## The network to run on, valid values: "Mainnet", "Testnet", "Stagenet".
network = "Mainnet"
## Tracing config.
[tracing]
## The minimum level for log events to be displayed.
level = "info"
## Clear-net config.
[p2p.clear_net]
## The number of outbound connections we should make and maintain.
outbound_connections = 64
## The number of extra connections we should make under load from the rest of Cuprate, i.e. when syncing.
extra_outbound_connections = 8
## The maximum number of incoming we should allow.
max_inbound_connections = 128
## The percent of outbound connections that should be to nodes we have not connected to before.
gray_peers_percent = 0.7
## The port to accept connections on, if left `0` no connections will be accepted.
p2p_port = 0
## The IP address to listen to connections on.
listen_on = "0.0.0.0"
## The Clear-net addressbook config.
[p2p.clear_net.address_book_config]
## The size of the white peer list, which contains peers we have made a connection to before.
max_white_list_length = 1_000
## The size of the gray peer list, which contains peers we have not made a connection to before.
max_gray_list_length = 5_000
## The amount of time between address book saves.
peer_save_period = { secs = 90, nanos = 0 }
## The block downloader config.
[p2p.block_downloader]
## The size of the buffer of sequential blocks waiting to be verified and added to the chain (bytes).
buffer_bytes = 50_000_000
## The size of the queue of blocks which are waiting for a parent block to be downloaded (bytes).
in_progress_queue_bytes = 50_000_000
## The target size of a batch of blocks (bytes), must not exceed 100MB.
target_batch_bytes= 5_000_000
## The amount of time between checking the pool of connected peers for free peers to download blocks.
check_client_pool_interval = { secs = 30, nanos = 0 }
## Storage config
[storage]
## The amount of reader threads to spawn.
reader_threads = "OnePerThread"
## Txpool storage config.
[storage.txpool]
## The database sync mode for the txpool.
sync_mode = "Async"
## The maximum size of all the txs in the pool (bytes).
max_txpool_byte_size = 100_000_000
## Blockchain storage config.
[storage.blockchain]
## The database sync mode for the blockchain.
sync_mode = "Async"

View file

@ -12,7 +12,7 @@ use tracing::instrument;
use cuprate_consensus::{BlockChainContext, BlockChainContextRequest, BlockChainContextResponse};
use cuprate_p2p::{
block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse},
NetworkInterface,
NetworkInterface, PeerSetRequest, PeerSetResponse,
};
use cuprate_p2p_core::ClearNet;
@ -28,15 +28,11 @@ pub enum SyncerError {
}
/// The syncer tasks that makes sure we are fully synchronised with our connected peers.
#[expect(
clippy::significant_drop_tightening,
reason = "Client pool which will be removed"
)]
#[instrument(level = "debug", skip_all)]
pub async fn syncer<C, CN>(
mut context_svc: C,
our_chain: CN,
clearnet_interface: NetworkInterface<ClearNet>,
mut clearnet_interface: NetworkInterface<ClearNet>,
incoming_block_batch_tx: mpsc::Sender<BlockBatch>,
stop_current_block_downloader: Arc<Notify>,
block_downloader_config: BlockDownloaderConfig,
@ -67,8 +63,6 @@ where
unreachable!();
};
let client_pool = clearnet_interface.client_pool();
tracing::debug!("Waiting for new sync info in top sync channel");
loop {
@ -79,9 +73,20 @@ where
check_update_blockchain_context(&mut context_svc, &mut blockchain_ctx).await?;
let raw_blockchain_context = blockchain_ctx.unchecked_blockchain_context();
if !client_pool.contains_client_with_more_cumulative_difficulty(
raw_blockchain_context.cumulative_difficulty,
) {
let PeerSetResponse::MostPoWSeen {
cumulative_difficulty,
..
} = clearnet_interface
.peer_set()
.ready()
.await?
.call(PeerSetRequest::MostPoWSeen)
.await?
else {
unreachable!();
};
if cumulative_difficulty <= raw_blockchain_context.cumulative_difficulty {
continue;
}

View file

@ -1 +1,159 @@
//! cuprated config
use std::{
fs::{read_to_string, File},
io,
path::Path,
time::Duration,
};
use clap::Parser;
use serde::{Deserialize, Serialize};
use cuprate_consensus::ContextConfig;
use cuprate_helper::{
fs::{CUPRATE_CONFIG_DIR, DEFAULT_CONFIG_FILE_NAME},
network::Network,
};
use cuprate_p2p::block_downloader::BlockDownloaderConfig;
use cuprate_p2p_core::{ClearNet, ClearNetServerCfg};
mod args;
mod fs;
mod p2p;
mod storage;
mod tracing_config;
use crate::config::fs::FileSystemConfig;
use p2p::P2PConfig;
use storage::StorageConfig;
use tracing_config::TracingConfig;
/// Reads the args & config file, returning a [`Config`].
pub fn read_config_and_args() -> Config {
let args = args::Args::parse();
args.do_quick_requests();
let config: Config = if let Some(config_file) = &args.config_file {
// If a config file was set in the args try to read it and exit if we can't.
match Config::read_from_path(config_file) {
Ok(config) => config,
Err(e) => {
eprintln!("Failed to read config from file: {e}");
std::process::exit(1);
}
}
} else {
// First attempt to read the config file from the current directory.
std::env::current_dir()
.map(|path| path.join(DEFAULT_CONFIG_FILE_NAME))
.map_err(Into::into)
.and_then(Config::read_from_path)
.inspect_err(|e| tracing::debug!("Failed to read config from current dir: {e}"))
// otherwise try the main config directory.
.or_else(|_| {
let file = CUPRATE_CONFIG_DIR.join(DEFAULT_CONFIG_FILE_NAME);
Config::read_from_path(file)
})
.inspect_err(|e| {
tracing::debug!("Failed to read config from config dir: {e}");
eprintln!("Failed to find/read config file, using default config.");
})
.unwrap_or_default()
};
args.apply_args(config)
}
/// The config for all of Cuprate.
#[derive(Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
/// The network we should run on.
network: Network,
/// [`tracing`] config.
tracing: TracingConfig,
/// The P2P network config.
p2p: P2PConfig,
/// The storage config.
storage: StorageConfig,
fs: FileSystemConfig,
}
impl Config {
/// Attempts to read a config file in [`toml`] format from the given [`Path`].
///
/// # Errors
///
/// Will return an [`Err`] if the file cannot be read or if the file is not a valid [`toml`] config.
fn read_from_path(file: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
let file_text = read_to_string(file.as_ref())?;
Ok(toml::from_str(&file_text)
.inspect(|_| eprintln!("Using config at: {}", file.as_ref().to_string_lossy()))
.inspect_err(|e| {
eprintln!("{e}");
eprintln!(
"Failed to parse config file at: {}",
file.as_ref().to_string_lossy()
);
})?)
}
/// Returns the current [`Network`] we are running on.
pub const fn network(&self) -> Network {
self.network
}
/// The [`ClearNet`], [`cuprate_p2p::P2PConfig`].
pub fn clearnet_p2p_config(&self) -> cuprate_p2p::P2PConfig<ClearNet> {
cuprate_p2p::P2PConfig {
network: self.network,
seeds: p2p::clear_net_seed_nodes(self.network),
outbound_connections: self.p2p.clear_net.general.outbound_connections,
extra_outbound_connections: self.p2p.clear_net.general.extra_outbound_connections,
max_inbound_connections: self.p2p.clear_net.general.max_inbound_connections,
gray_peers_percent: self.p2p.clear_net.general.gray_peers_percent,
server_config: Some(ClearNetServerCfg {
ip: self.p2p.clear_net.listen_on,
}),
p2p_port: self.p2p.clear_net.general.p2p_port,
// TODO: set this if a public RPC server is set.
rpc_port: 0,
address_book_config: self
.p2p
.clear_net
.general
.address_book_config(&self.fs.cache_directory, self.network),
}
}
/// The [`ContextConfig`].
pub const fn context_config(&self) -> ContextConfig {
match self.network {
Network::Mainnet => ContextConfig::main_net(),
Network::Stagenet => ContextConfig::stage_net(),
Network::Testnet => ContextConfig::test_net(),
}
}
/// The [`cuprate_blockchain`] config.
pub fn blockchain_config(&self) -> cuprate_blockchain::config::Config {
let blockchain = &self.storage.blockchain;
// We don't set reader threads as we manually make the reader threadpool.
cuprate_blockchain::config::ConfigBuilder::default()
.network(self.network)
.data_directory(self.fs.data_directory.clone())
.sync_mode(blockchain.shared.sync_mode)
.build()
}
/// The [`BlockDownloaderConfig`].
pub fn block_downloader_config(&self) -> BlockDownloaderConfig {
self.p2p.block_downloader.clone().into()
}
}

View file

@ -0,0 +1,55 @@
use std::{io::Write, path::PathBuf, process::exit};
use clap::builder::TypedValueParser;
use cuprate_helper::network::Network;
use crate::{config::Config, constants::EXAMPLE_CONFIG};
/// Cuprate Args.
#[derive(clap::Parser, Debug)]
#[command(version, about)]
pub struct Args {
/// The network to run on.
#[arg(
long,
default_value_t = Network::Mainnet,
value_parser = clap::builder::PossibleValuesParser::new(["mainnet", "testnet", "stagenet"])
.map(|s| s.parse::<Network>().unwrap()),
)]
pub network: Network,
/// The amount of outbound clear-net connections to maintain.
#[arg(long)]
pub outbound_connections: Option<usize>,
/// The PATH of the `cuprated` config file.
#[arg(long)]
pub config_file: Option<PathBuf>,
/// Generate a config file and print it to stdout.
#[arg(long)]
pub generate_config: bool,
}
impl Args {
/// Complete any quick requests asked for in [`Args`].
///
/// May cause the process to [`exit`].
pub fn do_quick_requests(&self) {
if self.generate_config {
println!("{EXAMPLE_CONFIG}");
exit(0);
}
}
/// Apply the [`Args`] to the given [`Config`].
///
/// This may exit the program if a config value was set that requires an early exit.
pub const fn apply_args(&self, mut config: Config) -> Config {
config.network = self.network;
if let Some(outbound_connections) = self.outbound_connections {
config.p2p.clear_net.general.outbound_connections = outbound_connections;
}
config
}
}

View file

@ -0,0 +1,21 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use cuprate_helper::fs::{CUPRATE_CACHE_DIR, CUPRATE_DATA_DIR};
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct FileSystemConfig {
pub data_directory: PathBuf,
pub cache_directory: PathBuf,
}
impl Default for FileSystemConfig {
fn default() -> Self {
Self {
data_directory: CUPRATE_DATA_DIR.to_path_buf(),
cache_directory: CUPRATE_CACHE_DIR.to_path_buf(),
}
}
}

View file

@ -0,0 +1,178 @@
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::Path,
time::Duration,
};
use serde::{Deserialize, Serialize};
use cuprate_helper::{fs::address_book_path, network::Network};
/// P2P config.
#[derive(Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct P2PConfig {
/// Clear-net config.
pub clear_net: ClearNetConfig,
/// Block downloader config.
pub block_downloader: BlockDownloaderConfig,
}
#[derive(Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct BlockDownloaderConfig {
/// The size in bytes of the buffer between the block downloader and the place which
/// is consuming the downloaded blocks.
pub buffer_bytes: usize,
/// The size of the in progress queue (in bytes) at which we stop requesting more blocks.
pub in_progress_queue_bytes: usize,
/// The [`Duration`] between checking the client pool for free peers.
pub check_client_pool_interval: Duration,
/// The target size of a single batch of blocks (in bytes).
pub target_batch_bytes: usize,
}
impl From<BlockDownloaderConfig> for cuprate_p2p::block_downloader::BlockDownloaderConfig {
fn from(value: BlockDownloaderConfig) -> Self {
Self {
buffer_bytes: value.buffer_bytes,
in_progress_queue_bytes: value.in_progress_queue_bytes,
check_client_pool_interval: value.check_client_pool_interval,
target_batch_bytes: value.target_batch_bytes,
initial_batch_len: 1,
}
}
}
impl Default for BlockDownloaderConfig {
fn default() -> Self {
Self {
buffer_bytes: 50_000_000,
in_progress_queue_bytes: 50_000_000,
check_client_pool_interval: Duration::from_secs(30),
target_batch_bytes: 5_000_000,
}
}
}
/// The config values for P2P clear-net.
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct ClearNetConfig {
/// The server config.
pub listen_on: IpAddr,
#[serde(flatten)]
pub general: SharedNetConfig,
}
impl Default for ClearNetConfig {
fn default() -> Self {
Self {
listen_on: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
general: Default::default(),
}
}
}
/// Network config values shared between all network zones.
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct SharedNetConfig {
/// The number of outbound connections to make and try keep.
pub outbound_connections: usize,
/// The amount of extra connections we can make if we are under load from the rest of Cuprate.
pub extra_outbound_connections: usize,
/// The maximum amount of inbound connections
pub max_inbound_connections: usize,
/// The percent of connections that should be to peers we haven't connected to before.
pub gray_peers_percent: f64,
/// port to use to accept p2p connections.
pub p2p_port: u16,
/// The address book config.
address_book_config: AddressBookConfig,
}
impl SharedNetConfig {
/// Returns the [`AddressBookConfig`].
pub fn address_book_config(
&self,
cache_dir: &Path,
network: Network,
) -> cuprate_address_book::AddressBookConfig {
cuprate_address_book::AddressBookConfig {
max_white_list_length: self.address_book_config.max_white_list_length,
max_gray_list_length: self.address_book_config.max_gray_list_length,
peer_store_directory: address_book_path(cache_dir, network),
peer_save_period: self.address_book_config.peer_save_period,
}
}
}
impl Default for SharedNetConfig {
fn default() -> Self {
Self {
outbound_connections: 64,
extra_outbound_connections: 8,
max_inbound_connections: 128,
gray_peers_percent: 0.7,
p2p_port: 0,
address_book_config: AddressBookConfig::default(),
}
}
}
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct AddressBookConfig {
max_white_list_length: usize,
max_gray_list_length: usize,
peer_save_period: Duration,
}
impl Default for AddressBookConfig {
fn default() -> Self {
Self {
max_white_list_length: 1_000,
max_gray_list_length: 5_000,
peer_save_period: Duration::from_secs(30),
}
}
}
/// Seed nodes for [`ClearNet`](cuprate_p2p_core::ClearNet).
pub fn clear_net_seed_nodes(network: Network) -> Vec<SocketAddr> {
let seeds = match network {
Network::Mainnet => [
"176.9.0.187:18080",
"88.198.163.90:18080",
"66.85.74.134:18080",
"51.79.173.165:18080",
"192.99.8.110:18080",
"37.187.74.171:18080",
"77.172.183.193:18080",
]
.as_slice(),
Network::Stagenet => [
"176.9.0.187:38080",
"51.79.173.165:38080",
"192.99.8.110:38080",
"37.187.74.171:38080",
"77.172.183.193:38080",
]
.as_slice(),
Network::Testnet => [
"176.9.0.187:28080",
"51.79.173.165:28080",
"192.99.8.110:28080",
"37.187.74.171:28080",
"77.172.183.193:28080",
]
.as_slice(),
};
seeds
.iter()
.map(|s| s.parse())
.collect::<Result<_, _>>()
.unwrap()
}

View file

@ -0,0 +1,67 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use cuprate_database::config::SyncMode;
use cuprate_database_service::ReaderThreads;
use cuprate_helper::fs::CUPRATE_DATA_DIR;
/// The storage config.
#[derive(Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct StorageConfig {
/// The amount of reader threads to spawn between the tx-pool and blockchain.
pub reader_threads: ReaderThreads,
/// The tx-pool config.
pub txpool: TxpoolConfig,
/// The blockchain config.
pub blockchain: BlockchainConfig,
}
/// The blockchain config.
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct BlockchainConfig {
#[serde(flatten)]
pub shared: SharedStorageConfig,
}
impl Default for BlockchainConfig {
fn default() -> Self {
Self {
shared: SharedStorageConfig {
sync_mode: SyncMode::Async,
},
}
}
}
/// The tx-pool config.
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct TxpoolConfig {
#[serde(flatten)]
pub shared: SharedStorageConfig,
/// The maximum size of the tx-pool.
pub max_txpool_byte_size: usize,
}
impl Default for TxpoolConfig {
fn default() -> Self {
Self {
shared: SharedStorageConfig {
sync_mode: SyncMode::Async,
},
max_txpool_byte_size: 100_000_000,
}
}
}
/// Config values shared between the tx-pool and blockchain.
#[derive(Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct SharedStorageConfig {
/// The [`SyncMode`] of the database.
pub sync_mode: SyncMode,
}

View file

@ -0,0 +1,42 @@
use serde::{Deserialize, Serialize};
use tracing::level_filters::LevelFilter;
/// [`tracing`] config.
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct TracingConfig {
/// The default minimum log level.
#[serde(with = "level_filter_serde")]
level: LevelFilter,
}
impl Default for TracingConfig {
fn default() -> Self {
Self {
level: LevelFilter::INFO,
}
}
}
mod level_filter_serde {
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serializer};
use tracing::level_filters::LevelFilter;
#[expect(clippy::trivially_copy_pass_by_ref, reason = "serde")]
pub fn serialize<S>(level_filter: &LevelFilter, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(&level_filter.to_string())
}
pub fn deserialize<'de, D>(d: D) -> Result<LevelFilter, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(d)?;
LevelFilter::from_str(&s).map_err(serde::de::Error::custom)
}
}

View file

@ -18,9 +18,12 @@ pub const VERSION_BUILD: &str = if cfg!(debug_assertions) {
pub const PANIC_CRITICAL_SERVICE_ERROR: &str =
"A service critical to Cuprate's function returned an unexpected error.";
pub const EXAMPLE_CONFIG: &str = include_str!("../Cuprated.toml");
#[cfg(test)]
mod test {
use super::*;
use crate::config::Config;
#[test]
fn version() {
@ -35,4 +38,9 @@ mod test {
assert_eq!(VERSION_BUILD, "0.0.1-release");
}
}
#[test]
fn generate_config_text_is_valid() {
let config: Config = toml::from_str(EXAMPLE_CONFIG).unwrap();
}
}

View file

@ -9,6 +9,10 @@
unused_variables,
clippy::needless_pass_by_value,
clippy::unused_async,
clippy::diverging_sub_expression,
unused_mut,
clippy::let_unit_value,
clippy::needless_pass_by_ref_mut,
reason = "TODO: remove after v1.0.0"
)]
@ -25,6 +29,8 @@ fn main() {
// Initialize global static `LazyLock` data.
statics::init_lazylock_statics();
let _config = config::read_config_and_args();
// TODO: everything else.
todo!()
}

View file

@ -1,8 +1,57 @@
//! P2P
//!
//! Will handle initiating the P2P and contains a protocol request handler.
use futures::{FutureExt, TryFutureExt};
use tokio::sync::oneshot;
use tower::ServiceExt;
use cuprate_blockchain::service::BlockchainReadHandle;
use cuprate_consensus::BlockChainContextService;
use cuprate_p2p::{NetworkInterface, P2PConfig};
use cuprate_p2p_core::ClearNet;
use cuprate_txpool::service::TxpoolReadHandle;
use crate::txpool::IncomingTxHandler;
mod core_sync_service;
mod network_address;
pub mod request_handler;
pub use network_address::CrossNetworkInternalPeerId;
/// Starts the P2P clearnet network, returning a [`NetworkInterface`] to interact with it.
///
/// A [`oneshot::Sender`] is also returned to provide the [`IncomingTxHandler`], until this is provided network
/// handshakes can not be completed.
pub async fn start_clearnet_p2p(
blockchain_read_handle: BlockchainReadHandle,
blockchain_context_service: BlockChainContextService,
txpool_read_handle: TxpoolReadHandle,
config: P2PConfig<ClearNet>,
) -> Result<
(
NetworkInterface<ClearNet>,
oneshot::Sender<IncomingTxHandler>,
),
tower::BoxError,
> {
let (incoming_tx_handler_tx, incoming_tx_handler_rx) = oneshot::channel();
let request_handler_maker = request_handler::P2pProtocolRequestHandlerMaker {
blockchain_read_handle,
blockchain_context_service: blockchain_context_service.clone(),
txpool_read_handle,
incoming_tx_handler: None,
incoming_tx_handler_fut: incoming_tx_handler_rx.shared(),
};
Ok((
cuprate_p2p::initialize_network(
request_handler_maker.map_response(|s| s.map_err(Into::into)),
core_sync_service::CoreSyncService(blockchain_context_service),
config,
)
.await?,
incoming_tx_handler_tx,
))
}

View file

@ -0,0 +1,49 @@
use std::task::{Context, Poll};
use futures::{future::BoxFuture, FutureExt, TryFutureExt};
use tower::Service;
use cuprate_consensus::{
BlockChainContextRequest, BlockChainContextResponse, BlockChainContextService,
};
use cuprate_helper::{cast::usize_to_u64, map::split_u128_into_low_high_bits};
use cuprate_p2p_core::services::{CoreSyncDataRequest, CoreSyncDataResponse};
use cuprate_wire::CoreSyncData;
/// The core sync service.
#[derive(Clone)]
pub struct CoreSyncService(pub BlockChainContextService);
impl Service<CoreSyncDataRequest> for CoreSyncService {
type Response = CoreSyncDataResponse;
type Error = tower::BoxError;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, _: CoreSyncDataRequest) -> Self::Future {
self.0
.call(BlockChainContextRequest::Context)
.map_ok(|res| {
let BlockChainContextResponse::Context(context) = res else {
unreachable!()
};
let context = context.unchecked_blockchain_context();
let (cumulative_difficulty, cumulative_difficulty_top64) =
split_u128_into_low_high_bits(context.cumulative_difficulty);
CoreSyncDataResponse(CoreSyncData {
cumulative_difficulty,
cumulative_difficulty_top64,
current_height: usize_to_u64(context.chain_height),
pruning_seed: 0,
top_id: context.top_hash,
top_version: context.current_hf.as_u8(),
})
})
.boxed()
}
}

View file

@ -1 +1,422 @@
use std::{
collections::HashSet,
future::{ready, Ready},
hash::Hash,
task::{Context, Poll},
};
use bytes::Bytes;
use futures::{
future::{BoxFuture, Shared},
FutureExt,
};
use monero_serai::{block::Block, transaction::Transaction};
use tokio::sync::{broadcast, oneshot, watch};
use tokio_stream::wrappers::WatchStream;
use tower::{Service, ServiceExt};
use cuprate_blockchain::service::BlockchainReadHandle;
use cuprate_consensus::{
transactions::new_tx_verification_data, BlockChainContextRequest, BlockChainContextResponse,
BlockChainContextService,
};
use cuprate_dandelion_tower::TxState;
use cuprate_fixed_bytes::ByteArrayVec;
use cuprate_helper::cast::u64_to_usize;
use cuprate_helper::{
asynch::rayon_spawn_async,
cast::usize_to_u64,
map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits},
};
use cuprate_p2p::constants::{
MAX_BLOCKS_IDS_IN_CHAIN_ENTRY, MAX_BLOCK_BATCH_LEN, MAX_TRANSACTION_BLOB_SIZE, MEDIUM_BAN,
};
use cuprate_p2p_core::{
client::{InternalPeerID, PeerInformation},
NetZoneAddress, NetworkZone, ProtocolRequest, ProtocolResponse,
};
use cuprate_txpool::service::TxpoolReadHandle;
use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse},
BlockCompleteEntry, TransactionBlobs, TxsInBlock,
};
use cuprate_wire::protocol::{
ChainRequest, ChainResponse, FluffyMissingTransactionsRequest, GetObjectsRequest,
GetObjectsResponse, NewFluffyBlock, NewTransactions,
};
use crate::{
blockchain::interface::{self as blockchain_interface, IncomingBlockError},
constants::PANIC_CRITICAL_SERVICE_ERROR,
p2p::CrossNetworkInternalPeerId,
txpool::{IncomingTxError, IncomingTxHandler, IncomingTxs},
};
/// The P2P protocol request handler [`MakeService`](tower::MakeService).
#[derive(Clone)]
pub struct P2pProtocolRequestHandlerMaker {
pub blockchain_read_handle: BlockchainReadHandle,
pub blockchain_context_service: BlockChainContextService,
pub txpool_read_handle: TxpoolReadHandle,
/// The [`IncomingTxHandler`], wrapped in an [`Option`] as there is a cyclic reference between [`P2pProtocolRequestHandlerMaker`]
/// and the [`IncomingTxHandler`].
pub incoming_tx_handler: Option<IncomingTxHandler>,
/// A [`Future`](std::future::Future) that produces the [`IncomingTxHandler`].
pub incoming_tx_handler_fut: Shared<oneshot::Receiver<IncomingTxHandler>>,
}
impl<A: NetZoneAddress> Service<PeerInformation<A>> for P2pProtocolRequestHandlerMaker
where
InternalPeerID<A>: Into<CrossNetworkInternalPeerId>,
{
type Response = P2pProtocolRequestHandler<A>;
type Error = tower::BoxError;
type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.incoming_tx_handler.is_none() {
return self
.incoming_tx_handler_fut
.poll_unpin(cx)
.map(|incoming_tx_handler| {
self.incoming_tx_handler = Some(incoming_tx_handler?);
Ok(())
});
}
Poll::Ready(Ok(()))
}
fn call(&mut self, peer_information: PeerInformation<A>) -> Self::Future {
let Some(incoming_tx_handler) = self.incoming_tx_handler.clone() else {
panic!("poll_ready was not called or did not return `Poll::Ready`")
};
// TODO: check sync info?
let blockchain_read_handle = self.blockchain_read_handle.clone();
let txpool_read_handle = self.txpool_read_handle.clone();
ready(Ok(P2pProtocolRequestHandler {
peer_information,
blockchain_read_handle,
blockchain_context_service: self.blockchain_context_service.clone(),
txpool_read_handle,
incoming_tx_handler,
}))
}
}
/// The P2P protocol request handler.
#[derive(Clone)]
pub struct P2pProtocolRequestHandler<N: NetZoneAddress> {
peer_information: PeerInformation<N>,
blockchain_read_handle: BlockchainReadHandle,
blockchain_context_service: BlockChainContextService,
txpool_read_handle: TxpoolReadHandle,
incoming_tx_handler: IncomingTxHandler,
}
impl<A: NetZoneAddress> Service<ProtocolRequest> for P2pProtocolRequestHandler<A>
where
InternalPeerID<A>: Into<CrossNetworkInternalPeerId>,
{
type Response = ProtocolResponse;
type Error = anyhow::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, request: ProtocolRequest) -> Self::Future {
match request {
ProtocolRequest::GetObjects(r) => {
get_objects(r, self.blockchain_read_handle.clone()).boxed()
}
ProtocolRequest::GetChain(r) => {
get_chain(r, self.blockchain_read_handle.clone()).boxed()
}
ProtocolRequest::FluffyMissingTxs(r) => {
fluffy_missing_txs(r, self.blockchain_read_handle.clone()).boxed()
}
ProtocolRequest::NewBlock(_) => ready(Err(anyhow::anyhow!(
"Peer sent a full block when we support fluffy blocks"
)))
.boxed(),
ProtocolRequest::NewFluffyBlock(r) => new_fluffy_block(
self.peer_information.clone(),
r,
self.blockchain_read_handle.clone(),
self.txpool_read_handle.clone(),
)
.boxed(),
ProtocolRequest::NewTransactions(r) => new_transactions(
self.peer_information.clone(),
r,
self.blockchain_context_service.clone(),
self.incoming_tx_handler.clone(),
)
.boxed(),
ProtocolRequest::GetTxPoolCompliment(_) => ready(Ok(ProtocolResponse::NA)).boxed(), // TODO: should we support this?
}
}
}
//---------------------------------------------------------------------------------------------------- Handler functions
/// [`ProtocolRequest::GetObjects`]
async fn get_objects(
request: GetObjectsRequest,
mut blockchain_read_handle: BlockchainReadHandle,
) -> anyhow::Result<ProtocolResponse> {
if request.blocks.len() > MAX_BLOCK_BATCH_LEN {
anyhow::bail!("Peer requested more blocks than allowed.")
}
let block_hashes: Vec<[u8; 32]> = (&request.blocks).into();
// deallocate the backing `Bytes`.
drop(request);
let BlockchainResponse::BlockCompleteEntries {
blocks,
missing_hashes,
blockchain_height,
} = blockchain_read_handle
.ready()
.await?
.call(BlockchainReadRequest::BlockCompleteEntries(block_hashes))
.await?
else {
unreachable!();
};
Ok(ProtocolResponse::GetObjects(GetObjectsResponse {
blocks,
missed_ids: ByteArrayVec::from(missing_hashes),
current_blockchain_height: usize_to_u64(blockchain_height),
}))
}
/// [`ProtocolRequest::GetChain`]
async fn get_chain(
request: ChainRequest,
mut blockchain_read_handle: BlockchainReadHandle,
) -> anyhow::Result<ProtocolResponse> {
if request.block_ids.len() > MAX_BLOCKS_IDS_IN_CHAIN_ENTRY {
anyhow::bail!("Peer sent too many block hashes in chain request.")
}
let block_hashes: Vec<[u8; 32]> = (&request.block_ids).into();
let want_pruned_data = request.prune;
// deallocate the backing `Bytes`.
drop(request);
let BlockchainResponse::NextChainEntry {
start_height,
chain_height,
block_ids,
block_weights,
cumulative_difficulty,
first_block_blob,
} = blockchain_read_handle
.ready()
.await?
.call(BlockchainReadRequest::NextChainEntry(block_hashes, 10_000))
.await?
else {
unreachable!();
};
let Some(start_height) = start_height else {
anyhow::bail!("The peers chain has a different genesis block than ours.");
};
let (cumulative_difficulty_low64, cumulative_difficulty_top64) =
split_u128_into_low_high_bits(cumulative_difficulty);
Ok(ProtocolResponse::GetChain(ChainResponse {
start_height: usize_to_u64(std::num::NonZero::get(start_height)),
total_height: usize_to_u64(chain_height),
cumulative_difficulty_low64,
cumulative_difficulty_top64,
m_block_ids: ByteArrayVec::from(block_ids),
first_block: first_block_blob.map_or(Bytes::new(), Bytes::from),
// only needed when pruned
m_block_weights: if want_pruned_data {
block_weights.into_iter().map(usize_to_u64).collect()
} else {
vec![]
},
}))
}
/// [`ProtocolRequest::FluffyMissingTxs`]
async fn fluffy_missing_txs(
mut request: FluffyMissingTransactionsRequest,
mut blockchain_read_handle: BlockchainReadHandle,
) -> anyhow::Result<ProtocolResponse> {
let tx_indexes = std::mem::take(&mut request.missing_tx_indices);
let block_hash: [u8; 32] = *request.block_hash;
let current_blockchain_height = request.current_blockchain_height;
// deallocate the backing `Bytes`.
drop(request);
let BlockchainResponse::TxsInBlock(res) = blockchain_read_handle
.ready()
.await?
.call(BlockchainReadRequest::TxsInBlock {
block_hash,
tx_indexes,
})
.await?
else {
unreachable!();
};
let Some(TxsInBlock { block, txs }) = res else {
anyhow::bail!("The peer requested txs out of range.");
};
Ok(ProtocolResponse::NewFluffyBlock(NewFluffyBlock {
b: BlockCompleteEntry {
block: Bytes::from(block),
txs: TransactionBlobs::Normal(txs.into_iter().map(Bytes::from).collect()),
pruned: false,
// only needed for pruned blocks.
block_weight: 0,
},
current_blockchain_height,
}))
}
/// [`ProtocolRequest::NewFluffyBlock`]
async fn new_fluffy_block<A: NetZoneAddress>(
peer_information: PeerInformation<A>,
request: NewFluffyBlock,
mut blockchain_read_handle: BlockchainReadHandle,
mut txpool_read_handle: TxpoolReadHandle,
) -> anyhow::Result<ProtocolResponse> {
// TODO: check context service here and ignore the block?
let current_blockchain_height = request.current_blockchain_height;
peer_information
.core_sync_data
.lock()
.unwrap()
.current_height = current_blockchain_height;
let (block, txs) = rayon_spawn_async(move || -> Result<_, anyhow::Error> {
let block = Block::read(&mut request.b.block.as_ref())?;
let tx_blobs = request
.b
.txs
.take_normal()
.ok_or(anyhow::anyhow!("Peer sent pruned txs in fluffy block"))?;
let txs = tx_blobs
.into_iter()
.map(|tx_blob| {
if tx_blob.len() > MAX_TRANSACTION_BLOB_SIZE {
anyhow::bail!("Peer sent a transaction over the size limit.");
}
let tx = Transaction::read(&mut tx_blob.as_ref())?;
Ok((tx.hash(), tx))
})
.collect::<Result<_, anyhow::Error>>()?;
// The backing `Bytes` will be deallocated when this closure returns.
Ok((block, txs))
})
.await?;
let res = blockchain_interface::handle_incoming_block(
block,
txs,
&mut blockchain_read_handle,
&mut txpool_read_handle,
)
.await;
match res {
Ok(_) => Ok(ProtocolResponse::NA),
Err(IncomingBlockError::UnknownTransactions(block_hash, missing_tx_indices)) => Ok(
ProtocolResponse::FluffyMissingTransactionsRequest(FluffyMissingTransactionsRequest {
block_hash: block_hash.into(),
current_blockchain_height,
missing_tx_indices: missing_tx_indices.into_iter().map(usize_to_u64).collect(),
}),
),
Err(IncomingBlockError::Orphan) => {
// Block's parent was unknown, could be syncing?
Ok(ProtocolResponse::NA)
}
Err(e) => Err(e.into()),
}
}
/// [`ProtocolRequest::NewTransactions`]
async fn new_transactions<A>(
peer_information: PeerInformation<A>,
request: NewTransactions,
mut blockchain_context_service: BlockChainContextService,
mut incoming_tx_handler: IncomingTxHandler,
) -> anyhow::Result<ProtocolResponse>
where
A: NetZoneAddress,
InternalPeerID<A>: Into<CrossNetworkInternalPeerId>,
{
let BlockChainContextResponse::Context(context) = blockchain_context_service
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(BlockChainContextRequest::Context)
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
else {
unreachable!()
};
let context = context.unchecked_blockchain_context();
// If we are more than 2 blocks behind the peer then ignore the txs - we are probably still syncing.
if usize_to_u64(context.chain_height + 2)
< peer_information
.core_sync_data
.lock()
.unwrap()
.current_height
{
return Ok(ProtocolResponse::NA);
}
let state = if request.dandelionpp_fluff {
TxState::Fluff
} else {
TxState::Stem {
from: peer_information.id.into(),
}
};
// Drop all the data except the stuff we still need.
let NewTransactions { txs, .. } = request;
let res = incoming_tx_handler
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(IncomingTxs { txs, state })
.await;
match res {
Ok(()) => Ok(ProtocolResponse::NA),
Err(e) => Err(e.into()),
}
}

View file

@ -3,6 +3,7 @@
//! Will contain the code to initiate the RPC and a request handler.
mod bin;
mod constants;
mod handler;
mod json;
mod other;

View file

@ -0,0 +1,5 @@
//! Constants used within RPC.
/// The string message used in RPC response fields for when
/// `cuprated` does not support a field that `monerod` has.
pub(super) const FIELD_NOT_SUPPORTED: &str = "`cuprated` does not support this field.";

View file

@ -8,6 +8,8 @@ use monero_serai::block::Block;
use tower::Service;
use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle};
use cuprate_consensus::BlockChainContextService;
use cuprate_pruning::PruningSeed;
use cuprate_rpc_interface::RpcHandler;
use cuprate_rpc_types::{
bin::{BinRequest, BinResponse},
@ -15,6 +17,7 @@ use cuprate_rpc_types::{
other::{OtherRequest, OtherResponse},
};
use cuprate_txpool::service::{TxpoolReadHandle, TxpoolWriteHandle};
use cuprate_types::{AddAuxPow, AuxPow, HardFork};
use crate::rpc::{bin, json, other};
@ -54,6 +57,32 @@ pub enum BlockchainManagerRequest {
/// The height of the next block in the chain.
TargetHeight,
/// Generate new blocks.
///
/// This request is only for regtest, see RPC's `generateblocks`.
GenerateBlocks {
/// Number of the blocks to be generated.
amount_of_blocks: u64,
/// The previous block's hash.
prev_block: [u8; 32],
/// The starting value for the nonce.
starting_nonce: u32,
/// The address that will receive the coinbase reward.
wallet_address: String,
},
// // TODO: the below requests actually belong to the block downloader/syncer:
// // <https://github.com/Cuprate/cuprate/pull/320#discussion_r1811089758>
// /// Get [`Span`] data.
// ///
// /// This is data that describes an active downloading process,
// /// if we are fully synced, this will return an empty [`Vec`].
// Spans,
//
/// Get the next [`PruningSeed`] needed for a pruned sync.
NextNeededPruningSeed,
}
/// TODO: use real type when public.
@ -69,6 +98,9 @@ pub enum BlockchainManagerResponse {
/// Response to [`BlockchainManagerRequest::PopBlocks`]
PopBlocks { new_height: usize },
/// Response to [`BlockchainManagerRequest::Prune`]
Prune(PruningSeed),
/// Response to [`BlockchainManagerRequest::Pruned`]
Pruned(bool),
@ -83,6 +115,19 @@ pub enum BlockchainManagerResponse {
/// Response to [`BlockchainManagerRequest::TargetHeight`]
TargetHeight { height: usize },
/// Response to [`BlockchainManagerRequest::GenerateBlocks`]
GenerateBlocks {
/// Hashes of the blocks generated.
blocks: Vec<[u8; 32]>,
/// The new top height. (TODO: is this correct?)
height: usize,
},
// /// Response to [`BlockchainManagerRequest::Spans`].
// Spans(Vec<Span<Z::Addr>>),
/// Response to [`BlockchainManagerRequest::NextNeededPruningSeed`].
NextNeededPruningSeed(PruningSeed),
}
/// TODO: use real type when public.
@ -102,6 +147,9 @@ pub struct CupratedRpcHandler {
/// Read handle to the blockchain database.
pub blockchain_read: BlockchainReadHandle,
/// Handle to the blockchain context service.
pub blockchain_context: BlockChainContextService,
/// Handle to the blockchain manager.
pub blockchain_manager: BlockchainManagerHandle,
@ -117,6 +165,7 @@ impl CupratedRpcHandler {
pub const fn new(
restricted: bool,
blockchain_read: BlockchainReadHandle,
blockchain_context: BlockChainContextService,
blockchain_manager: BlockchainManagerHandle,
txpool_read: TxpoolReadHandle,
txpool_manager: std::convert::Infallible,
@ -124,6 +173,7 @@ impl CupratedRpcHandler {
Self {
restricted,
blockchain_read,
blockchain_context,
blockchain_manager,
txpool_read,
txpool_manager,

View file

@ -2,26 +2,33 @@
use std::convert::Infallible;
use anyhow::Error;
use anyhow::{anyhow, Error};
use tower::ServiceExt;
use cuprate_helper::cast::usize_to_u64;
use cuprate_p2p_core::{
services::{AddressBookRequest, AddressBookResponse},
types::{BanState, ConnectionId},
AddressBook, NetworkZone,
};
use cuprate_pruning::PruningSeed;
use cuprate_rpc_types::misc::{ConnectionInfo, Span};
use crate::rpc::constants::FIELD_NOT_SUPPORTED;
// FIXME: use `anyhow::Error` over `tower::BoxError` in address book.
/// [`AddressBookRequest::PeerlistSize`]
pub(super) async fn peerlist_size<Z: NetworkZone>(
pub(crate) async fn peerlist_size<Z: NetworkZone>(
address_book: &mut impl AddressBook<Z>,
) -> Result<(u64, u64), Error> {
let AddressBookResponse::PeerlistSize { white, grey } = address_book
.ready()
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
.call(AddressBookRequest::PeerlistSize)
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
@ -29,17 +36,74 @@ pub(super) async fn peerlist_size<Z: NetworkZone>(
Ok((usize_to_u64(white), usize_to_u64(grey)))
}
/// [`AddressBookRequest::ConnectionInfo`]
pub(crate) async fn connection_info<Z: NetworkZone>(
address_book: &mut impl AddressBook<Z>,
) -> Result<Vec<ConnectionInfo>, Error> {
let AddressBookResponse::ConnectionInfo(vec) = address_book
.ready()
.await
.map_err(|e| anyhow!(e))?
.call(AddressBookRequest::ConnectionInfo)
.await
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
// FIXME: impl this map somewhere instead of inline.
let vec = vec
.into_iter()
.map(|info| {
let (ip, port) = match info.socket_addr {
Some(socket) => (socket.ip().to_string(), socket.port().to_string()),
None => (String::new(), String::new()),
};
ConnectionInfo {
address: info.address.to_string(),
address_type: info.address_type,
avg_download: info.avg_download,
avg_upload: info.avg_upload,
connection_id: String::from(ConnectionId::DEFAULT_STR),
current_download: info.current_download,
current_upload: info.current_upload,
height: info.height,
host: info.host,
incoming: info.incoming,
ip,
live_time: info.live_time,
localhost: info.localhost,
local_ip: info.local_ip,
peer_id: hex::encode(info.peer_id.to_ne_bytes()),
port,
pruning_seed: info.pruning_seed.compress(),
recv_count: info.recv_count,
recv_idle_time: info.recv_idle_time,
rpc_credits_per_hash: info.rpc_credits_per_hash,
rpc_port: info.rpc_port,
send_count: info.send_count,
send_idle_time: info.send_idle_time,
state: info.state,
support_flags: info.support_flags,
}
})
.collect();
Ok(vec)
}
/// [`AddressBookRequest::ConnectionCount`]
pub(super) async fn connection_count<Z: NetworkZone>(
pub(crate) async fn connection_count<Z: NetworkZone>(
address_book: &mut impl AddressBook<Z>,
) -> Result<(u64, u64), Error> {
let AddressBookResponse::ConnectionCount { incoming, outgoing } = address_book
.ready()
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
.call(AddressBookRequest::ConnectionCount)
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
@ -48,17 +112,17 @@ pub(super) async fn connection_count<Z: NetworkZone>(
}
/// [`AddressBookRequest::SetBan`]
pub(super) async fn set_ban<Z: NetworkZone>(
pub(crate) async fn set_ban<Z: NetworkZone>(
address_book: &mut impl AddressBook<Z>,
peer: cuprate_p2p_core::ban::SetBan<Z::Addr>,
set_ban: cuprate_p2p_core::types::SetBan<Z::Addr>,
) -> Result<(), Error> {
let AddressBookResponse::Ok = address_book
.ready()
.await
.expect("TODO")
.call(AddressBookRequest::SetBan(peer))
.map_err(|e| anyhow!(e))?
.call(AddressBookRequest::SetBan(set_ban))
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
@ -67,17 +131,17 @@ pub(super) async fn set_ban<Z: NetworkZone>(
}
/// [`AddressBookRequest::GetBan`]
pub(super) async fn get_ban<Z: NetworkZone>(
pub(crate) async fn get_ban<Z: NetworkZone>(
address_book: &mut impl AddressBook<Z>,
peer: Z::Addr,
) -> Result<Option<std::time::Instant>, Error> {
let AddressBookResponse::GetBan { unban_instant } = address_book
.ready()
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
.call(AddressBookRequest::GetBan(peer))
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
@ -86,19 +150,19 @@ pub(super) async fn get_ban<Z: NetworkZone>(
}
/// [`AddressBookRequest::GetBans`]
pub(super) async fn get_bans<Z: NetworkZone>(
pub(crate) async fn get_bans<Z: NetworkZone>(
address_book: &mut impl AddressBook<Z>,
) -> Result<(), Error> {
) -> Result<Vec<BanState<Z::Addr>>, Error> {
let AddressBookResponse::GetBans(bans) = address_book
.ready()
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
.call(AddressBookRequest::GetBans)
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
Ok(todo!())
Ok(bans)
}

View file

@ -1,24 +1,61 @@
//! Functions for [`BlockchainReadRequest`].
use std::{
collections::{HashMap, HashSet},
collections::{BTreeMap, HashMap, HashSet},
ops::Range,
};
use anyhow::Error;
use cuprate_blockchain::service::BlockchainReadHandle;
use monero_serai::block::Block;
use tower::{Service, ServiceExt};
use cuprate_blockchain::{service::BlockchainReadHandle, types::AltChainInfo};
use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse},
Chain, CoinbaseTxSum, ExtendedBlockHeader, MinerData, OutputHistogramEntry,
OutputHistogramInput, OutputOnChain,
Chain, ChainInfo, CoinbaseTxSum, ExtendedBlockHeader, HardFork, MinerData,
OutputHistogramEntry, OutputHistogramInput, OutputOnChain,
};
/// [`BlockchainReadRequest::Block`].
pub(crate) async fn block(
blockchain_read: &mut BlockchainReadHandle,
height: u64,
) -> Result<Block, Error> {
let BlockchainResponse::Block(block) = blockchain_read
.ready()
.await?
.call(BlockchainReadRequest::Block {
height: u64_to_usize(height),
})
.await?
else {
unreachable!();
};
Ok(block)
}
/// [`BlockchainReadRequest::BlockByHash`].
pub(crate) async fn block_by_hash(
blockchain_read: &mut BlockchainReadHandle,
hash: [u8; 32],
) -> Result<Block, Error> {
let BlockchainResponse::Block(block) = blockchain_read
.ready()
.await?
.call(BlockchainReadRequest::BlockByHash(hash))
.await?
else {
unreachable!();
};
Ok(block)
}
/// [`BlockchainReadRequest::BlockExtendedHeader`].
pub(super) async fn block_extended_header(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn block_extended_header(
blockchain_read: &mut BlockchainReadHandle,
height: u64,
) -> Result<ExtendedBlockHeader, Error> {
let BlockchainResponse::BlockExtendedHeader(header) = blockchain_read
@ -36,8 +73,8 @@ pub(super) async fn block_extended_header(
}
/// [`BlockchainReadRequest::BlockHash`].
pub(super) async fn block_hash(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn block_hash(
blockchain_read: &mut BlockchainReadHandle,
height: u64,
chain: Chain,
) -> Result<[u8; 32], Error> {
@ -57,8 +94,8 @@ pub(super) async fn block_hash(
}
/// [`BlockchainReadRequest::FindBlock`].
pub(super) async fn find_block(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn find_block(
blockchain_read: &mut BlockchainReadHandle,
block_hash: [u8; 32],
) -> Result<Option<(Chain, usize)>, Error> {
let BlockchainResponse::FindBlock(option) = blockchain_read
@ -74,8 +111,8 @@ pub(super) async fn find_block(
}
/// [`BlockchainReadRequest::FilterUnknownHashes`].
pub(super) async fn filter_unknown_hashes(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn filter_unknown_hashes(
blockchain_read: &mut BlockchainReadHandle,
block_hashes: HashSet<[u8; 32]>,
) -> Result<HashSet<[u8; 32]>, Error> {
let BlockchainResponse::FilterUnknownHashes(output) = blockchain_read
@ -91,8 +128,8 @@ pub(super) async fn filter_unknown_hashes(
}
/// [`BlockchainReadRequest::BlockExtendedHeaderInRange`]
pub(super) async fn block_extended_header_in_range(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn block_extended_header_in_range(
blockchain_read: &mut BlockchainReadHandle,
range: Range<usize>,
chain: Chain,
) -> Result<Vec<ExtendedBlockHeader>, Error> {
@ -111,8 +148,8 @@ pub(super) async fn block_extended_header_in_range(
}
/// [`BlockchainReadRequest::ChainHeight`].
pub(super) async fn chain_height(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn chain_height(
blockchain_read: &mut BlockchainReadHandle,
) -> Result<(u64, [u8; 32]), Error> {
let BlockchainResponse::ChainHeight(height, hash) = blockchain_read
.ready()
@ -127,8 +164,8 @@ pub(super) async fn chain_height(
}
/// [`BlockchainReadRequest::GeneratedCoins`].
pub(super) async fn generated_coins(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn generated_coins(
blockchain_read: &mut BlockchainReadHandle,
block_height: u64,
) -> Result<u64, Error> {
let BlockchainResponse::GeneratedCoins(generated_coins) = blockchain_read
@ -146,8 +183,8 @@ pub(super) async fn generated_coins(
}
/// [`BlockchainReadRequest::Outputs`]
pub(super) async fn outputs(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn outputs(
blockchain_read: &mut BlockchainReadHandle,
outputs: HashMap<u64, HashSet<u64>>,
) -> Result<HashMap<u64, HashMap<u64, OutputOnChain>>, Error> {
let BlockchainResponse::Outputs(outputs) = blockchain_read
@ -163,8 +200,8 @@ pub(super) async fn outputs(
}
/// [`BlockchainReadRequest::NumberOutputsWithAmount`]
pub(super) async fn number_outputs_with_amount(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn number_outputs_with_amount(
blockchain_read: &mut BlockchainReadHandle,
output_amounts: Vec<u64>,
) -> Result<HashMap<u64, usize>, Error> {
let BlockchainResponse::NumberOutputsWithAmount(map) = blockchain_read
@ -182,8 +219,8 @@ pub(super) async fn number_outputs_with_amount(
}
/// [`BlockchainReadRequest::KeyImagesSpent`]
pub(super) async fn key_images_spent(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn key_images_spent(
blockchain_read: &mut BlockchainReadHandle,
key_images: HashSet<[u8; 32]>,
) -> Result<bool, Error> {
let BlockchainResponse::KeyImagesSpent(is_spent) = blockchain_read
@ -199,8 +236,8 @@ pub(super) async fn key_images_spent(
}
/// [`BlockchainReadRequest::CompactChainHistory`]
pub(super) async fn compact_chain_history(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn compact_chain_history(
blockchain_read: &mut BlockchainReadHandle,
) -> Result<(Vec<[u8; 32]>, u128), Error> {
let BlockchainResponse::CompactChainHistory {
block_ids,
@ -218,8 +255,8 @@ pub(super) async fn compact_chain_history(
}
/// [`BlockchainReadRequest::FindFirstUnknown`]
pub(super) async fn find_first_unknown(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn find_first_unknown(
blockchain_read: &mut BlockchainReadHandle,
hashes: Vec<[u8; 32]>,
) -> Result<Option<(usize, u64)>, Error> {
let BlockchainResponse::FindFirstUnknown(resp) = blockchain_read
@ -235,8 +272,8 @@ pub(super) async fn find_first_unknown(
}
/// [`BlockchainReadRequest::TotalTxCount`]
pub(super) async fn total_tx_count(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn total_tx_count(
blockchain_read: &mut BlockchainReadHandle,
) -> Result<u64, Error> {
let BlockchainResponse::TotalTxCount(tx_count) = blockchain_read
.ready()
@ -251,8 +288,8 @@ pub(super) async fn total_tx_count(
}
/// [`BlockchainReadRequest::DatabaseSize`]
pub(super) async fn database_size(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn database_size(
blockchain_read: &mut BlockchainReadHandle,
) -> Result<(u64, u64), Error> {
let BlockchainResponse::DatabaseSize {
database_size,
@ -270,8 +307,8 @@ pub(super) async fn database_size(
}
/// [`BlockchainReadRequest::OutputHistogram`]
pub(super) async fn output_histogram(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn output_histogram(
blockchain_read: &mut BlockchainReadHandle,
input: OutputHistogramInput,
) -> Result<Vec<OutputHistogramEntry>, Error> {
let BlockchainResponse::OutputHistogram(histogram) = blockchain_read
@ -287,8 +324,8 @@ pub(super) async fn output_histogram(
}
/// [`BlockchainReadRequest::CoinbaseTxSum`]
pub(super) async fn coinbase_tx_sum(
mut blockchain_read: BlockchainReadHandle,
pub(crate) async fn coinbase_tx_sum(
blockchain_read: &mut BlockchainReadHandle,
height: u64,
count: u64,
) -> Result<CoinbaseTxSum, Error> {
@ -306,3 +343,35 @@ pub(super) async fn coinbase_tx_sum(
Ok(sum)
}
/// [`BlockchainReadRequest::AltChains`]
pub(crate) async fn alt_chains(
blockchain_read: &mut BlockchainReadHandle,
) -> Result<Vec<ChainInfo>, Error> {
let BlockchainResponse::AltChains(vec) = blockchain_read
.ready()
.await?
.call(BlockchainReadRequest::AltChains)
.await?
else {
unreachable!();
};
Ok(vec)
}
/// [`BlockchainReadRequest::AltChainCount`]
pub(crate) async fn alt_chain_count(
blockchain_read: &mut BlockchainReadHandle,
) -> Result<u64, Error> {
let BlockchainResponse::AltChainCount(count) = blockchain_read
.ready()
.await?
.call(BlockchainReadRequest::AltChainCount)
.await?
else {
unreachable!();
};
Ok(usize_to_u64(count))
}

View file

@ -2,27 +2,30 @@
use std::convert::Infallible;
use anyhow::Error;
use anyhow::{anyhow, Error};
use monero_serai::block::Block;
use tower::{Service, ServiceExt};
use cuprate_consensus_context::{
BlockChainContext, BlockChainContextRequest, BlockChainContextResponse,
BlockChainContextService,
};
use cuprate_helper::cast::u64_to_usize;
use cuprate_types::{FeeEstimate, HardFork, HardForkInfo};
// FIXME: use `anyhow::Error` over `tower::BoxError` in blockchain context.
/// [`BlockChainContextRequest::Context`].
pub(super) async fn context(
service: &mut BlockChainContextService,
height: u64,
pub(crate) async fn context(
blockchain_context: &mut BlockChainContextService,
) -> Result<BlockChainContext, Error> {
let BlockChainContextResponse::Context(context) = service
let BlockChainContextResponse::Context(context) = blockchain_context
.ready()
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
.call(BlockChainContextRequest::Context)
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
@ -31,17 +34,17 @@ pub(super) async fn context(
}
/// [`BlockChainContextRequest::HardForkInfo`].
pub(super) async fn hard_fork_info(
service: &mut BlockChainContextService,
pub(crate) async fn hard_fork_info(
blockchain_context: &mut BlockChainContextService,
hard_fork: HardFork,
) -> Result<HardForkInfo, Error> {
let BlockChainContextResponse::HardForkInfo(hf_info) = service
let BlockChainContextResponse::HardForkInfo(hf_info) = blockchain_context
.ready()
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
.call(BlockChainContextRequest::HardForkInfo(hard_fork))
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
@ -50,20 +53,47 @@ pub(super) async fn hard_fork_info(
}
/// [`BlockChainContextRequest::FeeEstimate`].
pub(super) async fn fee_estimate(
service: &mut BlockChainContextService,
pub(crate) async fn fee_estimate(
blockchain_context: &mut BlockChainContextService,
grace_blocks: u64,
) -> Result<FeeEstimate, Error> {
let BlockChainContextResponse::FeeEstimate(fee) = service
let BlockChainContextResponse::FeeEstimate(fee) = blockchain_context
.ready()
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
.call(BlockChainContextRequest::FeeEstimate { grace_blocks })
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
Ok(fee)
}
/// [`BlockChainContextRequest::CalculatePow`]
pub(crate) async fn calculate_pow(
blockchain_context: &mut BlockChainContextService,
hardfork: HardFork,
height: u64,
block: Box<Block>,
seed_hash: [u8; 32],
) -> Result<[u8; 32], Error> {
let BlockChainContextResponse::CalculatePow(hash) = blockchain_context
.ready()
.await
.map_err(|e| anyhow!(e))?
.call(BlockChainContextRequest::CalculatePow {
hardfork,
height: u64_to_usize(height),
block,
seed_hash,
})
.await
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
Ok(hash)
}

View file

@ -5,13 +5,18 @@ use monero_serai::block::Block;
use tower::{Service, ServiceExt};
use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
use cuprate_p2p_core::{types::ConnectionId, NetworkZone};
use cuprate_pruning::PruningSeed;
use cuprate_rpc_types::misc::Span;
use cuprate_types::{AddAuxPow, AuxPow, HardFork};
use crate::rpc::handler::{
BlockchainManagerHandle, BlockchainManagerRequest, BlockchainManagerResponse,
use crate::rpc::{
constants::FIELD_NOT_SUPPORTED,
handler::{BlockchainManagerHandle, BlockchainManagerRequest, BlockchainManagerResponse},
};
/// [`BlockchainManagerRequest::PopBlocks`]
pub(super) async fn pop_blocks(
pub(crate) async fn pop_blocks(
blockchain_manager: &mut BlockchainManagerHandle,
amount: u64,
) -> Result<u64, Error> {
@ -30,8 +35,10 @@ pub(super) async fn pop_blocks(
}
/// [`BlockchainManagerRequest::Prune`]
pub(super) async fn prune(blockchain_manager: &mut BlockchainManagerHandle) -> Result<(), Error> {
let BlockchainManagerResponse::Ok = blockchain_manager
pub(crate) async fn prune(
blockchain_manager: &mut BlockchainManagerHandle,
) -> Result<PruningSeed, Error> {
let BlockchainManagerResponse::Prune(seed) = blockchain_manager
.ready()
.await?
.call(BlockchainManagerRequest::Prune)
@ -40,11 +47,11 @@ pub(super) async fn prune(blockchain_manager: &mut BlockchainManagerHandle) -> R
unreachable!();
};
Ok(())
Ok(seed)
}
/// [`BlockchainManagerRequest::Pruned`]
pub(super) async fn pruned(
pub(crate) async fn pruned(
blockchain_manager: &mut BlockchainManagerHandle,
) -> Result<bool, Error> {
let BlockchainManagerResponse::Pruned(pruned) = blockchain_manager
@ -60,7 +67,7 @@ pub(super) async fn pruned(
}
/// [`BlockchainManagerRequest::RelayBlock`]
pub(super) async fn relay_block(
pub(crate) async fn relay_block(
blockchain_manager: &mut BlockchainManagerHandle,
block: Block,
) -> Result<(), Error> {
@ -77,7 +84,7 @@ pub(super) async fn relay_block(
}
/// [`BlockchainManagerRequest::Syncing`]
pub(super) async fn syncing(
pub(crate) async fn syncing(
blockchain_manager: &mut BlockchainManagerHandle,
) -> Result<bool, Error> {
let BlockchainManagerResponse::Syncing(syncing) = blockchain_manager
@ -93,7 +100,7 @@ pub(super) async fn syncing(
}
/// [`BlockchainManagerRequest::Synced`]
pub(super) async fn synced(
pub(crate) async fn synced(
blockchain_manager: &mut BlockchainManagerHandle,
) -> Result<bool, Error> {
let BlockchainManagerResponse::Synced(syncing) = blockchain_manager
@ -109,7 +116,7 @@ pub(super) async fn synced(
}
/// [`BlockchainManagerRequest::Target`]
pub(super) async fn target(
pub(crate) async fn target(
blockchain_manager: &mut BlockchainManagerHandle,
) -> Result<std::time::Duration, Error> {
let BlockchainManagerResponse::Target(target) = blockchain_manager
@ -125,7 +132,7 @@ pub(super) async fn target(
}
/// [`BlockchainManagerRequest::TargetHeight`]
pub(super) async fn target_height(
pub(crate) async fn target_height(
blockchain_manager: &mut BlockchainManagerHandle,
) -> Result<u64, Error> {
let BlockchainManagerResponse::TargetHeight { height } = blockchain_manager
@ -139,3 +146,76 @@ pub(super) async fn target_height(
Ok(usize_to_u64(height))
}
/// [`BlockchainManagerRequest::GenerateBlocks`]
pub(crate) async fn generate_blocks(
blockchain_manager: &mut BlockchainManagerHandle,
amount_of_blocks: u64,
prev_block: [u8; 32],
starting_nonce: u32,
wallet_address: String,
) -> Result<(Vec<[u8; 32]>, u64), Error> {
let BlockchainManagerResponse::GenerateBlocks { blocks, height } = blockchain_manager
.ready()
.await?
.call(BlockchainManagerRequest::GenerateBlocks {
amount_of_blocks,
prev_block,
starting_nonce,
wallet_address,
})
.await?
else {
unreachable!();
};
Ok((blocks, usize_to_u64(height)))
}
// [`BlockchainManagerRequest::Spans`]
pub(crate) async fn spans<Z: NetworkZone>(
blockchain_manager: &mut BlockchainManagerHandle,
) -> Result<Vec<Span>, Error> {
// let BlockchainManagerResponse::Spans(vec) = blockchain_manager
// .ready()
// .await?
// .call(BlockchainManagerRequest::Spans)
// .await?
// else {
// unreachable!();
// };
let vec: Vec<cuprate_p2p_core::types::Span<Z::Addr>> = todo!();
// FIXME: impl this map somewhere instead of inline.
let vec = vec
.into_iter()
.map(|span| Span {
connection_id: String::from(ConnectionId::DEFAULT_STR),
nblocks: span.nblocks,
rate: span.rate,
remote_address: span.remote_address.to_string(),
size: span.size,
speed: span.speed,
start_block_height: span.start_block_height,
})
.collect();
Ok(vec)
}
/// [`BlockchainManagerRequest::NextNeededPruningSeed`]
pub(crate) async fn next_needed_pruning_seed(
blockchain_manager: &mut BlockchainManagerHandle,
) -> Result<PruningSeed, Error> {
let BlockchainManagerResponse::NextNeededPruningSeed(seed) = blockchain_manager
.ready()
.await?
.call(BlockchainManagerRequest::NextNeededPruningSeed)
.await?
else {
unreachable!();
};
Ok(seed)
}

View file

@ -2,7 +2,7 @@
use std::convert::Infallible;
use anyhow::Error;
use anyhow::{anyhow, Error};
use tower::{Service, ServiceExt};
use cuprate_helper::cast::usize_to_u64;
@ -14,15 +14,17 @@ use cuprate_txpool::{
TxEntry,
};
// FIXME: use `anyhow::Error` over `tower::BoxError` in txpool.
/// [`TxpoolReadRequest::Backlog`]
pub(super) async fn backlog(txpool_read: &mut TxpoolReadHandle) -> Result<Vec<TxEntry>, Error> {
pub(crate) async fn backlog(txpool_read: &mut TxpoolReadHandle) -> Result<Vec<TxEntry>, Error> {
let TxpoolReadResponse::Backlog(tx_entries) = txpool_read
.ready()
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
.call(TxpoolReadRequest::Backlog)
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
@ -31,14 +33,19 @@ pub(super) async fn backlog(txpool_read: &mut TxpoolReadHandle) -> Result<Vec<Tx
}
/// [`TxpoolReadRequest::Size`]
pub(super) async fn size(txpool_read: &mut TxpoolReadHandle) -> Result<u64, Error> {
pub(crate) async fn size(
txpool_read: &mut TxpoolReadHandle,
include_sensitive_txs: bool,
) -> Result<u64, Error> {
let TxpoolReadResponse::Size(size) = txpool_read
.ready()
.await
.expect("TODO")
.call(TxpoolReadRequest::Size)
.map_err(|e| anyhow!(e))?
.call(TxpoolReadRequest::Size {
include_sensitive_txs,
})
.await
.expect("TODO")
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
@ -47,9 +54,17 @@ pub(super) async fn size(txpool_read: &mut TxpoolReadHandle) -> Result<u64, Erro
}
/// TODO
#[expect(clippy::needless_pass_by_ref_mut, reason = "TODO: remove after impl")]
pub(super) async fn flush(
txpool_read: &mut TxpoolReadHandle,
pub(crate) async fn flush(
txpool_manager: &mut Infallible,
tx_hashes: Vec<[u8; 32]>,
) -> Result<(), Error> {
todo!();
Ok(())
}
/// TODO
pub(crate) async fn relay(
txpool_manager: &mut Infallible,
tx_hashes: Vec<[u8; 32]>,
) -> Result<(), Error> {
todo!();

View file

@ -13,7 +13,7 @@ use std::{
macro_rules! define_init_lazylock_statics {
($(
$( #[$attr:meta] )*
$name:ident: $t:ty = $init_fn:expr;
$name:ident: $t:ty = $init_fn:expr_2021;
)*) => {
/// Initialize global static `LazyLock` data.
pub fn init_lazylock_statics() {

View file

@ -12,4 +12,4 @@ mod dandelion;
mod incoming_tx;
mod txs_being_handled;
pub use incoming_tx::IncomingTxHandler;
pub use incoming_tx::{IncomingTxError, IncomingTxHandler, IncomingTxs};

View file

@ -59,7 +59,7 @@ pub fn dandelion_router(clear_net: NetworkInterface<ClearNet>) -> ConcreteDandel
diffuse_service::DiffuseService {
clear_net_broadcast_service: clear_net.broadcast_svc(),
},
stem_service::OutboundPeerStream { clear_net },
stem_service::OutboundPeerStream::new(clear_net),
DANDELION_CONFIG,
)
}

View file

@ -1,14 +1,15 @@
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
task::{ready, Context, Poll},
};
use bytes::Bytes;
use futures::Stream;
use futures::{future::BoxFuture, FutureExt, Stream};
use tower::Service;
use cuprate_dandelion_tower::{traits::StemRequest, OutboundPeer};
use cuprate_p2p::{ClientPoolDropGuard, NetworkInterface};
use cuprate_p2p::{ClientDropGuard, NetworkInterface, PeerSetRequest, PeerSetResponse};
use cuprate_p2p_core::{
client::{Client, InternalPeerID},
ClearNet, NetworkZone, PeerRequest, ProtocolRequest,
@ -19,7 +20,17 @@ use crate::{p2p::CrossNetworkInternalPeerId, txpool::dandelion::DandelionTx};
/// The dandelion outbound peer stream.
pub struct OutboundPeerStream {
pub clear_net: NetworkInterface<ClearNet>,
clear_net: NetworkInterface<ClearNet>,
state: OutboundPeerStreamState,
}
impl OutboundPeerStream {
pub const fn new(clear_net: NetworkInterface<ClearNet>) -> Self {
Self {
clear_net,
state: OutboundPeerStreamState::Standby,
}
}
}
impl Stream for OutboundPeerStream {
@ -28,23 +39,49 @@ impl Stream for OutboundPeerStream {
tower::BoxError,
>;
fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// TODO: make the outbound peer choice random.
Poll::Ready(Some(Ok(self
.clear_net
.client_pool()
.outbound_client()
.map_or(OutboundPeer::Exhausted, |client| {
OutboundPeer::Peer(
CrossNetworkInternalPeerId::ClearNet(client.info.id),
StemPeerService(client),
)
}))))
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
match &mut self.state {
OutboundPeerStreamState::Standby => {
let peer_set = self.clear_net.peer_set();
let res = ready!(peer_set.poll_ready(cx));
self.state = OutboundPeerStreamState::AwaitingPeer(
peer_set.call(PeerSetRequest::StemPeer).boxed(),
);
}
OutboundPeerStreamState::AwaitingPeer(fut) => {
let res = ready!(fut.poll_unpin(cx));
return Poll::Ready(Some(res.map(|res| {
let PeerSetResponse::StemPeer(stem_peer) = res else {
unreachable!()
};
match stem_peer {
Some(peer) => OutboundPeer::Peer(
CrossNetworkInternalPeerId::ClearNet(peer.info.id),
StemPeerService(peer),
),
None => OutboundPeer::Exhausted,
}
})));
}
}
}
}
}
/// The state of the [`OutboundPeerStream`].
enum OutboundPeerStreamState {
/// Standby state.
Standby,
/// Awaiting a response from the peer-set.
AwaitingPeer(BoxFuture<'static, Result<PeerSetResponse<ClearNet>, tower::BoxError>>),
}
/// The stem service, used to send stem txs.
pub struct StemPeerService<N: NetworkZone>(ClientPoolDropGuard<N>);
pub struct StemPeerService<N: NetworkZone>(ClientDropGuard<N>);
impl<N: NetworkZone> Service<StemRequest<DandelionTx>> for StemPeerService<N> {
type Response = <Client<N> as Service<PeerRequest>>::Response;

View file

@ -43,9 +43,13 @@ use crate::{
};
/// An error that can happen handling an incoming tx.
#[derive(Debug, thiserror::Error)]
pub enum IncomingTxError {
#[error("Error parsing tx: {0}")]
Parse(std::io::Error),
#[error(transparent)]
Consensus(ExtendedConsensusError),
#[error("Duplicate tx in message")]
DuplicateTransaction,
}
@ -67,6 +71,7 @@ pub(super) type TxId = [u8; 32];
/// The service than handles incoming transaction pool transactions.
///
/// This service handles everything including verifying the tx, adding it to the pool and routing it to other nodes.
#[derive(Clone)]
pub struct IncomingTxHandler {
/// A store of txs currently being handled in incoming tx requests.
pub(super) txs_being_handled: TxsBeingHandled,

View file

@ -143,9 +143,16 @@
---
- [⚪️ Benchmarking](benchmarking/intro.md)
- [⚪️ Criterion](benchmarking/criterion.md)
- [⚪️ Harness](benchmarking/harness.md)
- [🟢 Benchmarking](benchmarking/intro.md)
- [🟢 Criterion](benchmarking/criterion/intro.md)
- [🟢 Creating](benchmarking/criterion/creating.md)
- [🟢 Running](benchmarking/criterion/running.md)
- [🟢 `cuprate-benchmark`](benchmarking/cuprate/intro.md)
- [🟢 Creating](benchmarking/cuprate/creating.md)
- [🟢 Running](benchmarking/cuprate/running.md)
---
- [⚪️ Testing](testing/intro.md)
- [⚪️ Monero data](testing/monero-data.md)
- [⚪️ RPC client](testing/rpc-client.md)
@ -157,6 +164,11 @@
---
- [🟢 Monero oddities](oddities/intro.md)
- [🟡 Little-endian IPv4 addresses](oddities/le-ipv4.md)
---
- [⚪️ Appendix](appendix/intro.md)
- [🟢 Crates](appendix/crates.md)
- [🔴 Contributing](appendix/contributing.md)

View file

@ -35,6 +35,7 @@ cargo doc --open --package cuprate-blockchain
| [`cuprate-async-buffer`](https://doc.cuprate.org/cuprate_async_buffer) | [`p2p/async-buffer/`](https://github.com/Cuprate/cuprate/tree/main/p2p/async-buffer) | A bounded SPSC, FIFO, asynchronous buffer that supports arbitrary weights for values
| [`cuprate-dandelion-tower`](https://doc.cuprate.org/cuprate_dandelion_tower) | [`p2p/dandelion-tower/`](https://github.com/Cuprate/cuprate/tree/main/p2p/dandelion-tower) | TODO
| [`cuprate-p2p`](https://doc.cuprate.org/cuprate_p2p) | [`p2p/p2p/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p) | TODO
| [`cuprate-p2p-bucket`](https://doc.cuprate.org/cuprate_p2p_bucket) | [`p2p/bucket/`](https://github.com/Cuprate/cuprate/tree/main/p2p/bucket) | A collection data structure discriminating its items into "buckets" of limited size.
| [`cuprate-p2p-core`](https://doc.cuprate.org/cuprate_p2p_core) | [`p2p/p2p-core/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p-core) | TODO
## Storage
@ -53,6 +54,11 @@ cargo doc --open --package cuprate-blockchain
| [`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) | [`rpc/interface/`](https://github.com/Cuprate/cuprate/tree/main/rpc/interface) | RPC interface & routing
| [`cuprate-rpc-handler`](https://doc.cuprate.org/cuprate_rpc_handler) | [`rpc/handler/`](https://github.com/Cuprate/cuprate/tree/main/rpc/handler) | RPC inner handlers
## ZMQ
| Crate | In-tree path | Purpose |
|-------|--------------|---------|
| [`cuprate-zmq-types`](https://doc.cuprate.org/cuprate_zmq_types) | [`zmq/types/`](https://github.com/Cuprate/cuprate/tree/main/zmq/types) | Message types for ZMQ Pub/Sub interface
## 1-off crates
| Crate | In-tree path | Purpose |
|-------|--------------|---------|
@ -62,3 +68,11 @@ cargo doc --open --package cuprate-blockchain
| [`cuprate-helper`](https://doc.cuprate.org/cuprate_helper) | [`helper/`](https://github.com/Cuprate/cuprate/tree/main/helper) | Kitchen-sink helper crate for Cuprate
| [`cuprate-test-utils`](https://doc.cuprate.org/cuprate_test_utils) | [`test-utils/`](https://github.com/Cuprate/cuprate/tree/main/test-utils) | Testing utilities for Cuprate
| [`cuprate-types`](https://doc.cuprate.org/cuprate_types) | [`types/`](https://github.com/Cuprate/cuprate/tree/main/types) | Shared types across Cuprate
## Benchmarks
| Crate | In-tree path | Purpose |
|-------|--------------|---------|
| [`cuprate-benchmark`](https://doc.cuprate.org/cuprate_benchmark) | [`benches/benchmark/bin/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin) | Cuprate benchmarking binary
| [`cuprate-benchmark-lib`](https://doc.cuprate.org/cuprate_benchmark_lib) | [`benches/benchmark/lib/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib) | Cuprate benchmarking library
| `cuprate-benchmark-*` | [`benches/benchmark/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/) | Benchmark for a Cuprate crate that uses `cuprate-benchmark`
| `cuprate-criterion-*` | [`benches/criterion/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) | Benchmark for a Cuprate crate that uses [Criterion](https://bheisler.github.io/criterion.rs/book)

View file

@ -1 +0,0 @@
# ⚪️ Criterion

View file

@ -0,0 +1,21 @@
# Creating
Creating a new Criterion-based benchmarking crate for one of Cuprate's crates is relatively simple,
although, it requires knowledge of how to use Criterion first:
1. Read the `Getting Started` section of <https://bheisler.github.io/criterion.rs/book>
2. Copy [`benches/criterion/example`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/example) as base
3. Get started
## Naming
New benchmark crates using Criterion should:
- Be in [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/)
- Be in the `cuprate-criterion-$CRATE_NAME` format
For a real example, see:
[`cuprate-criterion-json-rpc`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-json-rpc).
## Workspace
Finally, make sure to add the benchmark crate to the workspace
[`Cargo.toml`](https://github.com/Cuprate/cuprate/blob/main/Cargo.toml) file.
Your benchmark is now ready to be ran.

View file

@ -0,0 +1,4 @@
# Criterion
Each sub-directory in [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) is a crate that uses [Criterion](https://bheisler.github.io/criterion.rs/book) for timing single functions and/or groups of functions.
They are generally be small in scope.

View file

@ -0,0 +1,15 @@
# Running
To run all Criterion benchmarks, run this from the repository root:
```bash
cargo bench
```
To run specific package(s), use:
```bash
cargo bench --package $CRITERION_BENCHMARK_CRATE_NAME
```
For example:
```bash
cargo bench --package cuprate-criterion-json-rpc
```

View file

@ -0,0 +1,57 @@
# Creating
New benchmarks are plugged into `cuprate-benchmark` by:
1. Implementing `cuprate_benchmark_lib::Benchmark`
1. Registering the benchmark in the `cuprate_benchmark` binary
See [`benches/benchmark/example`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/example)
for an example.
## Creating the benchmark crate
Before plugging into `cuprate-benchmark`, your actual benchmark crate must be created:
1. Create a new crate inside `benches/benchmark` (consider copying `benches/benchmark/example` as a base)
1. Pull in `cuprate_benchmark_lib` as a dependency
1. Create a benchmark
1. Implement `cuprate_benchmark_lib::Benchmark`
New benchmark crates using `cuprate-database` should:
- Be in [`benches/benchmark/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/)
- Be in the `cuprate-benchmark-$CRATE_NAME` format
For a real example, see:
[`cuprate-benchmark-database`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/cuprate-database).
## `cuprate_benchmark_lib::Benchmark`
This is the trait that standardizes all benchmarks ran under `cuprate-benchmark`.
It must be implemented by your benchmarking crate.
See `cuprate-benchmark-lib` crate documentation for a user-guide: <https://doc.cuprate.org/cuprate_benchmark_lib>.
## Adding a feature to `cuprate-benchmark`
After your benchmark's behavior is defined, it must be registered
in the binary that is actually ran: `cuprate-benchmark`.
If your benchmark is new, add a new crate feature to [`cuprate-benchmark`'s Cargo.toml file](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin/Cargo.toml) with an optional dependency to your benchmarking crate.
Please remember to edit the feature table in the
[`README.md`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin/README.md) as well!
## Adding to `cuprate-benchmark`'s `main()`
After adding your crate's feature, add a conditional line that run the benchmark
if the feature is enabled to the `main()` function:
For example, if your crate's name is `egg`:
```rust
cfg_if! {
if #[cfg(feature = "egg")] {
run::run_benchmark::<cuprate_benchmark_egg::Benchmark>(&mut timings);
}
}
```
## Workspace
Finally, make sure to add the benchmark crate to the workspace
[`Cargo.toml`](https://github.com/Cuprate/cuprate/blob/main/Cargo.toml) file.
Your benchmark is now ready to be ran.

View file

@ -0,0 +1,37 @@
# cuprate-benchmark
Cuprate has 2 custom crates for general benchmarking:
- `cuprate-benchmark`; the actual binary crate ran
- `cuprate-benchmark-lib`; the library that other crates hook into
The abstract purpose of `cuprate-benchmark` is very simple:
1. Set-up the benchmark
1. Start timer
1. Run benchmark
1. Output data
`cuprate-benchmark` runs the benchmarks found in [`benches/benchmark/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark).
`cuprate-benchmark-lib` defines the `Benchmark` trait that all
benchmark crates implement to "plug-in" to the benchmarking harness.
## Diagram
A diagram displaying the relation between `cuprate-benchmark` and related crates.
```
┌─────────────────────┐
│ cuprate_benchmark │
│ (actual binary ran) │
└──────────┬──────────┘
┌──────────────────┴───────────────────┐
│ cuprate_benchmark_lib │
│ ┌───────────────────────────────────┐│
│ │ trait Benchmark ││
│ └───────────────────────────────────┘│
└──────────────────┬───────────────────┘
┌───────────────────────────┐ │ ┌───────────────────────────┐
│ cuprate_benchmark_example ├──┼───┤ cuprate_benchmark_* │
└───────────────────────────┘ │ └───────────────────────────┘
┌───────────────────────────┐ │ ┌───────────────────────────┐
│ cuprate_benchmark_* ├──┴───┤ cuprate_benchmark_* │
└───────────────────────────┘ └───────────────────────────┘
```

View file

@ -0,0 +1,16 @@
# Running
`cuprate-benchmark` benchmarks are ran with this command:
```bash
cargo run --release --package cuprate-benchmark --features $BENCHMARK_CRATE_FEATURE
```
For example, to run the example benchmark:
```bash
cargo run --release --package cuprate-benchmark --features example
```
Use the `all` feature to run all benchmarks:
```bash
# Run all benchmarks
cargo run --release --package cuprate-benchmark --features all
```

View file

@ -1 +0,0 @@
# ⚪️ Harness

View file

@ -1 +1,22 @@
# ⚪️ Benchmarking
# Benchmarking
Cuprate has 2 types of benchmarks:
- [Criterion](https://bheisler.github.io/criterion.rs/book/user_guide/advanced_configuration.html) benchmarks
- `cuprate-benchmark` benchmarks
Criterion is used for micro benchmarks; they time single functions, groups of functions, and generally are small in scope.
`cuprate-benchmark` and [`cuprate-benchmark-lib`](https://doc.cuprate.org/cuprate_benchmark_lib) are custom in-house crates Cuprate uses for macro benchmarks; these test sub-systems, sections of a sub-system, or otherwise larger or more complicated code that isn't well-suited for micro benchmarks.
## File layout and purpose
All benchmarking related files are in the [`benches/`](https://github.com/Cuprate/cuprate/tree/main/benches) folder.
This directory is organized like such:
| Directory | Purpose |
|-------------------------------|---------|
| [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) | Criterion (micro) benchmarks
| `benches/criterion/cuprate-*` | Criterion benchmarks for the crate with the same name
| [`benches/benchmark/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark) | Cuprate's custom benchmarking files
| [`benches/benchmark/bin`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin) | The `cuprate-benchmark` crate; the actual binary run that links all benchmarks
| [`benches/benchmark/lib`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib) | The `cuprate-benchmark-lib` crate; the benchmarking framework all benchmarks plug into
| `benches/benchmark/cuprate-*` | `cuprate-benchmark` benchmarks for the crate with the same name

View file

@ -0,0 +1,37 @@
# Monero oddities
This section is a list of any peculiar, interesting,
or non-standard behavior that Monero has that is not
planned on being changed or deprecated.
This section exists to hold all the small yet noteworthy knowledge in one place,
instead of in any single contributor's mind.
These are usually behaviors stemming from implementation rather than protocol/cryptography.
## Formatting
This is the markdown formatting for each entry in this section.
If applicable, consider using this formatting when adding to this section.
```md
# <concise_title_of_the_behavior>
## What
A detailed description of the behavior.
## Expected
The norm or standard behavior that is usually expected.
## Why
The reasoning behind why this behavior exists and/or
any links to more detailed discussion on the behavior.
## Affects
A (potentially non-exhaustive) list of places that this behavior can/does affect.
## Example
An example link or section of code where the behavior occurs.
## Source
A link to original `monerod` code that defines the behavior.
```

View file

@ -0,0 +1,24 @@
# Little-endian IPv4 addresses
## What
Monero encodes IPv4 addresses in [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order.
## Expected
In general, [networking-related protocols/code use _networking order_ (big-endian)](https://en.wikipedia.org/wiki/Endianness#Networking).
## Why
TODO
- <https://github.com/monero-project/monero/issues/3826>
- <https://github.com/monero-project/monero/pull/5544>
## Affects
Any representation and (de)serialization of IPv4 addresses must keep little
endian in-mind, e.g. the P2P wire format or `int` encoded IPv4 addresses in RPC.
For example, [the `ip` field in `set_bans`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#set_bans).
For Cuprate, this means Rust's [`Ipv4Addr::from_bits/from`](https://doc.rust-lang.org/1.82.0/src/core/net/ip_addr.rs.html#1182) cannot be used in these cases as [it assumes big-endian encoding](https://doc.rust-lang.org/1.82.0/src/core/net/ip_addr.rs.html#540).
## Source
- <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/contrib/epee/include/net/net_utils_base.h#L97>

View file

@ -328,8 +328,8 @@ fn next_difficulty(
time_span = 1;
}
// TODO: do checked operations here and unwrap so we don't silently overflow?
(windowed_work * u128::from(hf.block_time().as_secs()) + time_span - 1) / time_span
// TODO: do `checked_mul` here and unwrap so we don't silently overflow?
(windowed_work * u128::from(hf.block_time().as_secs())).div_ceil(time_span)
}
/// Get the start and end of the window to calculate difficulty.

View file

@ -18,6 +18,7 @@ use std::{
};
use futures::{channel::oneshot, FutureExt};
use monero_serai::block::Block;
use tokio::sync::mpsc;
use tokio_util::sync::PollSender;
use tower::Service;
@ -267,6 +268,21 @@ pub enum BlockChainContextRequest {
grace_blocks: u64,
},
/// Calculate proof-of-work for this block.
CalculatePow {
/// The hardfork of the protocol at this block height.
hardfork: HardFork,
/// The height of the block.
height: usize,
/// The block data.
///
/// This is boxed because [`Block`] causes this enum to be 1200 bytes,
/// where the 2nd variant is only 96 bytes.
block: Box<Block>,
/// The seed hash for the proof-of-work.
seed_hash: [u8; 32],
},
/// Clear the alt chain context caches.
ClearAltCache,
@ -364,6 +380,9 @@ pub enum BlockChainContextResponse {
/// Response to [`BlockChainContextRequest::FeeEstimate`]
FeeEstimate(FeeEstimate),
/// Response to [`BlockChainContextRequest::CalculatePow`]
CalculatePow([u8; 32]),
/// Response to [`BlockChainContextRequest::AltChains`]
///
/// If the inner [`Vec::is_empty`], there were no alternate chains.

View file

@ -324,7 +324,8 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
}
BlockChainContextRequest::HardForkInfo(_)
| BlockChainContextRequest::FeeEstimate { .. }
| BlockChainContextRequest::AltChains => {
| BlockChainContextRequest::AltChains
| BlockChainContextRequest::CalculatePow { .. } => {
todo!("finish https://github.com/Cuprate/cuprate/pull/297")
}
})

View file

@ -9,7 +9,7 @@ use clap::Parser;
use tower::{Service, ServiceExt};
use cuprate_blockchain::{
config::ConfigBuilder, cuprate_database::RuntimeError, service::BlockchainReadHandle,
config::ConfigBuilder, cuprate_database::DbResult, service::BlockchainReadHandle,
};
use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse},
@ -23,7 +23,7 @@ const BATCH_SIZE: usize = 512;
async fn read_batch(
handle: &mut BlockchainReadHandle,
height_from: usize,
) -> Result<Vec<BlockId>, RuntimeError> {
) -> DbResult<Vec<BlockId>> {
let mut block_ids = Vec::<BlockId>::with_capacity(BATCH_SIZE);
for height in height_from..(height_from + BATCH_SIZE) {

View file

@ -1,5 +1,3 @@
#![expect(non_local_definitions, reason = "proptest macro")]
use std::{
future::Future,
pin::Pin,

View file

@ -49,7 +49,7 @@ pub(crate) fn subarray_copy<T: AsRef<[U]> + ?Sized, U: Copy, const LEN: usize>(
/// A mutable reference to a fixed-size subarray of type `[U; LEN]`.
///
/// # Panics
/// Panics if `start + LEN > array.as_ref().len()`.
/// Panics if `start + LEN > array.as_mut().len()`.
#[inline]
pub(crate) fn subarray_mut<T: AsMut<[U]> + ?Sized, U, const LEN: usize>(
array: &mut T,

View file

@ -81,6 +81,9 @@ ignore = [
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
# TODO: check this is sorted before a beta release.
{ id = "RUSTSEC-2024-0370", reason = "unmaintained crate, not necessarily vulnerable yet." }
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
@ -110,6 +113,7 @@ allow = [
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"Zlib", # https://spdx.org/licenses/Zlib.html
# OpenSSL 3.0+ uses Apache-2.0
# OpenSSL 1.x.x uses https://www.openssl.org/source/license-openssl-ssleay.txt

View file

@ -35,6 +35,8 @@ futures = { workspace = true, optional = true, features = ["std"] }
monero-serai = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["derive"] }
# This is kinda a stupid work around.
# [thread] needs to activate one of these libs (windows|libc)
# although it depends on what target we're building for.

View file

@ -18,7 +18,6 @@
// //
//============================ SAFETY: DO NOT REMOVE ===========================//
//---------------------------------------------------------------------------------------------------- Free functions
/// Cast [`u64`] to [`usize`].
#[inline(always)]
pub const fn u64_to_usize(u: u64) -> usize {

View file

@ -28,7 +28,12 @@
//! - <https://docs.rs/dirs>
//---------------------------------------------------------------------------------------------------- Use
use std::{path::PathBuf, sync::LazyLock};
use std::{
path::{Path, PathBuf},
sync::LazyLock,
};
use crate::network::Network;
//---------------------------------------------------------------------------------------------------- Const
/// Cuprate's main directory.
@ -58,6 +63,9 @@ pub const CUPRATE_DIR: &str = {
}
};
/// The default name of Cuprate's config file.
pub const DEFAULT_CONFIG_FILE_NAME: &str = "Cuprated.toml";
//---------------------------------------------------------------------------------------------------- Directories
/// Create a `LazyLock` for common PATHs used by Cuprate.
///
@ -150,32 +158,61 @@ impl_path_lazylock! {
CUPRATE_DATA_DIR,
data_dir,
"",
}
/// Cuprate's blockchain directory.
///
/// This is the PATH used for any Cuprate blockchain files.
///
/// | OS | PATH |
/// |---------|----------------------------------------------------------------|
/// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\blockchain\` |
/// | macOS | `/Users/Alice/Library/Application Support/Cuprate/blockchain/` |
/// | Linux | `/home/alice/.local/share/cuprate/blockchain/` |
CUPRATE_BLOCKCHAIN_DIR,
data_dir,
"blockchain",
/// Joins the [`Network`] to the [`Path`].
///
/// This will keep the path the same for [`Network::Mainnet`].
fn path_with_network(path: &Path, network: Network) -> PathBuf {
match network {
Network::Mainnet => path.to_path_buf(),
network => path.join(network.to_string()),
}
}
/// Cuprate's transaction pool directory.
///
/// This is the PATH used for any Cuprate txpool files.
///
/// | OS | PATH |
/// |---------|------------------------------------------------------------|
/// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\txpool\` |
/// | macOS | `/Users/Alice/Library/Application Support/Cuprate/txpool/` |
/// | Linux | `/home/alice/.local/share/cuprate/txpool/` |
CUPRATE_TXPOOL_DIR,
data_dir,
"txpool",
/// Cuprate's blockchain directory.
///
/// This is the PATH used for any Cuprate blockchain files.
///
/// ```rust
/// use cuprate_helper::{network::Network, fs::{CUPRATE_DATA_DIR, blockchain_path}};
///
/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Mainnet).as_path(), CUPRATE_DATA_DIR.join("blockchain"));
/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Stagenet).as_path(), CUPRATE_DATA_DIR.join(Network::Stagenet.to_string()).join("blockchain"));
/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Testnet).as_path(), CUPRATE_DATA_DIR.join(Network::Testnet.to_string()).join("blockchain"));
/// ```
pub fn blockchain_path(data_dir: &Path, network: Network) -> PathBuf {
path_with_network(data_dir, network).join("blockchain")
}
/// Cuprate's txpool directory.
///
/// This is the PATH used for any Cuprate txpool files.
///
/// ```rust
/// use cuprate_helper::{network::Network, fs::{CUPRATE_DATA_DIR, txpool_path}};
///
/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Mainnet).as_path(), CUPRATE_DATA_DIR.join("txpool"));
/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Stagenet).as_path(), CUPRATE_DATA_DIR.join(Network::Stagenet.to_string()).join("txpool"));
/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Testnet).as_path(), CUPRATE_DATA_DIR.join(Network::Testnet.to_string()).join("txpool"));
/// ```
pub fn txpool_path(data_dir: &Path, network: Network) -> PathBuf {
path_with_network(data_dir, network).join("txpool")
}
/// Cuprate's address-book directory.
///
/// This is the PATH used for any Cuprate address-book files.
///
/// ```rust
/// use cuprate_helper::{network::Network, fs::{CUPRATE_CACHE_DIR, address_book_path}};
///
/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Mainnet).as_path(), CUPRATE_CACHE_DIR.join("addressbook"));
/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Stagenet).as_path(), CUPRATE_CACHE_DIR.join(Network::Stagenet.to_string()).join("addressbook"));
/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Testnet).as_path(), CUPRATE_CACHE_DIR.join(Network::Testnet.to_string()).join("addressbook"));
/// ```
pub fn address_book_path(cache_dir: &Path, network: Network) -> PathBuf {
path_with_network(cache_dir, network).join("addressbook")
}
//---------------------------------------------------------------------------------------------------- Tests
@ -197,29 +234,21 @@ mod test {
(&*CUPRATE_CACHE_DIR, ""),
(&*CUPRATE_CONFIG_DIR, ""),
(&*CUPRATE_DATA_DIR, ""),
(&*CUPRATE_BLOCKCHAIN_DIR, ""),
(&*CUPRATE_TXPOOL_DIR, ""),
];
if cfg!(target_os = "windows") {
array[0].1 = r"AppData\Local\Cuprate";
array[1].1 = r"AppData\Roaming\Cuprate";
array[2].1 = r"AppData\Roaming\Cuprate";
array[3].1 = r"AppData\Roaming\Cuprate\blockchain";
array[4].1 = r"AppData\Roaming\Cuprate\txpool";
} else if cfg!(target_os = "macos") {
array[0].1 = "Library/Caches/Cuprate";
array[1].1 = "Library/Application Support/Cuprate";
array[2].1 = "Library/Application Support/Cuprate";
array[3].1 = "Library/Application Support/Cuprate/blockchain";
array[4].1 = "Library/Application Support/Cuprate/txpool";
} else {
// Assumes Linux.
array[0].1 = ".cache/cuprate";
array[1].1 = ".config/cuprate";
array[2].1 = ".local/share/cuprate";
array[3].1 = ".local/share/cuprate/blockchain";
array[4].1 = ".local/share/cuprate/txpool";
};
for (path, expected) in array {

View file

@ -5,6 +5,12 @@
//! into it's own crate.
//!
//! `#[no_std]` compatible.
// TODO: move to types crate.
use core::{
fmt::{Display, Formatter},
str::FromStr,
};
const MAINNET_NETWORK_ID: [u8; 16] = [
0x12, 0x30, 0xF1, 0x71, 0x61, 0x04, 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10,
@ -17,7 +23,8 @@ const STAGENET_NETWORK_ID: [u8; 16] = [
];
/// An enum representing every Monero network.
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy, Default, Ord, PartialOrd, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Network {
/// Mainnet
#[default]
@ -38,3 +45,28 @@ impl Network {
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParseNetworkError;
impl FromStr for Network {
type Err = ParseNetworkError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"mainnet" | "Mainnet" => Ok(Self::Mainnet),
"testnet" | "Testnet" => Ok(Self::Testnet),
"stagenet" | "Stagenet" => Ok(Self::Stagenet),
_ => Err(ParseNetworkError),
}
}
}
impl Display for Network {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::Mainnet => "mainnet",
Self::Testnet => "testnet",
Self::Stagenet => "stagenet",
})
}
}

View file

@ -76,14 +76,14 @@ macro_rules! epee_object {
// All this does is return the second (right) arg if present otherwise the left is returned.
(
@internal_try_right_then_left
$a:expr, $b:expr
$a:expr_2021, $b:expr_2021
) => {
$b
};
(
@internal_try_right_then_left
$a:expr,
$a:expr_2021,
) => {
$a
};
@ -122,7 +122,7 @@ macro_rules! epee_object {
// ------------------------------------------------------------------------ Entry Point
(
$obj:ident,
$($field: ident $(($alt_name: literal))?: $ty:ty $(as $ty_as:ty )? $(= $default:expr)? $(=> $read_fn:expr, $write_fn:expr, $should_write_fn:expr)?, )*
$($field: ident $(($alt_name: literal))?: $ty:ty $(as $ty_as:ty )? $(= $default:expr_2021)? $(=> $read_fn:expr_2021, $write_fn:expr_2021, $should_write_fn:expr_2021)?, )*
$(!flatten: $flat_field: ident: $flat_ty:ty ,)*
) => {

View file

@ -17,10 +17,12 @@
//! Monero network. Core Monero has 4 main addresses: IPv4, IPv6, Tor,
//! I2p. Currently this module only has IPv(4/6).
//!
use bytes::BufMut;
use cuprate_epee_encoding::EpeeObject;
use std::{hash::Hash, net, net::SocketAddr};
use bytes::BufMut;
use cuprate_epee_encoding::EpeeObject;
mod epee_builder;
use epee_builder::*;

View file

@ -1,9 +1,10 @@
use bytes::Buf;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use cuprate_epee_encoding::{epee_object, EpeeObjectBuilder};
use bytes::Buf;
use thiserror::Error;
use cuprate_epee_encoding::{epee_object, EpeeObjectBuilder};
use crate::NetworkAddress;
#[derive(Default)]
@ -77,7 +78,7 @@ impl From<NetworkAddress> for TaggedNetworkAddress {
SocketAddr::V4(addr) => Self {
ty: Some(1),
addr: Some(AllFieldsNetworkAddress {
m_ip: Some(u32::from_be_bytes(addr.ip().octets())),
m_ip: Some(u32::from_le_bytes(addr.ip().octets())),
m_port: Some(addr.port()),
addr: None,
}),
@ -112,7 +113,10 @@ epee_object!(
impl AllFieldsNetworkAddress {
fn try_into_network_address(self, ty: u8) -> Option<NetworkAddress> {
Some(match ty {
1 => NetworkAddress::from(SocketAddrV4::new(Ipv4Addr::from(self.m_ip?), self.m_port?)),
1 => NetworkAddress::from(SocketAddrV4::new(
Ipv4Addr::from(self.m_ip?.to_le_bytes()),
self.m_port?,
)),
2 => NetworkAddress::from(SocketAddrV6::new(
Ipv6Addr::from(self.addr?),
self.m_port?,

View file

@ -159,7 +159,7 @@ epee_object!(
current_blockchain_height: u64,
);
/// A request for Txs we are missing from our `TxPool`
/// A request for txs we are missing from an incoming block.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FluffyMissingTransactionsRequest {
/// The Block we are missing the Txs in

View file

@ -23,7 +23,7 @@ indexmap = { workspace = true, features = ["std"] }
rand = { workspace = true, features = ["std", "std_rng"] }
borsh = { workspace = true, features = ["derive", "std"]}
borsh = { workspace = true, features = ["derive", "std"] }
[dev-dependencies]
cuprate-test-utils = { workspace = true }

View file

@ -423,7 +423,8 @@ impl<Z: BorshNetworkZone> Service<AddressBookRequest<Z>> for AddressBook<Z> {
AddressBookRequest::PeerlistSize
| AddressBookRequest::ConnectionCount
| AddressBookRequest::SetBan(_)
| AddressBookRequest::GetBans => {
| AddressBookRequest::GetBans
| AddressBookRequest::ConnectionInfo => {
todo!("finish https://github.com/Cuprate/cuprate/pull/297")
}
};

View file

@ -15,7 +15,7 @@ fn test_cfg() -> AddressBookConfig {
AddressBookConfig {
max_white_list_length: 100,
max_gray_list_length: 500,
peer_store_file: PathBuf::new(),
peer_store_directory: PathBuf::new(),
peer_save_period: Duration::from_secs(60),
}
}

View file

@ -29,8 +29,8 @@ pub struct AddressBookConfig {
///
/// Gray peers are peers we are yet to make a connection to.
pub max_gray_list_length: usize,
/// The location to store the address book.
pub peer_store_file: PathBuf,
/// The location to store the peer store files.
pub peer_store_directory: PathBuf,
/// The amount of time between saving the address book to disk.
pub peer_save_period: Duration,
}
@ -63,11 +63,6 @@ pub enum AddressBookError {
pub async fn init_address_book<Z: BorshNetworkZone>(
cfg: AddressBookConfig,
) -> Result<book::AddressBook<Z>, std::io::Error> {
tracing::info!(
"Loading peers from file: {} ",
cfg.peer_store_file.display()
);
let (white_list, gray_list) = match store::read_peers_from_disk::<Z>(&cfg).await {
Ok(res) => res,
Err(e) if e.kind() == ErrorKind::NotFound => (vec![], vec![]),

View file

@ -39,7 +39,9 @@ pub(crate) fn save_peers_to_disk<Z: BorshNetworkZone>(
})
.unwrap();
let file = cfg.peer_store_file.clone();
let file = cfg
.peer_store_directory
.join(format!("{}_p2p_state", Z::NAME));
spawn_blocking(move || fs::write(&file, &data))
}
@ -52,7 +54,12 @@ pub(crate) async fn read_peers_from_disk<Z: BorshNetworkZone>(
),
std::io::Error,
> {
let file = cfg.peer_store_file.clone();
let file = cfg
.peer_store_directory
.join(format!("{}_p2p_state", Z::NAME));
tracing::info!("Loading peers from file: {} ", file.display());
let data = spawn_blocking(move || fs::read(file)).await.unwrap()?;
let de_ser: DeserPeerDataV1<Z::Addr> = from_slice(&data)?;

View file

@ -157,7 +157,7 @@ pub struct BufferSinkSend<'a, T> {
item: Option<T>,
}
impl<'a, T> Future for BufferSinkSend<'a, T> {
impl<T> Future for BufferSinkSend<'_, T> {
type Output = Result<(), BufferError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
@ -183,7 +183,7 @@ pub struct BufferSinkReady<'a, T> {
size_needed: usize,
}
impl<'a, T> Future for BufferSinkReady<'a, T> {
impl<T> Future for BufferSinkReady<'_, T> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {

13
p2p/bucket/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "cuprate-p2p-bucket"
version = "0.1.0"
edition = "2021"
license = "MIT"
authors = ["SyntheticBird"]
[dependencies]
arrayvec = { workspace = true }
rand = { workspace = true, features = ["std", "std_rng"]}
[lints]
workspace = true

172
p2p/bucket/src/lib.rs Normal file
View file

@ -0,0 +1,172 @@
//! Bucket data structure
//!
//! A collection data structure that discriminates its unique items and place them into "buckets".
//!
//! The item must implement the [`Bucketable`] trait that defines how to create the discriminant
//! from the item type. The data structure will internally contain any item into "buckets" or vectors
//! of sized capacity `N` that regroup all the stored items with this specific discriminant.
//!
//! A practical example of this data structure is for storing `N` amount of IP discriminated by their subnets.
//! You can store in each "buckets" corresponding to a `/16` subnet up to `N` IPs of that subnet.
//!
//! # Example
//!
//! ```
//! use cuprate_p2p_bucket::Bucket;
//! use std::net::Ipv4Addr;
//!
//! // Create a new bucket that can store at most 2 IPs in a particular `/16` subnet.
//! let mut bucket = Bucket::<2,Ipv4Addr>::new();
//!
//! // Fulfill the `96.96.0.0/16` bucket.
//! bucket.push("96.96.0.1".parse().unwrap());
//! bucket.push("96.96.0.2".parse().unwrap());
//! assert_eq!(2, bucket.len());
//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap());
//!
//! // Push a new IP from another subnet
//! bucket.push("127.0.0.1".parse().unwrap());
//! assert_eq!(3, bucket.len());
//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap());
//! assert_eq!(1, bucket.len_bucket(&[127_u8,0_u8]).unwrap());
//!
//! // Attempting to push a new IP within `96.96.0.0/16` bucket will return the IP back
//! // as this subnet is already full.
//! let pushed = bucket.push("96.96.0.3".parse().unwrap());
//! assert!(pushed.is_some());
//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap());
//!
//! ```
use arrayvec::{ArrayVec, CapacityError};
use rand::random;
use std::{collections::BTreeMap, net::Ipv4Addr};
/// A discriminant that can be computed from the type.
pub trait Bucketable: Sized + Eq + Clone {
/// The type of the discriminant being used in the Binary tree.
type Discriminant: Ord + AsRef<[u8]>;
/// Method that can compute the discriminant from the item.
fn discriminant(&self) -> Self::Discriminant;
}
/// A collection data structure discriminating its unique items
/// with a specified method. Limiting the amount of items stored
/// with that discriminant to the const `N`.
pub struct Bucket<const N: usize, I: Bucketable> {
/// The storage of the bucket
storage: BTreeMap<I::Discriminant, ArrayVec<I, N>>,
}
impl<const N: usize, I: Bucketable> Bucket<N, I> {
/// Create a new Bucket
pub const fn new() -> Self {
Self {
storage: BTreeMap::new(),
}
}
/// Push a new element into the Bucket
///
/// Will internally create a new vector for each new discriminant being
/// generated from an item.
///
/// This function WILL NOT push the element if it already exists.
///
/// Return `None` if the item has been pushed or ignored. `Some(I)` if
/// the vector is full.
///
/// # Example
///
/// ```
/// use cuprate_p2p_bucket::Bucket;
/// use std::net::Ipv4Addr;
///
/// let mut bucket = Bucket::<8,Ipv4Addr>::new();
///
/// // Push a first IP address.
/// bucket.push("127.0.0.1".parse().unwrap());
/// assert_eq!(1, bucket.len());
///
/// // Push the same IP address a second time.
/// bucket.push("127.0.0.1".parse().unwrap());
/// assert_eq!(1, bucket.len());
/// ```
pub fn push(&mut self, item: I) -> Option<I> {
let discriminant = item.discriminant();
if let Some(vec) = self.storage.get_mut(&discriminant) {
// Push the item if it doesn't exist.
if !vec.contains(&item) {
return vec.try_push(item).err().map(CapacityError::element);
}
} else {
// Initialize the vector if not found.
let mut vec = ArrayVec::<I, N>::new();
vec.push(item);
self.storage.insert(discriminant, vec);
}
None
}
/// Will attempt to remove an item from the bucket.
pub fn remove(&mut self, item: &I) -> Option<I> {
self.storage.get_mut(&item.discriminant()).and_then(|vec| {
vec.iter()
.enumerate()
.find_map(|(i, v)| (item == v).then_some(i))
.map(|index| vec.swap_remove(index))
})
}
/// Return the number of item stored within the storage
pub fn len(&self) -> usize {
self.storage.values().map(ArrayVec::len).sum()
}
/// Return the number of item stored with a specific discriminant.
///
/// This method returns None if the bucket with this discriminant
/// doesn't exist.
pub fn len_bucket(&self, discriminant: &I::Discriminant) -> Option<usize> {
self.storage.get(discriminant).map(ArrayVec::len)
}
/// Return `true` if the storage contains no items
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Return a reference to an item chosen at random.
///
/// Repeated use of this function will provide a normal distribution of
/// items based on their discriminants.
pub fn get_random(&mut self) -> Option<&I> {
// Get the total amount of discriminants to explore.
let len = self.storage.len();
// Get a random bucket.
let (_, vec) = self.storage.iter().nth(random::<usize>() / len).unwrap();
// Return a reference chose at random.
vec.get(random::<usize>() / vec.len())
}
}
impl<const N: usize, I: Bucketable> Default for Bucket<N, I> {
fn default() -> Self {
Self::new()
}
}
impl Bucketable for Ipv4Addr {
/// We are discriminating by `/16` subnets.
type Discriminant = [u8; 2];
fn discriminant(&self) -> Self::Discriminant {
[self.octets()[0], self.octets()[1]]
}
}

View file

@ -12,6 +12,7 @@ use crate::{
OutboundPeer, State,
};
#[expect(clippy::type_complexity)]
pub(crate) fn mock_discover_svc<Req: Send + 'static>() -> (
impl Stream<
Item = Result<

View file

@ -10,9 +10,10 @@ default = ["borsh"]
borsh = ["dep:borsh", "cuprate-pruning/borsh"]
[dependencies]
cuprate-helper = { workspace = true, features = ["asynch"], default-features = false }
cuprate-wire = { workspace = true, features = ["tracing"] }
cuprate-helper = { workspace = true, features = ["asynch"], default-features = false }
cuprate-wire = { workspace = true, features = ["tracing"] }
cuprate-pruning = { workspace = true }
cuprate-types = { workspace = true }
tokio = { workspace = true, features = ["net", "sync", "macros", "time", "rt", "rt-multi-thread"]}
tokio-util = { workspace = true, features = ["codec"] }

View file

@ -1,23 +0,0 @@
//! Data structures related to bans.
use std::time::{Duration, Instant};
use crate::NetZoneAddress;
/// Data within [`crate::services::AddressBookRequest::SetBan`].
pub struct SetBan<A: NetZoneAddress> {
/// Address of the peer.
pub address: A,
/// - If [`Some`], how long this peer should be banned for
/// - If [`None`], the peer will be unbanned
pub ban: Option<Duration>,
}
/// Data within [`crate::services::AddressBookResponse::GetBans`].
pub struct BanState<A: NetZoneAddress> {
/// Address of the peer.
pub address: A,
/// - If [`Some`], the peer is banned until this [`Instant`]
/// - If [`None`], the peer is not currently banned
pub unban_instant: Option<Instant>,
}

View file

@ -27,9 +27,11 @@ mod connector;
pub mod handshaker;
mod request_handler;
mod timeout_monitor;
mod weak;
pub use connector::{ConnectRequest, Connector};
pub use handshaker::{DoHandshakeRequest, HandshakeError, HandshakerBuilder};
pub use weak::WeakClient;
/// An internal identifier for a given peer, will be their address if known
/// or a random u128 if not.
@ -128,6 +130,17 @@ impl<Z: NetworkZone> Client<Z> {
}
.into()
}
/// Create a [`WeakClient`] for this [`Client`].
pub fn downgrade(&self) -> WeakClient<Z> {
WeakClient {
info: self.info.clone(),
connection_tx: self.connection_tx.downgrade(),
semaphore: self.semaphore.clone(),
permit: None,
error: self.error.clone(),
}
}
}
impl<Z: NetworkZone> Service<PeerRequest> for Client<Z> {

View file

@ -111,7 +111,8 @@ impl<N: NetworkZone> Service<AddressBookRequest<N>> for DummyAddressBook {
AddressBookRequest::PeerlistSize
| AddressBookRequest::ConnectionCount
| AddressBookRequest::SetBan(_)
| AddressBookRequest::GetBans => {
| AddressBookRequest::GetBans
| AddressBookRequest::ConnectionInfo => {
todo!("finish https://github.com/Cuprate/cuprate/pull/297")
}
}))

View file

@ -0,0 +1,114 @@
use std::task::{ready, Context, Poll};
use futures::channel::oneshot;
use tokio::sync::{mpsc, OwnedSemaphorePermit};
use tokio_util::sync::PollSemaphore;
use tower::Service;
use cuprate_helper::asynch::InfallibleOneshotReceiver;
use crate::{
client::{connection, PeerInformation},
NetworkZone, PeerError, PeerRequest, PeerResponse, SharedError,
};
/// A weak handle to a [`Client`](super::Client).
///
/// When this is dropped the peer will not be disconnected.
pub struct WeakClient<N: NetworkZone> {
/// Information on the connected peer.
pub info: PeerInformation<N::Addr>,
/// The channel to the [`Connection`](connection::Connection) task.
pub(super) connection_tx: mpsc::WeakSender<connection::ConnectionTaskRequest>,
/// The semaphore that limits the requests sent to the peer.
pub(super) semaphore: PollSemaphore,
/// A permit for the semaphore, will be [`Some`] after `poll_ready` returns ready.
pub(super) permit: Option<OwnedSemaphorePermit>,
/// The error slot shared between the [`Client`] and [`Connection`](connection::Connection).
pub(super) error: SharedError<PeerError>,
}
impl<N: NetworkZone> WeakClient<N> {
/// Internal function to set an error on the [`SharedError`].
fn set_err(&self, err: PeerError) -> tower::BoxError {
let err_str = err.to_string();
match self.error.try_insert_err(err) {
Ok(()) => err_str,
Err(e) => e.to_string(),
}
.into()
}
}
impl<Z: NetworkZone> Service<PeerRequest> for WeakClient<Z> {
type Response = PeerResponse;
type Error = tower::BoxError;
type Future = InfallibleOneshotReceiver<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if let Some(err) = self.error.try_get_err() {
return Poll::Ready(Err(err.to_string().into()));
}
if self.connection_tx.strong_count() == 0 {
let err = self.set_err(PeerError::ClientChannelClosed);
return Poll::Ready(Err(err));
}
if self.permit.is_some() {
return Poll::Ready(Ok(()));
}
let permit = ready!(self.semaphore.poll_acquire(cx))
.expect("Client semaphore should not be closed!");
self.permit = Some(permit);
Poll::Ready(Ok(()))
}
#[expect(clippy::significant_drop_tightening)]
fn call(&mut self, request: PeerRequest) -> Self::Future {
let permit = self
.permit
.take()
.expect("poll_ready did not return ready before call to call");
let (tx, rx) = oneshot::channel();
let req = connection::ConnectionTaskRequest {
response_channel: tx,
request,
permit: Some(permit),
};
match self.connection_tx.upgrade() {
None => {
self.set_err(PeerError::ClientChannelClosed);
let resp = Err(PeerError::ClientChannelClosed.into());
drop(req.response_channel.send(resp));
}
Some(sender) => {
if let Err(e) = sender.try_send(req) {
// The connection task could have closed between a call to `poll_ready` and the call to
// `call`, which means if we don't handle the error here the receiver would panic.
use mpsc::error::TrySendError;
match e {
TrySendError::Closed(req) | TrySendError::Full(req) => {
self.set_err(PeerError::ClientChannelClosed);
let resp = Err(PeerError::ClientChannelClosed.into());
drop(req.response_channel.send(resp));
}
}
}
}
}
rx.into()
}
}

View file

@ -75,7 +75,6 @@ use cuprate_wire::{
NetworkAddress,
};
pub mod ban;
pub mod client;
mod constants;
pub mod error;
@ -83,6 +82,7 @@ pub mod handles;
mod network_zones;
pub mod protocol;
pub mod services;
pub mod types;
pub use error::*;
pub use network_zones::{ClearNet, ClearNetServerCfg};
@ -121,7 +121,6 @@ pub trait NetZoneAddress:
///
/// - TODO: IP zone banning?
/// - TODO: rename this to Host.
type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static;
/// Changes the port of this address to `port`.

View file

@ -116,6 +116,7 @@ pub enum ProtocolResponse {
GetChain(ChainResponse),
NewFluffyBlock(NewFluffyBlock),
NewTransactions(NewTransactions),
FluffyMissingTransactionsRequest(FluffyMissingTransactionsRequest),
NA,
}
@ -139,6 +140,9 @@ impl PeerResponse {
ProtocolResponse::GetChain(_) => MessageID::GetChain,
ProtocolResponse::NewFluffyBlock(_) => MessageID::NewBlock,
ProtocolResponse::NewTransactions(_) => MessageID::NewFluffyBlock,
ProtocolResponse::FluffyMissingTransactionsRequest(_) => {
MessageID::FluffyMissingTxs
}
ProtocolResponse::NA => return None,
},

View file

@ -71,6 +71,9 @@ impl TryFrom<ProtocolResponse> for ProtocolMessage {
ProtocolResponse::NewFluffyBlock(val) => Self::NewFluffyBlock(val),
ProtocolResponse::GetChain(val) => Self::ChainEntryResponse(val),
ProtocolResponse::GetObjects(val) => Self::GetObjectsResponse(val),
ProtocolResponse::FluffyMissingTransactionsRequest(val) => {
Self::FluffyMissingTransactionsRequest(val)
}
ProtocolResponse::NA => return Err(MessageConversionError),
})
}

View file

@ -4,9 +4,9 @@ use cuprate_pruning::{PruningError, PruningSeed};
use cuprate_wire::{CoreSyncData, PeerListEntryBase};
use crate::{
ban::{BanState, SetBan},
client::InternalPeerID,
handles::ConnectionHandle,
types::{BanState, ConnectionInfo, SetBan},
NetZoneAddress, NetworkAddressIncorrectZone, NetworkZone,
};
@ -118,6 +118,9 @@ pub enum AddressBookRequest<Z: NetworkZone> {
/// Get the amount of white & grey peers.
PeerlistSize,
/// Get information on all connections.
ConnectionInfo,
/// Get the amount of incoming & outgoing connections.
ConnectionCount,
@ -152,6 +155,9 @@ pub enum AddressBookResponse<Z: NetworkZone> {
/// Response to [`AddressBookRequest::PeerlistSize`].
PeerlistSize { white: usize, grey: usize },
/// Response to [`AddressBookRequest::ConnectionInfo`].
ConnectionInfo(Vec<ConnectionInfo<Z::Addr>>),
/// Response to [`AddressBookRequest::ConnectionCount`].
ConnectionCount { incoming: usize, outgoing: usize },

Some files were not shown because too many files have changed in this diff Show more