Compare commits

...

8 commits

Author SHA1 Message Date
hinto.janai
79b03e386a
Merge branch 'main' into json
Some checks failed
Audit / audit (push) Has been cancelled
Deny / audit (push) Has been cancelled
2024-11-01 20:18:25 -04:00
hinto-janai
372cab24d7
cuprated: internal signatures required for RPC pt. 2 (#320)
Some checks failed
CI / ci (macos-latest, stable, bash) (push) Has been cancelled
Audit / audit (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / typo (push) Has been cancelled
CI / ci (ubuntu-latest, stable, bash) (push) Has been cancelled
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Has been cancelled
Deny / audit (push) Has been cancelled
Doc / build (push) Has been cancelled
Doc / deploy (push) Has been cancelled
* apply diffs

* clippy

* fix tests

* rpc: fix tests

* remove `BlockchainManagerRequest::Overview`

* cuprated/p2p: fix `ConnectionInfo`

* move `CalculatePow`

* remove `AddAuxPow`

* move `Spans` and `NextNeededPruningSeed`

* factor types into `cuprate-types`

* scope cargo features

* fix/doc type serde

* Update binaries/cuprated/src/rpc/request/address_book.rs

Co-authored-by: Boog900 <boog900@tutanota.com>

* Update binaries/cuprated/src/rpc/request/blockchain_context.rs

Co-authored-by: Boog900 <boog900@tutanota.com>

* Update binaries/cuprated/src/rpc/request/blockchain_manager.rs

Co-authored-by: Boog900 <boog900@tutanota.com>

* fmt

* txpool: collapse `TxEntry`

* `ConnectionId`

* fix import

* fix bin

---------

Co-authored-by: Boog900 <boog900@tutanota.com>
2024-11-01 22:25:55 +00:00
44981f2b24
CI: add cargo hack (#170)
* add workflow

* fix errors

* fix workflow

* install dependencies

* fix more errors

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

Co-authored-by: hinto-janai <hinto.janai@protonmail.com>

* fix hack + enable it for cuprate-database

* move hack to main CI

* fix docs

* fix ci formatting

* fix txpool tests

* fix CONTRIBUTING.md formatting

* service -> tower::Service

* review fixes

* review fixes

* fix CI

---------

Co-authored-by: hinto-janai <hinto.janai@protonmail.com>
2024-11-01 20:22:14 +00:00
b57ee2f4cf
cuprated: txpool (#312)
Some checks failed
Audit / audit (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / typo (push) Has been cancelled
CI / ci (macos-latest, stable, bash) (push) Has been cancelled
CI / ci (ubuntu-latest, stable, bash) (push) Has been cancelled
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Has been cancelled
Deny / audit (push) Has been cancelled
Doc / build (push) Has been cancelled
Doc / deploy (push) Has been cancelled
* init dandelion integration

* add dandelion start function

* finish incoming tx handler

* Add tx blob hash table

* Add missing txpool requests

* handle duplicate stem txs

* check txpool on incoming block

* add request to remove tx in new blocks from the pool

* tell the txpool about incoming blocks

* fix merge

* typos

* remove blockchain height from txpool

* add function to start the pool

* add cross network address

* pre-review changes

* fix CI

* review fixes

* review fixes

* abort on DB error

* fix clippy
2024-10-29 15:30:51 +00:00
SyntheticBird
63216aecae
workspace: Defines cuprate members as workspace dependencies (#326)
Some checks failed
Deny / audit (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / typo (push) Has been cancelled
CI / ci (macos-latest, stable, bash) (push) Has been cancelled
Audit / audit (push) Has been cancelled
CI / ci (ubuntu-latest, stable, bash) (push) Has been cancelled
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Has been cancelled
Doc / build (push) Has been cancelled
Doc / deploy (push) Has been cancelled
Defines cuprate members as workspace dependencies

- Defines cuprate members as workspace dependencies
- Changed all `path` import into `workspace = true`

Co-authored-by: Boog900 <boog900@tutanota.com>
2024-10-24 23:12:30 +01:00
SyntheticBird
b8e2d00af4
storage: Add common amounts commitment lookup table (#323)
Add common ammounts commitment lookup table

- Implements `compute_zero_commitment` function in `cuprate-helper::crypto` module.
- Added test that compare the function output with the correct calculation.
- Use of a constant-time algorithm for the lookup table.
- Added according documentation
2024-10-24 22:10:33 +01:00
hinto-janai
4b350e897d
consensus-context: enable workspace lints (#321)
enable lints, fix 1.82 clippy
2024-10-22 17:35:54 +01:00
SyntheticBird
978d72b6c1
Move consensus context service into a subcrate. (#318)
Some checks failed
Architecture mdBook / build (push) Has been cancelled
Audit / audit (push) Has been cancelled
CI / typo (push) Has been cancelled
CI / ci (macos-latest, stable, bash) (push) Has been cancelled
CI / ci (ubuntu-latest, stable, bash) (push) Has been cancelled
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Has been cancelled
Deny / audit (push) Has been cancelled
CI / fmt (push) Has been cancelled
Doc / build (push) Has been cancelled
Doc / deploy (push) Has been cancelled
Co-authored-by: Boog900 <boog900@tutanota.com>
2024-10-17 00:17:58 +01:00
120 changed files with 2262 additions and 780 deletions

View file

@ -133,7 +133,12 @@ jobs:
- name: Test - name: Test
run: | run: |
cargo test --all-features --workspace cargo test --all-features --workspace
cargo test --package cuprate-blockchain --no-default-features --features redb --features service cargo test --package cuprate-blockchain --no-default-features --features redb
- name: Hack Check
run: |
cargo install cargo-hack --locked
cargo hack --workspace check --feature-powerset --no-dev-deps
# TODO: upload binaries with `actions/upload-artifact@v3` # TODO: upload binaries with `actions/upload-artifact@v3`
- name: Build - name: Build

View file

@ -120,12 +120,15 @@ Before pushing your code, please run the following at the root of the repository
After that, ensure all other CI passes by running: After that, ensure all other CI passes by running:
| Command | Does what | | Command | Does what |
|------------------------------------------------------------------------|-----------| |------------------------------------------------------------------------|-------------------------------------------------------------------------|
| `RUSTDOCFLAGS='-D warnings' cargo doc --workspace --all-features` | Checks documentation is OK | `RUSTDOCFLAGS='-D warnings' cargo doc --workspace --all-features` | Checks documentation is OK |
| `cargo clippy --workspace --all-features --all-targets -- -D warnings` | Checks clippy lints are satisfied | `cargo clippy --workspace --all-features --all-targets -- -D warnings` | Checks clippy lints are satisfied |
| `cargo test --all-features --workspace` | Runs all tests | `cargo test --all-features --workspace` | Runs all tests |
| `cargo build --all-features --all-targets --workspace` | Builds all code | `cargo build --all-features --all-targets --workspace` | Builds all code |
| `cargo hack --workspace check --feature-powerset --no-dev-deps` | Uses `cargo hack` to check our crates build with different features set |
`cargo hack` can be installed with `cargo` from: https://github.com/taiki-e/cargo-hack.
**Note: in order for some tests to work, you will need to place a [`monerod`](https://www.getmonero.org/downloads/) binary at the root of the repository.** **Note: in order for some tests to work, you will need to place a [`monerod`](https://www.getmonero.org/downloads/) binary at the root of the repository.**

56
Cargo.lock generated
View file

@ -56,6 +56,18 @@ version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.5" version = "0.3.5"
@ -238,6 +250,19 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "blake3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -403,6 +428,12 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -575,6 +606,7 @@ name = "cuprate-consensus"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cuprate-consensus-context",
"cuprate-consensus-rules", "cuprate-consensus-rules",
"cuprate-helper", "cuprate-helper",
"cuprate-test-utils", "cuprate-test-utils",
@ -587,12 +619,30 @@ dependencies = [
"proptest", "proptest",
"proptest-derive", "proptest-derive",
"rand", "rand",
"randomx-rs",
"rayon", "rayon",
"thiserror", "thiserror",
"thread_local", "thread_local",
"tokio", "tokio",
"tokio-test", "tokio-test",
"tower 0.5.1",
"tracing",
]
[[package]]
name = "cuprate-consensus-context"
version = "0.1.0"
dependencies = [
"cuprate-consensus-rules",
"cuprate-helper",
"cuprate-types",
"futures",
"hex",
"monero-serai",
"randomx-rs",
"rayon",
"thiserror",
"thread_local",
"tokio",
"tokio-util", "tokio-util",
"tower 0.5.1", "tower 0.5.1",
"tracing", "tracing",
@ -704,6 +754,7 @@ dependencies = [
"clap", "clap",
"cuprate-blockchain", "cuprate-blockchain",
"cuprate-consensus", "cuprate-consensus",
"cuprate-consensus-context",
"cuprate-consensus-rules", "cuprate-consensus-rules",
"cuprate-helper", "cuprate-helper",
"cuprate-types", "cuprate-types",
@ -814,6 +865,7 @@ dependencies = [
"cuprate-helper", "cuprate-helper",
"cuprate-pruning", "cuprate-pruning",
"cuprate-test-utils", "cuprate-test-utils",
"cuprate-types",
"cuprate-wire", "cuprate-wire",
"futures", "futures",
"hex", "hex",
@ -899,6 +951,7 @@ name = "cuprate-txpool"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"blake3",
"bytemuck", "bytemuck",
"cuprate-database", "cuprate-database",
"cuprate-database-service", "cuprate-database-service",
@ -972,6 +1025,7 @@ dependencies = [
"cuprate-async-buffer", "cuprate-async-buffer",
"cuprate-blockchain", "cuprate-blockchain",
"cuprate-consensus", "cuprate-consensus",
"cuprate-consensus-context",
"cuprate-consensus-rules", "cuprate-consensus-rules",
"cuprate-constants", "cuprate-constants",
"cuprate-cryptonight", "cuprate-cryptonight",

View file

@ -5,6 +5,7 @@ members = [
"binaries/cuprated", "binaries/cuprated",
"constants", "constants",
"consensus", "consensus",
"consensus/context",
"consensus/fast-sync", "consensus/fast-sync",
"consensus/rules", "consensus/rules",
"cryptonight", "cryptonight",
@ -49,9 +50,39 @@ opt-level = 1
opt-level = 3 opt-level = 3
[workspace.dependencies] [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}
# External dependencies
anyhow = { version = "1.0.89", default-features = false } anyhow = { version = "1.0.89", default-features = false }
async-trait = { version = "0.1.82", default-features = false } async-trait = { version = "0.1.82", default-features = false }
bitflags = { version = "2.6.0", default-features = false } bitflags = { version = "2.6.0", default-features = false }
blake3 = { version = "1", default-features = false }
borsh = { version = "1.5.1", default-features = false } borsh = { version = "1.5.1", default-features = false }
bytemuck = { version = "1.18.0", default-features = false } bytemuck = { version = "1.18.0", default-features = false }
bytes = { version = "1.7.2", default-features = false } bytes = { version = "1.7.2", default-features = false }
@ -322,4 +353,4 @@ non_camel_case_types = "deny"
# unused_results = "deny" # unused_results = "deny"
# non_exhaustive_omitted_patterns = "deny" # non_exhaustive_omitted_patterns = "deny"
# missing_docs = "deny" # missing_docs = "deny"
# missing_copy_implementations = "deny" # missing_copy_implementations = "deny"

View file

@ -9,31 +9,32 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/binaries/cuprated"
[dependencies] [dependencies]
# TODO: after v1.0.0, remove unneeded dependencies. # TODO: after v1.0.0, remove unneeded dependencies.
cuprate-consensus = { path = "../../consensus" } cuprate-consensus = { workspace = true }
cuprate-fast-sync = { path = "../../consensus/fast-sync" } cuprate-fast-sync = { workspace = true }
cuprate-consensus-rules = { path = "../../consensus/rules" } cuprate-consensus-context = { workspace = true }
cuprate-constants = { path = "../../constants", features = ["build", "rpc"] } cuprate-consensus-rules = { workspace = true }
cuprate-cryptonight = { path = "../../cryptonight" } cuprate-constants = { workspace = true, features = ["build", "rpc"] }
cuprate-helper = { path = "../../helper" } cuprate-cryptonight = { workspace = true }
cuprate-epee-encoding = { path = "../../net/epee-encoding" } cuprate-helper = { workspace = true }
cuprate-fixed-bytes = { path = "../../net/fixed-bytes" } cuprate-epee-encoding = { workspace = true }
cuprate-levin = { path = "../../net/levin" } cuprate-fixed-bytes = { workspace = true }
cuprate-wire = { path = "../../net/wire" } cuprate-levin = { workspace = true }
cuprate-p2p = { path = "../../p2p/p2p" } cuprate-wire = { workspace = true }
cuprate-p2p-core = { path = "../../p2p/p2p-core" } cuprate-p2p = { workspace = true }
cuprate-dandelion-tower = { path = "../../p2p/dandelion-tower" } cuprate-p2p-core = { workspace = true }
cuprate-async-buffer = { path = "../../p2p/async-buffer" } cuprate-dandelion-tower = { workspace = true, features = ["txpool"] }
cuprate-address-book = { path = "../../p2p/address-book" } cuprate-async-buffer = { workspace = true }
cuprate-blockchain = { path = "../../storage/blockchain", features = ["service"] } cuprate-address-book = { workspace = true }
cuprate-database-service = { path = "../../storage/service" } cuprate-blockchain = { workspace = true }
cuprate-txpool = { path = "../../storage/txpool" } cuprate-database-service = { workspace = true }
cuprate-database = { path = "../../storage/database" } cuprate-txpool = { workspace = true }
cuprate-pruning = { path = "../../pruning" } cuprate-database = { workspace = true }
cuprate-test-utils = { path = "../../test-utils" } cuprate-pruning = { workspace = true }
cuprate-types = { path = "../../types" } cuprate-test-utils = { workspace = true }
cuprate-json-rpc = { path = "../../rpc/json-rpc" } cuprate-types = { workspace = true }
cuprate-rpc-interface = { path = "../../rpc/interface" } cuprate-json-rpc = { workspace = true }
cuprate-rpc-types = { path = "../../rpc/types" } cuprate-rpc-interface = { workspace = true }
cuprate-rpc-types = { workspace = true }
# TODO: after v1.0.0, remove unneeded dependencies. # TODO: after v1.0.0, remove unneeded dependencies.
anyhow = { workspace = true } anyhow = { workspace = true }

View file

@ -25,7 +25,7 @@ mod manager;
mod syncer; mod syncer;
mod types; mod types;
use types::{ pub use types::{
ConcreteBlockVerifierService, ConcreteTxVerifierService, ConsensusBlockchainReadHandle, ConcreteBlockVerifierService, ConcreteTxVerifierService, ConsensusBlockchainReadHandle,
}; };

View file

@ -8,17 +8,16 @@ use std::{
}; };
use monero_serai::{block::Block, transaction::Transaction}; use monero_serai::{block::Block, transaction::Transaction};
use rayon::prelude::*;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use cuprate_blockchain::service::BlockchainReadHandle; use cuprate_blockchain::service::BlockchainReadHandle;
use cuprate_consensus::transactions::new_tx_verification_data; use cuprate_consensus::transactions::new_tx_verification_data;
use cuprate_helper::cast::usize_to_u64; use cuprate_txpool::service::{
use cuprate_types::{ interface::{TxpoolReadRequest, TxpoolReadResponse},
blockchain::{BlockchainReadRequest, BlockchainResponse}, TxpoolReadHandle,
Chain,
}; };
use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
use crate::{ use crate::{
blockchain::manager::{BlockchainManagerCommand, IncomingBlockOk}, blockchain::manager::{BlockchainManagerCommand, IncomingBlockOk},
@ -38,7 +37,7 @@ pub enum IncomingBlockError {
/// ///
/// The inner values are the block hash and the indexes of the missing txs in the block. /// The inner values are the block hash and the indexes of the missing txs in the block.
#[error("Unknown transactions in block.")] #[error("Unknown transactions in block.")]
UnknownTransactions([u8; 32], Vec<u64>), UnknownTransactions([u8; 32], Vec<usize>),
/// We are missing the block's parent. /// We are missing the block's parent.
#[error("The block has an unknown parent.")] #[error("The block has an unknown parent.")]
Orphan, Orphan,
@ -59,8 +58,9 @@ pub enum IncomingBlockError {
/// - the block's parent is unknown /// - the block's parent is unknown
pub async fn handle_incoming_block( pub async fn handle_incoming_block(
block: Block, block: Block,
given_txs: Vec<Transaction>, mut given_txs: HashMap<[u8; 32], Transaction>,
blockchain_read_handle: &mut BlockchainReadHandle, blockchain_read_handle: &mut BlockchainReadHandle,
txpool_read_handle: &mut TxpoolReadHandle,
) -> Result<IncomingBlockOk, IncomingBlockError> { ) -> Result<IncomingBlockOk, IncomingBlockError> {
/// A [`HashSet`] of block hashes that the blockchain manager is currently handling. /// A [`HashSet`] of block hashes that the blockchain manager is currently handling.
/// ///
@ -72,7 +72,12 @@ pub async fn handle_incoming_block(
/// which are also more expensive than `Mutex`s. /// which are also more expensive than `Mutex`s.
static BLOCKS_BEING_HANDLED: LazyLock<Mutex<HashSet<[u8; 32]>>> = static BLOCKS_BEING_HANDLED: LazyLock<Mutex<HashSet<[u8; 32]>>> =
LazyLock::new(|| Mutex::new(HashSet::new())); LazyLock::new(|| Mutex::new(HashSet::new()));
// FIXME: we should look in the tx-pool for txs when that is ready.
if given_txs.len() > block.transactions.len() {
return Err(IncomingBlockError::InvalidBlock(anyhow::anyhow!(
"Too many transactions given for block"
)));
}
if !block_exists(block.header.previous, blockchain_read_handle) if !block_exists(block.header.previous, blockchain_read_handle)
.await .await
@ -90,23 +95,36 @@ pub async fn handle_incoming_block(
return Ok(IncomingBlockOk::AlreadyHave); return Ok(IncomingBlockOk::AlreadyHave);
} }
// TODO: remove this when we have a working tx-pool. let TxpoolReadResponse::TxsForBlock { mut txs, missing } = txpool_read_handle
if given_txs.len() != block.transactions.len() { .ready()
return Err(IncomingBlockError::UnknownTransactions( .await
block_hash, .expect(PANIC_CRITICAL_SERVICE_ERROR)
(0..usize_to_u64(block.transactions.len())).collect(), .call(TxpoolReadRequest::TxsForBlock(block.transactions.clone()))
)); .await
} .expect(PANIC_CRITICAL_SERVICE_ERROR)
else {
unreachable!()
};
// TODO: check we actually got given the right txs. if !missing.is_empty() {
let prepped_txs = given_txs let needed_hashes = missing.iter().map(|index| block.transactions[*index]);
.into_par_iter()
.map(|tx| { for needed_hash in needed_hashes {
let tx = new_tx_verification_data(tx)?; let Some(tx) = given_txs.remove(&needed_hash) else {
Ok((tx.tx_hash, tx)) // We return back the indexes of all txs missing from our pool, not taking into account the txs
}) // that were given with the block, as these txs will be dropped. It is not worth it to try to add
.collect::<Result<_, anyhow::Error>>() // these txs to the pool as this will only happen with a misbehaving peer or if the txpool reaches
.map_err(IncomingBlockError::InvalidBlock)?; // the size limit.
return Err(IncomingBlockError::UnknownTransactions(block_hash, missing));
};
txs.insert(
needed_hash,
new_tx_verification_data(tx)
.map_err(|e| IncomingBlockError::InvalidBlock(e.into()))?,
);
}
}
let Some(incoming_block_tx) = COMMAND_TX.get() else { let Some(incoming_block_tx) = COMMAND_TX.get() else {
// We could still be starting up the blockchain manager. // We could still be starting up the blockchain manager.
@ -119,28 +137,37 @@ pub async fn handle_incoming_block(
return Ok(IncomingBlockOk::AlreadyHave); return Ok(IncomingBlockOk::AlreadyHave);
} }
// From this point on we MUST not early return without removing the block hash from `BLOCKS_BEING_HANDLED`. // We must remove the block hash from `BLOCKS_BEING_HANDLED`.
let _guard = {
struct RemoveFromBlocksBeingHandled {
block_hash: [u8; 32],
}
impl Drop for RemoveFromBlocksBeingHandled {
fn drop(&mut self) {
BLOCKS_BEING_HANDLED
.lock()
.unwrap()
.remove(&self.block_hash);
}
}
RemoveFromBlocksBeingHandled { block_hash }
};
let (response_tx, response_rx) = oneshot::channel(); let (response_tx, response_rx) = oneshot::channel();
incoming_block_tx incoming_block_tx
.send(BlockchainManagerCommand::AddBlock { .send(BlockchainManagerCommand::AddBlock {
block, block,
prepped_txs, prepped_txs: txs,
response_tx, response_tx,
}) })
.await .await
.expect("TODO: don't actually panic here, an err means we are shutting down"); .expect("TODO: don't actually panic here, an err means we are shutting down");
let res = response_rx response_rx
.await .await
.expect("The blockchain manager will always respond") .expect("The blockchain manager will always respond")
.map_err(IncomingBlockError::InvalidBlock); .map_err(IncomingBlockError::InvalidBlock)
// Remove the block hash from the blocks being handled.
BLOCKS_BEING_HANDLED.lock().unwrap().remove(&block_hash);
res
} }
/// Check if we have a block with the given hash. /// Check if we have a block with the given hash.

View file

@ -8,15 +8,17 @@ use tracing::error;
use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle}; use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle};
use cuprate_consensus::{ use cuprate_consensus::{
context::RawBlockChainContext, BlockChainContextRequest, BlockChainContextResponse, BlockChainContextRequest, BlockChainContextResponse, BlockChainContextService,
BlockChainContextService, BlockVerifierService, ExtendedConsensusError, TxVerifierService, BlockVerifierService, ExtendedConsensusError, TxVerifierService, VerifyBlockRequest,
VerifyBlockRequest, VerifyBlockResponse, VerifyTxRequest, VerifyTxResponse, VerifyBlockResponse, VerifyTxRequest, VerifyTxResponse,
}; };
use cuprate_consensus_context::RawBlockChainContext;
use cuprate_p2p::{ use cuprate_p2p::{
block_downloader::{BlockBatch, BlockDownloaderConfig}, block_downloader::{BlockBatch, BlockDownloaderConfig},
BroadcastSvc, NetworkInterface, BroadcastSvc, NetworkInterface,
}; };
use cuprate_p2p_core::ClearNet; use cuprate_p2p_core::ClearNet;
use cuprate_txpool::service::TxpoolWriteHandle;
use cuprate_types::{ use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse}, blockchain::{BlockchainReadRequest, BlockchainResponse},
Chain, TransactionVerificationData, Chain, TransactionVerificationData,
@ -45,6 +47,7 @@ pub async fn init_blockchain_manager(
clearnet_interface: NetworkInterface<ClearNet>, clearnet_interface: NetworkInterface<ClearNet>,
blockchain_write_handle: BlockchainWriteHandle, blockchain_write_handle: BlockchainWriteHandle,
blockchain_read_handle: BlockchainReadHandle, blockchain_read_handle: BlockchainReadHandle,
txpool_write_handle: TxpoolWriteHandle,
mut blockchain_context_service: BlockChainContextService, mut blockchain_context_service: BlockChainContextService,
block_verifier_service: ConcreteBlockVerifierService, block_verifier_service: ConcreteBlockVerifierService,
block_downloader_config: BlockDownloaderConfig, block_downloader_config: BlockDownloaderConfig,
@ -79,6 +82,7 @@ pub async fn init_blockchain_manager(
let manager = BlockchainManager { let manager = BlockchainManager {
blockchain_write_handle, blockchain_write_handle,
blockchain_read_handle, blockchain_read_handle,
txpool_write_handle,
blockchain_context_service, blockchain_context_service,
cached_blockchain_context: blockchain_context.unchecked_blockchain_context().clone(), cached_blockchain_context: blockchain_context.unchecked_blockchain_context().clone(),
block_verifier_service, block_verifier_service,
@ -101,6 +105,8 @@ pub struct BlockchainManager {
blockchain_write_handle: BlockchainWriteHandle, blockchain_write_handle: BlockchainWriteHandle,
/// A [`BlockchainReadHandle`]. /// A [`BlockchainReadHandle`].
blockchain_read_handle: BlockchainReadHandle, blockchain_read_handle: BlockchainReadHandle,
/// A [`TxpoolWriteHandle`].
txpool_write_handle: TxpoolWriteHandle,
// TODO: Improve the API of the cache service. // TODO: Improve the API of the cache service.
// TODO: rename the cache service -> `BlockchainContextService`. // TODO: rename the cache service -> `BlockchainContextService`.
/// The blockchain context cache, this caches the current state of the blockchain to quickly calculate/retrieve /// The blockchain context cache, this caches the current state of the blockchain to quickly calculate/retrieve

View file

@ -1,7 +1,10 @@
//! The blockchain manager handler functions. //! The blockchain manager handler functions.
use bytes::Bytes; use bytes::Bytes;
use futures::{TryFutureExt, TryStreamExt}; use futures::{TryFutureExt, TryStreamExt};
use monero_serai::{block::Block, transaction::Transaction}; use monero_serai::{
block::Block,
transaction::{Input, Transaction},
};
use rayon::prelude::*; use rayon::prelude::*;
use std::ops::ControlFlow; use std::ops::ControlFlow;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
@ -10,23 +13,21 @@ use tracing::info;
use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle}; use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle};
use cuprate_consensus::{ use cuprate_consensus::{
block::PreparedBlock, context::NewBlockData, transactions::new_tx_verification_data, block::PreparedBlock, transactions::new_tx_verification_data, BlockChainContextRequest,
BlockChainContextRequest, BlockChainContextResponse, BlockVerifierService, BlockChainContextResponse, BlockVerifierService, ExtendedConsensusError, VerifyBlockRequest,
ExtendedConsensusError, VerifyBlockRequest, VerifyBlockResponse, VerifyTxRequest, VerifyBlockResponse, VerifyTxRequest, VerifyTxResponse,
VerifyTxResponse,
}; };
use cuprate_consensus_context::NewBlockData;
use cuprate_helper::cast::usize_to_u64; use cuprate_helper::cast::usize_to_u64;
use cuprate_p2p::{block_downloader::BlockBatch, constants::LONG_BAN, BroadcastRequest}; use cuprate_p2p::{block_downloader::BlockBatch, constants::LONG_BAN, BroadcastRequest};
use cuprate_txpool::service::interface::TxpoolWriteRequest;
use cuprate_types::{ use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest}, blockchain::{BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest},
AltBlockInformation, HardFork, TransactionVerificationData, VerifiedBlockInformation, AltBlockInformation, HardFork, TransactionVerificationData, VerifiedBlockInformation,
}; };
use crate::blockchain::manager::commands::IncomingBlockOk;
use crate::{ use crate::{
blockchain::{ blockchain::manager::commands::{BlockchainManagerCommand, IncomingBlockOk},
manager::commands::BlockchainManagerCommand, types::ConsensusBlockchainReadHandle,
},
constants::PANIC_CRITICAL_SERVICE_ERROR, constants::PANIC_CRITICAL_SERVICE_ERROR,
signals::REORG_LOCK, signals::REORG_LOCK,
}; };
@ -434,6 +435,18 @@ impl super::BlockchainManager {
&mut self, &mut self,
verified_block: VerifiedBlockInformation, verified_block: VerifiedBlockInformation,
) { ) {
// FIXME: this is pretty inefficient, we should probably return the KI map created in the consensus crate.
let spent_key_images = verified_block
.txs
.iter()
.flat_map(|tx| {
tx.tx.prefix().inputs.iter().map(|input| match input {
Input::ToKey { key_image, .. } => key_image.compress().0,
Input::Gen(_) => unreachable!(),
})
})
.collect::<Vec<[u8; 32]>>();
self.blockchain_context_service self.blockchain_context_service
.ready() .ready()
.await .await
@ -472,6 +485,14 @@ impl super::BlockchainManager {
}; };
self.cached_blockchain_context = blockchain_context.unchecked_blockchain_context().clone(); self.cached_blockchain_context = blockchain_context.unchecked_blockchain_context().clone();
self.txpool_write_handle
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(TxpoolWriteRequest::NewBlock { spent_key_images })
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR);
} }
} }

View file

@ -1,11 +1,10 @@
// FIXME: This whole module is not great and should be rewritten when the PeerSet is made. // FIXME: This whole module is not great and should be rewritten when the PeerSet is made.
use std::{pin::pin, sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use futures::StreamExt; use futures::StreamExt;
use tokio::time::interval;
use tokio::{ use tokio::{
sync::{mpsc, Notify}, sync::{mpsc, Notify},
time::sleep, time::interval,
}; };
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use tracing::instrument; use tracing::instrument;

View file

@ -1,13 +1,7 @@
use std::task::{Context, Poll}; use tower::util::MapErr;
use futures::future::BoxFuture;
use futures::{FutureExt, TryFutureExt};
use tower::{util::MapErr, Service};
use cuprate_blockchain::{cuprate_database::RuntimeError, service::BlockchainReadHandle}; use cuprate_blockchain::{cuprate_database::RuntimeError, service::BlockchainReadHandle};
use cuprate_consensus::{BlockChainContextService, BlockVerifierService, TxVerifierService}; use cuprate_consensus::{BlockChainContextService, BlockVerifierService, TxVerifierService};
use cuprate_p2p::block_downloader::{ChainSvcRequest, ChainSvcResponse};
use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
/// The [`BlockVerifierService`] with all generic types defined. /// The [`BlockVerifierService`] with all generic types defined.
pub type ConcreteBlockVerifierService = BlockVerifierService< pub type ConcreteBlockVerifierService = BlockVerifierService<

View file

@ -2,4 +2,7 @@
//! //!
//! Will handle initiating the P2P and contains a protocol request handler. //! Will handle initiating the P2P and contains a protocol request handler.
mod network_address;
pub mod request_handler; pub mod request_handler;
pub use network_address::CrossNetworkInternalPeerId;

View file

@ -0,0 +1,16 @@
use std::net::SocketAddr;
use cuprate_p2p_core::{client::InternalPeerID, ClearNet, NetworkZone};
/// An identifier for a P2P peer on any network.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CrossNetworkInternalPeerId {
/// A clear-net peer.
ClearNet(InternalPeerID<<ClearNet as NetworkZone>::Addr>),
}
impl From<InternalPeerID<<ClearNet as NetworkZone>::Addr>> for CrossNetworkInternalPeerId {
fn from(addr: InternalPeerID<<ClearNet as NetworkZone>::Addr>) -> Self {
Self::ClearNet(addr)
}
}

View file

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

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

@ -57,33 +57,6 @@ pub enum BlockchainManagerRequest {
/// The height of the next block in the chain. /// The height of the next block in the chain.
TargetHeight, TargetHeight,
/// 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.
block: Block,
/// The seed hash for the proof-of-work.
seed_hash: [u8; 32],
},
/// Add auxirilly proof-of-work to a block.
///
/// From the RPC `add_aux_pow` usecase's documentation:
/// ````
/// This enables merge mining with Monero without requiring
/// software that manually alters the extra field in the coinbase
/// tx to include the merkle root of the aux blocks.
/// ````
AddAuxPow {
/// The block template to add to.
block_template: Block,
/// The auxirilly proof-of-work to add.
aux_pow: Vec<AuxPow>,
},
/// Generate new blocks. /// Generate new blocks.
/// ///
/// This request is only for regtest, see RPC's `generateblocks`. /// This request is only for regtest, see RPC's `generateblocks`.
@ -98,19 +71,17 @@ pub enum BlockchainManagerRequest {
wallet_address: String, wallet_address: String,
}, },
/// Get a visual [`String`] overview of blockchain progress. // // TODO: the below requests actually belong to the block downloader/syncer:
/// // // <https://github.com/Cuprate/cuprate/pull/320#discussion_r1811089758>
/// This is a highly implementation specific format used by // /// Get [`Span`] data.
/// `monerod` in the `sync_info` RPC call's `overview` field; // ///
/// it is essentially an ASCII visual of blocks. // /// This is data that describes an active downloading process,
/// // /// if we are fully synced, this will return an empty [`Vec`].
/// See also: // Spans,
/// - <https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#sync_info>
/// - <https://github.com/monero-project/monero/blob/master/src/cryptonote_protocol/block_queue.cpp#L178> //
Overview { /// Get the next [`PruningSeed`] needed for a pruned sync.
/// TODO: the current blockchain height? do we need to pass this? NextNeededPruningSeed,
height: usize,
},
} }
/// TODO: use real type when public. /// TODO: use real type when public.
@ -144,12 +115,6 @@ pub enum BlockchainManagerResponse {
/// Response to [`BlockchainManagerRequest::TargetHeight`] /// Response to [`BlockchainManagerRequest::TargetHeight`]
TargetHeight { height: usize }, TargetHeight { height: usize },
/// Response to [`BlockchainManagerRequest::CalculatePow`]
CalculatePow([u8; 32]),
/// Response to [`BlockchainManagerRequest::AddAuxPow`]
AddAuxPow(AddAuxPow),
/// Response to [`BlockchainManagerRequest::GenerateBlocks`] /// Response to [`BlockchainManagerRequest::GenerateBlocks`]
GenerateBlocks { GenerateBlocks {
/// Hashes of the blocks generated. /// Hashes of the blocks generated.
@ -158,8 +123,10 @@ pub enum BlockchainManagerResponse {
height: usize, height: usize,
}, },
/// Response to [`BlockchainManagerRequest::Overview`] // /// Response to [`BlockchainManagerRequest::Spans`].
Overview(String), // Spans(Vec<Span<Z::Addr>>),
/// Response to [`BlockchainManagerRequest::NextNeededPruningSeed`].
NextNeededPruningSeed(PruningSeed),
} }
/// TODO: use real type when public. /// TODO: use real type when public.

View file

@ -8,7 +8,6 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use cuprate_rpc_types::misc::{BlockHeader, KeyImageSpentStatus};
use futures::StreamExt; use futures::StreamExt;
use monero_serai::block::Block; use monero_serai::block::Block;
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
@ -19,6 +18,7 @@ use cuprate_helper::{
cast::{u64_to_usize, usize_to_u64}, cast::{u64_to_usize, usize_to_u64},
map::split_u128_into_low_high_bits, map::split_u128_into_low_high_bits,
}; };
use cuprate_rpc_types::misc::{BlockHeader, KeyImageSpentStatus};
use cuprate_types::{ use cuprate_types::{
blockchain::BlockchainReadRequest, Chain, ExtendedBlockHeader, VerifiedBlockInformation, blockchain::BlockchainReadRequest, Chain, ExtendedBlockHeader, VerifiedBlockInformation,
}; };

View file

@ -4,7 +4,6 @@ use std::{
}; };
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use cuprate_p2p_core::{client::handshaker::builder::DummyAddressBook, ClearNet};
use futures::TryFutureExt; use futures::TryFutureExt;
use monero_serai::block::Block; use monero_serai::block::Block;
use strum::{EnumCount, VariantArray}; use strum::{EnumCount, VariantArray};
@ -19,6 +18,7 @@ use cuprate_helper::{
cast::{u64_to_usize, usize_to_u64}, cast::{u64_to_usize, usize_to_u64},
map::split_u128_into_low_high_bits, map::split_u128_into_low_high_bits,
}; };
use cuprate_p2p_core::{client::handshaker::builder::DummyAddressBook, ClearNet};
use cuprate_rpc_interface::RpcHandler; use cuprate_rpc_interface::RpcHandler;
use cuprate_rpc_types::{ use cuprate_rpc_types::{
base::{AccessResponseBase, ResponseBase}, base::{AccessResponseBase, ResponseBase},
@ -784,12 +784,16 @@ async fn sync_info(
.map(|info| SyncInfoPeer { info }) .map(|info| SyncInfoPeer { info })
.collect(); .collect();
let next_needed_pruning_seed = // TODO
address_book::next_needed_pruning_seed::<ClearNet>(&mut DummyAddressBook) // let next_needed_pruning_seed =
.await? // address_book::next_needed_pruning_seed::<ClearNet>(&mut DummyAddressBook)
.compress(); // .await?
let overview = blockchain_manager::overview(&mut state.blockchain_manager, height).await?; // .compress();
let spans = address_book::spans::<ClearNet>(&mut DummyAddressBook).await?; // let overview = blockchain_manager::overview(&mut state.blockchain_manager, height).await?;
// let spans = address_book::spans::<ClearNet>(&mut DummyAddressBook).await?;
let next_needed_pruning_seed = todo!();
let overview = todo!();
let spans = todo!();
Ok(SyncInfoResponse { Ok(SyncInfoResponse {
base: AccessResponseBase::OK, base: AccessResponseBase::OK,
@ -837,15 +841,16 @@ async fn get_miner_data(
let difficulty = format!("{:#x}", context.next_difficulty); let difficulty = format!("{:#x}", context.next_difficulty);
let median_weight = usize_to_u64(context.median_weight_for_block_reward); let median_weight = usize_to_u64(context.median_weight_for_block_reward);
let already_generated_coins = context.already_generated_coins; let already_generated_coins = context.already_generated_coins;
let tx_backlog = txpool::block_template_backlog(&mut state.txpool_read) let tx_backlog = todo!();
.await? // let tx_backlog = txpool::block_template_backlog(&mut state.txpool_read)
.into_iter() // .await?
.map(|entry| GetMinerDataTxBacklogEntry { // .into_iter()
id: hex::encode(entry.id), // .map(|entry| GetMinerDataTxBacklogEntry {
weight: entry.weight, // id: hex::encode(entry.id),
fee: entry.fee, // weight: entry.weight,
}) // fee: entry.fee,
.collect(); // })
// .collect();
Ok(GetMinerDataResponse { Ok(GetMinerDataResponse {
base: ResponseBase::OK, base: ResponseBase::OK,
@ -887,16 +892,18 @@ async fn calc_pow(
let block = Block::read(&mut block_blob.as_slice())?; let block = Block::read(&mut block_blob.as_slice())?;
let seed_hash = helper::hex_to_hash(request.seed_hash)?; let seed_hash = helper::hex_to_hash(request.seed_hash)?;
let pow_hash = blockchain_manager::calculate_pow( // let pow_hash = blockchain_manager::calculate_pow(
&mut state.blockchain_manager, // &mut state.blockchain_manager,
hardfork, // hardfork,
request.height, // request.height,
block, // block,
seed_hash, // seed_hash,
) // )
.await?; // .await?;
let hex = hex::encode(pow_hash); // let hex = hex::encode(pow_hash);
let hex = todo!();
Ok(CalcPowResponse { pow_hash: hex }) Ok(CalcPowResponse { pow_hash: hex })
} }
@ -931,9 +938,10 @@ async fn add_aux_pow(
}) })
.collect::<Result<Vec<_>, Error>>()?; .collect::<Result<Vec<_>, Error>>()?;
let resp = let resp = todo!();
blockchain_manager::add_aux_pow(&mut state.blockchain_manager, block_template, aux_pow) // let resp =
.await?; // blockchain_manager::add_aux_pow(&mut state.blockchain_manager, block_template, aux_pow)
// .await?;
let blocktemplate_blob = hex::encode(resp.blocktemplate_blob); let blocktemplate_blob = hex::encode(resp.blocktemplate_blob);
let blockhashing_blob = hex::encode(resp.blockhashing_blob); let blockhashing_blob = hex::encode(resp.blockhashing_blob);

View file

@ -3,16 +3,20 @@
use std::convert::Infallible; use std::convert::Infallible;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use cuprate_pruning::PruningSeed;
use cuprate_rpc_types::misc::{ConnectionInfo, Span};
use tower::ServiceExt; use tower::ServiceExt;
use cuprate_helper::cast::usize_to_u64; use cuprate_helper::cast::usize_to_u64;
use cuprate_p2p_core::{ use cuprate_p2p_core::{
services::{AddressBookRequest, AddressBookResponse}, services::{AddressBookRequest, AddressBookResponse},
types::BanState, types::{BanState, ConnectionId},
AddressBook, NetworkZone, 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.
// FIXME: use `anyhow::Error` over `tower::BoxError` in address book. // FIXME: use `anyhow::Error` over `tower::BoxError` in address book.
@ -53,34 +57,28 @@ pub(crate) async fn connection_info<Z: NetworkZone>(
let vec = vec let vec = vec
.into_iter() .into_iter()
.map(|info| { .map(|info| {
use cuprate_p2p_core::types::AddressType as A1; let (ip, port) = match info.socket_addr {
use cuprate_rpc_types::misc::AddressType as A2; Some(socket) => (socket.ip().to_string(), socket.port().to_string()),
None => (String::new(), String::new()),
let address_type = match info.address_type {
A1::Invalid => A2::Invalid,
A1::Ipv4 => A2::Ipv4,
A1::Ipv6 => A2::Ipv6,
A1::I2p => A2::I2p,
A1::Tor => A2::Tor,
}; };
ConnectionInfo { ConnectionInfo {
address: info.address.to_string(), address: info.address.to_string(),
address_type, address_type: info.address_type,
avg_download: info.avg_download, avg_download: info.avg_download,
avg_upload: info.avg_upload, avg_upload: info.avg_upload,
connection_id: hex::encode(info.connection_id.to_ne_bytes()), connection_id: String::from(ConnectionId::DEFAULT_STR),
current_download: info.current_download, current_download: info.current_download,
current_upload: info.current_upload, current_upload: info.current_upload,
height: info.height, height: info.height,
host: info.host, host: info.host,
incoming: info.incoming, incoming: info.incoming,
ip: info.ip, ip,
live_time: info.live_time, live_time: info.live_time,
localhost: info.localhost, localhost: info.localhost,
local_ip: info.local_ip, local_ip: info.local_ip,
peer_id: info.peer_id, peer_id: hex::encode(info.peer_id.to_ne_bytes()),
port: info.port, port,
pruning_seed: info.pruning_seed.compress(), pruning_seed: info.pruning_seed.compress(),
recv_count: info.recv_count, recv_count: info.recv_count,
recv_idle_time: info.recv_idle_time, recv_idle_time: info.recv_idle_time,
@ -170,53 +168,3 @@ pub(crate) async fn get_bans<Z: NetworkZone>(
Ok(bans) Ok(bans)
} }
/// [`AddressBookRequest::Spans`]
pub(crate) async fn spans<Z: NetworkZone>(
address_book: &mut impl AddressBook<Z>,
) -> Result<Vec<Span>, Error> {
let AddressBookResponse::Spans(vec) = address_book
.ready()
.await
.map_err(|e| anyhow!(e))?
.call(AddressBookRequest::Spans)
.await
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
// FIXME: impl this map somewhere instead of inline.
let vec = vec
.into_iter()
.map(|span| Span {
connection_id: hex::encode(span.connection_id.to_ne_bytes()),
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)
}
/// [`AddressBookRequest::NextNeededPruningSeed`]
pub(crate) async fn next_needed_pruning_seed<Z: NetworkZone>(
address_book: &mut impl AddressBook<Z>,
) -> Result<PruningSeed, Error> {
let AddressBookResponse::NextNeededPruningSeed(seed) = address_book
.ready()
.await
.map_err(|e| anyhow!(e))?
.call(AddressBookRequest::NextNeededPruningSeed)
.await
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
Ok(seed)
}

View file

@ -3,12 +3,14 @@
use std::convert::Infallible; use std::convert::Infallible;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use monero_serai::block::Block;
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use cuprate_consensus::context::{ use cuprate_consensus_context::{
BlockChainContext, BlockChainContextRequest, BlockChainContextResponse, BlockChainContext, BlockChainContextRequest, BlockChainContextResponse,
BlockChainContextService, BlockChainContextService,
}; };
use cuprate_helper::cast::u64_to_usize;
use cuprate_types::{FeeEstimate, HardFork, HardForkInfo}; use cuprate_types::{FeeEstimate, HardFork, HardForkInfo};
// FIXME: use `anyhow::Error` over `tower::BoxError` in blockchain context. // FIXME: use `anyhow::Error` over `tower::BoxError` in blockchain context.
@ -68,3 +70,30 @@ pub(crate) async fn fee_estimate(
Ok(fee) 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

@ -1,15 +1,18 @@
//! Functions for [`BlockchainManagerRequest`] & [`BlockchainManagerResponse`]. //! Functions for [`BlockchainManagerRequest`] & [`BlockchainManagerResponse`].
use anyhow::Error; use anyhow::Error;
use cuprate_types::{AddAuxPow, AuxPow, HardFork};
use monero_serai::block::Block; use monero_serai::block::Block;
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use cuprate_helper::cast::{u64_to_usize, usize_to_u64}; use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
use cuprate_p2p_core::{types::ConnectionId, NetworkZone};
use cuprate_pruning::PruningSeed; use cuprate_pruning::PruningSeed;
use cuprate_rpc_types::misc::Span;
use cuprate_types::{AddAuxPow, AuxPow, HardFork};
use crate::rpc::handler::{ use crate::rpc::{
BlockchainManagerHandle, BlockchainManagerRequest, BlockchainManagerResponse, constants::FIELD_NOT_SUPPORTED,
handler::{BlockchainManagerHandle, BlockchainManagerRequest, BlockchainManagerResponse},
}; };
/// [`BlockchainManagerRequest::PopBlocks`] /// [`BlockchainManagerRequest::PopBlocks`]
@ -144,52 +147,6 @@ pub(crate) async fn target_height(
Ok(usize_to_u64(height)) Ok(usize_to_u64(height))
} }
/// [`BlockchainManagerRequest::CalculatePow`]
pub(crate) async fn calculate_pow(
blockchain_manager: &mut BlockchainManagerHandle,
hardfork: HardFork,
height: u64,
block: Block,
seed_hash: [u8; 32],
) -> Result<[u8; 32], Error> {
let BlockchainManagerResponse::CalculatePow(hash) = blockchain_manager
.ready()
.await?
.call(BlockchainManagerRequest::CalculatePow {
hardfork,
height: u64_to_usize(height),
block,
seed_hash,
})
.await?
else {
unreachable!();
};
Ok(hash)
}
/// [`BlockchainManagerRequest::AddAuxPow`]
pub(crate) async fn add_aux_pow(
blockchain_manager: &mut BlockchainManagerHandle,
block_template: Block,
aux_pow: Vec<AuxPow>,
) -> Result<AddAuxPow, Error> {
let BlockchainManagerResponse::AddAuxPow(response) = blockchain_manager
.ready()
.await?
.call(BlockchainManagerRequest::AddAuxPow {
block_template,
aux_pow,
})
.await?
else {
unreachable!();
};
Ok(response)
}
/// [`BlockchainManagerRequest::GenerateBlocks`] /// [`BlockchainManagerRequest::GenerateBlocks`]
pub(crate) async fn generate_blocks( pub(crate) async fn generate_blocks(
blockchain_manager: &mut BlockchainManagerHandle, blockchain_manager: &mut BlockchainManagerHandle,
@ -215,21 +172,50 @@ pub(crate) async fn generate_blocks(
Ok((blocks, usize_to_u64(height))) Ok((blocks, usize_to_u64(height)))
} }
/// [`BlockchainManagerRequest::Overview`] // [`BlockchainManagerRequest::Spans`]
pub(crate) async fn overview( pub(crate) async fn spans<Z: NetworkZone>(
blockchain_manager: &mut BlockchainManagerHandle, blockchain_manager: &mut BlockchainManagerHandle,
height: u64, ) -> Result<Vec<Span>, Error> {
) -> Result<String, Error> { // let BlockchainManagerResponse::Spans(vec) = blockchain_manager
let BlockchainManagerResponse::Overview(overview) = 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() .ready()
.await? .await?
.call(BlockchainManagerRequest::Overview { .call(BlockchainManagerRequest::NextNeededPruningSeed)
height: u64_to_usize(height),
})
.await? .await?
else { else {
unreachable!(); unreachable!();
}; };
Ok(overview) Ok(seed)
} }

View file

@ -11,7 +11,7 @@ use cuprate_txpool::{
interface::{TxpoolReadRequest, TxpoolReadResponse}, interface::{TxpoolReadRequest, TxpoolReadResponse},
TxpoolReadHandle, TxpoolReadHandle,
}, },
BlockTemplateTxEntry, TxEntry, TxEntry,
}; };
// FIXME: use `anyhow::Error` over `tower::BoxError` in txpool. // FIXME: use `anyhow::Error` over `tower::BoxError` in txpool.
@ -32,24 +32,6 @@ pub(crate) async fn backlog(txpool_read: &mut TxpoolReadHandle) -> Result<Vec<Tx
Ok(tx_entries) Ok(tx_entries)
} }
/// [`TxpoolReadRequest::BlockTemplateBacklog`]
pub(crate) async fn block_template_backlog(
txpool_read: &mut TxpoolReadHandle,
) -> Result<Vec<BlockTemplateTxEntry>, Error> {
let TxpoolReadResponse::BlockTemplateBacklog(tx_entries) = txpool_read
.ready()
.await
.map_err(|e| anyhow!(e))?
.call(TxpoolReadRequest::BlockTemplateBacklog)
.await
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
Ok(tx_entries)
}
/// [`TxpoolReadRequest::Size`] /// [`TxpoolReadRequest::Size`]
pub(crate) async fn size( pub(crate) async fn size(
txpool_read: &mut TxpoolReadHandle, txpool_read: &mut TxpoolReadHandle,

View file

@ -1,7 +1,7 @@
//! Global `static`s used throughout `cuprated`. //! Global `static`s used throughout `cuprated`.
use std::{ use std::{
sync::{atomic::AtomicU64, LazyLock}, sync::LazyLock,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };

View file

@ -1,3 +1,15 @@
//! Transaction Pool //! Transaction Pool
//! //!
//! Will handle initiating the tx-pool, providing the preprocessor required for the dandelion pool. //! Handles initiating the tx-pool, providing the preprocessor required for the dandelion pool.
use cuprate_consensus::BlockChainContextService;
use cuprate_p2p::NetworkInterface;
use cuprate_p2p_core::ClearNet;
use cuprate_txpool::service::{TxpoolReadHandle, TxpoolWriteHandle};
use crate::blockchain::ConcreteTxVerifierService;
mod dandelion;
mod incoming_tx;
mod txs_being_handled;
pub use incoming_tx::IncomingTxHandler;

View file

@ -0,0 +1,65 @@
use std::time::Duration;
use cuprate_dandelion_tower::{
pool::DandelionPoolService, DandelionConfig, DandelionRouter, Graph,
};
use cuprate_p2p::NetworkInterface;
use cuprate_p2p_core::ClearNet;
use cuprate_txpool::service::{TxpoolReadHandle, TxpoolWriteHandle};
use crate::{
p2p::CrossNetworkInternalPeerId,
txpool::incoming_tx::{DandelionTx, TxId},
};
mod diffuse_service;
mod stem_service;
mod tx_store;
/// The configuration used for [`cuprate_dandelion_tower`].
///
/// TODO: should we expose this to users of cuprated? probably not.
const DANDELION_CONFIG: DandelionConfig = DandelionConfig {
time_between_hop: Duration::from_millis(175),
epoch_duration: Duration::from_secs(10 * 60),
fluff_probability: 0.12,
graph: Graph::FourRegular,
};
/// A [`DandelionRouter`] with all generic types defined.
type ConcreteDandelionRouter = DandelionRouter<
stem_service::OutboundPeerStream,
diffuse_service::DiffuseService,
CrossNetworkInternalPeerId,
stem_service::StemPeerService<ClearNet>,
DandelionTx,
>;
/// Starts the dandelion pool manager task and returns a handle to send txs to broadcast.
pub fn start_dandelion_pool_manager(
router: ConcreteDandelionRouter,
txpool_read_handle: TxpoolReadHandle,
txpool_write_handle: TxpoolWriteHandle,
) -> DandelionPoolService<DandelionTx, TxId, CrossNetworkInternalPeerId> {
cuprate_dandelion_tower::pool::start_dandelion_pool_manager(
// TODO: make this constant configurable?
32,
router,
tx_store::TxStoreService {
txpool_read_handle,
txpool_write_handle,
},
DANDELION_CONFIG,
)
}
/// Creates a [`DandelionRouter`] from a [`NetworkInterface`].
pub fn dandelion_router(clear_net: NetworkInterface<ClearNet>) -> ConcreteDandelionRouter {
DandelionRouter::new(
diffuse_service::DiffuseService {
clear_net_broadcast_service: clear_net.broadcast_svc(),
},
stem_service::OutboundPeerStream { clear_net },
DANDELION_CONFIG,
)
}

View file

@ -0,0 +1,44 @@
use std::{
future::{ready, Ready},
task::{Context, Poll},
};
use futures::FutureExt;
use tower::Service;
use cuprate_dandelion_tower::traits::DiffuseRequest;
use cuprate_p2p::{BroadcastRequest, BroadcastSvc};
use cuprate_p2p_core::ClearNet;
use crate::txpool::dandelion::DandelionTx;
/// The dandelion diffusion service.
pub struct DiffuseService {
pub clear_net_broadcast_service: BroadcastSvc<ClearNet>,
}
impl Service<DiffuseRequest<DandelionTx>> for DiffuseService {
type Response = ();
type Error = tower::BoxError;
type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.clear_net_broadcast_service
.poll_ready(cx)
.map_err(Into::into)
}
fn call(&mut self, req: DiffuseRequest<DandelionTx>) -> Self::Future {
// TODO: the dandelion crate should pass along where we got the tx from.
let Ok(()) = self
.clear_net_broadcast_service
.call(BroadcastRequest::Transaction {
tx_bytes: req.0 .0,
direction: None,
received_from: None,
})
.into_inner();
ready(Ok(()))
}
}

View file

@ -0,0 +1,68 @@
use std::{
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use futures::Stream;
use tower::Service;
use cuprate_dandelion_tower::{traits::StemRequest, OutboundPeer};
use cuprate_p2p::{ClientPoolDropGuard, NetworkInterface};
use cuprate_p2p_core::{
client::{Client, InternalPeerID},
ClearNet, NetworkZone, PeerRequest, ProtocolRequest,
};
use cuprate_wire::protocol::NewTransactions;
use crate::{p2p::CrossNetworkInternalPeerId, txpool::dandelion::DandelionTx};
/// The dandelion outbound peer stream.
pub struct OutboundPeerStream {
pub clear_net: NetworkInterface<ClearNet>,
}
impl Stream for OutboundPeerStream {
type Item = Result<
OutboundPeer<CrossNetworkInternalPeerId, StemPeerService<ClearNet>>,
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),
)
}))))
}
}
/// The stem service, used to send stem txs.
pub struct StemPeerService<N: NetworkZone>(ClientPoolDropGuard<N>);
impl<N: NetworkZone> Service<StemRequest<DandelionTx>> for StemPeerService<N> {
type Response = <Client<N> as Service<PeerRequest>>::Response;
type Error = tower::BoxError;
type Future = <Client<N> as Service<PeerRequest>>::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, req: StemRequest<DandelionTx>) -> Self::Future {
self.0
.call(PeerRequest::Protocol(ProtocolRequest::NewTransactions(
NewTransactions {
txs: vec![req.0 .0],
dandelionpp_fluff: false,
padding: Bytes::new(),
},
)))
}
}

View file

@ -0,0 +1,74 @@
use std::task::{Context, Poll};
use bytes::Bytes;
use futures::{future::BoxFuture, FutureExt};
use tower::{Service, ServiceExt};
use cuprate_dandelion_tower::{
traits::{TxStoreRequest, TxStoreResponse},
State,
};
use cuprate_database::RuntimeError;
use cuprate_txpool::service::{
interface::{TxpoolReadRequest, TxpoolReadResponse, TxpoolWriteRequest},
TxpoolReadHandle, TxpoolWriteHandle,
};
use super::{DandelionTx, TxId};
/// The dandelion tx-store service.
///
/// This is just mapping the interface [`cuprate_dandelion_tower`] wants to what [`cuprate_txpool`] provides.
pub struct TxStoreService {
pub txpool_read_handle: TxpoolReadHandle,
pub txpool_write_handle: TxpoolWriteHandle,
}
impl Service<TxStoreRequest<TxId>> for TxStoreService {
type Response = TxStoreResponse<DandelionTx>;
type Error = tower::BoxError;
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, req: TxStoreRequest<TxId>) -> Self::Future {
match req {
TxStoreRequest::Get(tx_id) => self
.txpool_read_handle
.clone()
.oneshot(TxpoolReadRequest::TxBlob(tx_id))
.map(|res| match res {
Ok(TxpoolReadResponse::TxBlob {
tx_blob,
state_stem,
}) => {
let state = if state_stem {
State::Stem
} else {
State::Fluff
};
Ok(TxStoreResponse::Transaction(Some((
DandelionTx(Bytes::from(tx_blob)),
state,
))))
}
Err(RuntimeError::KeyNotFound) => Ok(TxStoreResponse::Transaction(None)),
Err(e) => Err(e.into()),
Ok(_) => unreachable!(),
})
.boxed(),
TxStoreRequest::Promote(tx_id) => self
.txpool_write_handle
.clone()
.oneshot(TxpoolWriteRequest::Promote(tx_id))
.map(|res| match res {
Ok(_) | Err(RuntimeError::KeyNotFound) => Ok(TxStoreResponse::Ok),
Err(e) => Err(e.into()),
})
.boxed(),
}
}
}

View file

@ -0,0 +1,379 @@
use std::{
collections::HashSet,
sync::Arc,
task::{Context, Poll},
};
use bytes::Bytes;
use futures::{future::BoxFuture, FutureExt};
use monero_serai::transaction::Transaction;
use tower::{Service, ServiceExt};
use cuprate_consensus::{
transactions::new_tx_verification_data, BlockChainContextRequest, BlockChainContextResponse,
BlockChainContextService, ExtendedConsensusError, VerifyTxRequest,
};
use cuprate_dandelion_tower::{
pool::{DandelionPoolService, IncomingTxBuilder},
State, TxState,
};
use cuprate_helper::asynch::rayon_spawn_async;
use cuprate_p2p::NetworkInterface;
use cuprate_p2p_core::ClearNet;
use cuprate_txpool::{
service::{
interface::{
TxpoolReadRequest, TxpoolReadResponse, TxpoolWriteRequest, TxpoolWriteResponse,
},
TxpoolReadHandle, TxpoolWriteHandle,
},
transaction_blob_hash,
};
use cuprate_types::TransactionVerificationData;
use crate::{
blockchain::ConcreteTxVerifierService,
constants::PANIC_CRITICAL_SERVICE_ERROR,
p2p::CrossNetworkInternalPeerId,
signals::REORG_LOCK,
txpool::{
dandelion,
txs_being_handled::{TxsBeingHandled, TxsBeingHandledLocally},
},
};
/// An error that can happen handling an incoming tx.
pub enum IncomingTxError {
Parse(std::io::Error),
Consensus(ExtendedConsensusError),
DuplicateTransaction,
}
/// Incoming transactions.
pub struct IncomingTxs {
/// The raw bytes of the transactions.
pub txs: Vec<Bytes>,
/// The routing state of the transactions.
pub state: TxState<CrossNetworkInternalPeerId>,
}
/// The transaction type used for dandelion++.
#[derive(Clone)]
pub struct DandelionTx(pub Bytes);
/// A transaction ID/hash.
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.
pub struct IncomingTxHandler {
/// A store of txs currently being handled in incoming tx requests.
pub(super) txs_being_handled: TxsBeingHandled,
/// The blockchain context cache.
pub(super) blockchain_context_cache: BlockChainContextService,
/// The dandelion txpool manager.
pub(super) dandelion_pool_manager:
DandelionPoolService<DandelionTx, TxId, CrossNetworkInternalPeerId>,
/// The transaction verifier service.
pub(super) tx_verifier_service: ConcreteTxVerifierService,
/// The txpool write handle.
pub(super) txpool_write_handle: TxpoolWriteHandle,
/// The txpool read handle.
pub(super) txpool_read_handle: TxpoolReadHandle,
}
impl IncomingTxHandler {
/// Initialize the [`IncomingTxHandler`].
#[expect(clippy::significant_drop_tightening)]
pub fn init(
clear_net: NetworkInterface<ClearNet>,
txpool_write_handle: TxpoolWriteHandle,
txpool_read_handle: TxpoolReadHandle,
blockchain_context_cache: BlockChainContextService,
tx_verifier_service: ConcreteTxVerifierService,
) -> Self {
let dandelion_router = dandelion::dandelion_router(clear_net);
let dandelion_pool_manager = dandelion::start_dandelion_pool_manager(
dandelion_router,
txpool_read_handle.clone(),
txpool_write_handle.clone(),
);
Self {
txs_being_handled: TxsBeingHandled::new(),
blockchain_context_cache,
dandelion_pool_manager,
tx_verifier_service,
txpool_write_handle,
txpool_read_handle,
}
}
}
impl Service<IncomingTxs> for IncomingTxHandler {
type Response = ();
type Error = IncomingTxError;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: IncomingTxs) -> Self::Future {
handle_incoming_txs(
req,
self.txs_being_handled.clone(),
self.blockchain_context_cache.clone(),
self.tx_verifier_service.clone(),
self.txpool_write_handle.clone(),
self.txpool_read_handle.clone(),
self.dandelion_pool_manager.clone(),
)
.boxed()
}
}
/// Handles the incoming txs.
async fn handle_incoming_txs(
IncomingTxs { txs, state }: IncomingTxs,
txs_being_handled: TxsBeingHandled,
mut blockchain_context_cache: BlockChainContextService,
mut tx_verifier_service: ConcreteTxVerifierService,
mut txpool_write_handle: TxpoolWriteHandle,
mut txpool_read_handle: TxpoolReadHandle,
mut dandelion_pool_manager: DandelionPoolService<DandelionTx, TxId, CrossNetworkInternalPeerId>,
) -> Result<(), IncomingTxError> {
let _reorg_guard = REORG_LOCK.read().await;
let (txs, stem_pool_txs, txs_being_handled_guard) =
prepare_incoming_txs(txs, txs_being_handled, &mut txpool_read_handle).await?;
let BlockChainContextResponse::Context(context) = blockchain_context_cache
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(BlockChainContextRequest::Context)
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
else {
unreachable!()
};
let context = context.unchecked_blockchain_context();
tx_verifier_service
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(VerifyTxRequest::Prepped {
txs: txs.clone(),
current_chain_height: context.chain_height,
top_hash: context.top_hash,
time_for_time_lock: context.current_adjusted_timestamp_for_time_lock(),
hf: context.current_hf,
})
.await
.map_err(IncomingTxError::Consensus)?;
for tx in txs {
handle_valid_tx(
tx,
state.clone(),
&mut txpool_write_handle,
&mut dandelion_pool_manager,
)
.await;
}
// Re-relay any txs we got in the block that were already in our stem pool.
for stem_tx in stem_pool_txs {
rerelay_stem_tx(
&stem_tx,
state.clone(),
&mut txpool_read_handle,
&mut dandelion_pool_manager,
)
.await;
}
Ok(())
}
/// Prepares the incoming transactions for verification.
///
/// This will filter out all transactions already in the pool or txs already being handled in another request.
///
/// Returns in order:
/// - The [`TransactionVerificationData`] for all the txs we did not already have
/// - The Ids of the transactions in the incoming message that are in our stem-pool
/// - A [`TxsBeingHandledLocally`] guard that prevents verifying the same tx at the same time across 2 tasks.
async fn prepare_incoming_txs(
tx_blobs: Vec<Bytes>,
txs_being_handled: TxsBeingHandled,
txpool_read_handle: &mut TxpoolReadHandle,
) -> Result<
(
Vec<Arc<TransactionVerificationData>>,
Vec<TxId>,
TxsBeingHandledLocally,
),
IncomingTxError,
> {
let mut tx_blob_hashes = HashSet::new();
let mut txs_being_handled_locally = txs_being_handled.local_tracker();
// Compute the blob hash for each tx and filter out the txs currently being handled by another incoming tx batch.
let txs = tx_blobs
.into_iter()
.filter_map(|tx_blob| {
let tx_blob_hash = transaction_blob_hash(&tx_blob);
// If a duplicate is in here the incoming tx batch contained the same tx twice.
if !tx_blob_hashes.insert(tx_blob_hash) {
return Some(Err(IncomingTxError::DuplicateTransaction));
}
// If a duplicate is here it is being handled in another batch.
if !txs_being_handled_locally.try_add_tx(tx_blob_hash) {
return None;
}
Some(Ok((tx_blob_hash, tx_blob)))
})
.collect::<Result<Vec<_>, _>>()?;
// Filter the txs already in the txpool out.
// This will leave the txs already in the pool in [`TxBeingHandledLocally`] but that shouldn't be an issue.
let TxpoolReadResponse::FilterKnownTxBlobHashes {
unknown_blob_hashes,
stem_pool_hashes,
} = txpool_read_handle
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(TxpoolReadRequest::FilterKnownTxBlobHashes(tx_blob_hashes))
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
else {
unreachable!()
};
// Now prepare the txs for verification.
rayon_spawn_async(move || {
let txs = txs
.into_iter()
.filter_map(|(tx_blob_hash, tx_blob)| {
if unknown_blob_hashes.contains(&tx_blob_hash) {
Some(tx_blob)
} else {
None
}
})
.map(|bytes| {
let tx = Transaction::read(&mut bytes.as_ref()).map_err(IncomingTxError::Parse)?;
let tx = new_tx_verification_data(tx)
.map_err(|e| IncomingTxError::Consensus(e.into()))?;
Ok(Arc::new(tx))
})
.collect::<Result<Vec<_>, IncomingTxError>>()?;
Ok((txs, stem_pool_hashes, txs_being_handled_locally))
})
.await
}
/// Handle a verified tx.
///
/// This will add the tx to the txpool and route it to the network.
async fn handle_valid_tx(
tx: Arc<TransactionVerificationData>,
state: TxState<CrossNetworkInternalPeerId>,
txpool_write_handle: &mut TxpoolWriteHandle,
dandelion_pool_manager: &mut DandelionPoolService<
DandelionTx,
TxId,
CrossNetworkInternalPeerId,
>,
) {
let incoming_tx =
IncomingTxBuilder::new(DandelionTx(Bytes::copy_from_slice(&tx.tx_blob)), tx.tx_hash);
let TxpoolWriteResponse::AddTransaction(double_spend) = txpool_write_handle
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(TxpoolWriteRequest::AddTransaction {
tx,
state_stem: state.is_stem_stage(),
})
.await
.expect("TODO")
else {
unreachable!()
};
// TODO: track double spends to quickly ignore them from their blob hash.
if let Some(tx_hash) = double_spend {
return;
};
// TODO: There is a race condition possible if a tx and block come in at the same time: <https://github.com/Cuprate/cuprate/issues/314>.
let incoming_tx = incoming_tx
.with_routing_state(state)
.with_state_in_db(None)
.build()
.unwrap();
dandelion_pool_manager
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(incoming_tx)
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR);
}
/// Re-relay a tx that was already in our stem pool.
async fn rerelay_stem_tx(
tx_hash: &TxId,
state: TxState<CrossNetworkInternalPeerId>,
txpool_read_handle: &mut TxpoolReadHandle,
dandelion_pool_manager: &mut DandelionPoolService<
DandelionTx,
TxId,
CrossNetworkInternalPeerId,
>,
) {
let Ok(TxpoolReadResponse::TxBlob { tx_blob, .. }) = txpool_read_handle
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(TxpoolReadRequest::TxBlob(*tx_hash))
.await
else {
// The tx could have been dropped from the pool.
return;
};
let incoming_tx =
IncomingTxBuilder::new(DandelionTx(Bytes::copy_from_slice(&tx_blob)), *tx_hash);
let incoming_tx = incoming_tx
.with_routing_state(state)
.with_state_in_db(Some(State::Stem))
.build()
.unwrap();
dandelion_pool_manager
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(incoming_tx)
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR);
}

View file

@ -0,0 +1,53 @@
use std::sync::Arc;
use dashmap::DashSet;
/// A set of txs currently being handled, shared between instances of the incoming tx handler.
#[derive(Clone)]
pub struct TxsBeingHandled(Arc<DashSet<[u8; 32]>>);
impl TxsBeingHandled {
/// Create a new [`TxsBeingHandled`]
pub fn new() -> Self {
Self(Arc::new(DashSet::new()))
}
/// Create a new [`TxsBeingHandledLocally`] that will keep track of txs being handled in a request.
pub fn local_tracker(&self) -> TxsBeingHandledLocally {
TxsBeingHandledLocally {
txs_being_handled: self.clone(),
txs: vec![],
}
}
}
/// A tracker of txs being handled in a single request. This will add the txs to the global [`TxsBeingHandled`]
/// tracker as well.
///
/// When this is dropped the txs will be removed from [`TxsBeingHandled`].
pub struct TxsBeingHandledLocally {
txs_being_handled: TxsBeingHandled,
txs: Vec<[u8; 32]>,
}
impl TxsBeingHandledLocally {
/// Try add a tx to the map from its [`transaction_blob_hash`](cuprate_txpool::transaction_blob_hash).
///
/// Returns `true` if the tx was added and `false` if another task is already handling this tx.
pub fn try_add_tx(&mut self, tx_blob_hash: [u8; 32]) -> bool {
if !self.txs_being_handled.0.insert(tx_blob_hash) {
return false;
}
self.txs.push(tx_blob_hash);
true
}
}
impl Drop for TxsBeingHandledLocally {
fn drop(&mut self) {
for hash in &self.txs {
self.txs_being_handled.0.remove(hash);
}
}
}

View file

@ -16,7 +16,8 @@ cargo doc --open --package cuprate-blockchain
| Crate | In-tree path | Purpose | | Crate | In-tree path | Purpose |
|-------|--------------|---------| |-------|--------------|---------|
| [`cuprate-consensus`](https://doc.cuprate.org/cuprate_consensus) | [`consensus/`](https://github.com/Cuprate/cuprate/tree/main/consensus) | TODO | [`cuprate-consensus`](https://doc.cuprate.org/cuprate_consensus) | [`consensus/`](https://github.com/Cuprate/cuprate/tree/main/consensus) | TODO
| [`cuprate-consensus-rules`](https://doc.cuprate.org/cuprate_consensus_rules) | [`consensus/rules/`](https://github.com/Cuprate/cuprate/tree/main/consensus-rules) | TODO | [`cuprate-consensus-context`](https://doc.cuprate.org/cuprate_consensus_context) | [`consensus/context/`](https://github.com/Cuprate/cuprate/tree/main/consensus/context) | TODO
| [`cuprate-consensus-rules`](https://doc.cuprate.org/cuprate_consensus_rules) | [`consensus/rules/`](https://github.com/Cuprate/cuprate/tree/main/consensus/rules) | TODO
| [`cuprate-fast-sync`](https://doc.cuprate.org/cuprate_fast_sync) | [`consensus/fast-sync/`](https://github.com/Cuprate/cuprate/tree/main/consensus/fast-sync) | Fast block synchronization | [`cuprate-fast-sync`](https://doc.cuprate.org/cuprate_fast_sync) | [`consensus/fast-sync/`](https://github.com/Cuprate/cuprate/tree/main/consensus/fast-sync) | Fast block synchronization
## Networking ## Networking

View file

@ -8,9 +8,10 @@ authors = ["Boog900"]
repository = "https://github.com/Cuprate/cuprate/tree/main/consensus" repository = "https://github.com/Cuprate/cuprate/tree/main/consensus"
[dependencies] [dependencies]
cuprate-helper = { path = "../helper", default-features = false, features = ["std", "asynch", "num"] } cuprate-helper = { workspace = true, default-features = false, features = ["std", "asynch", "num"] }
cuprate-consensus-rules = { path = "./rules", features = ["rayon"] } cuprate-consensus-rules = { workspace = true, features = ["rayon"] }
cuprate-types = { path = "../types" } cuprate-types = { workspace = true }
cuprate-consensus-context = { workspace = true }
cfg-if = { workspace = true } cfg-if = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
@ -18,20 +19,17 @@ tower = { workspace = true, features = ["util"] }
tracing = { workspace = true, features = ["std", "attributes"] } tracing = { workspace = true, features = ["std", "attributes"] }
futures = { workspace = true, features = ["std", "async-await"] } futures = { workspace = true, features = ["std", "async-await"] }
randomx-rs = { workspace = true }
monero-serai = { workspace = true, features = ["std"] } monero-serai = { workspace = true, features = ["std"] }
rayon = { workspace = true } rayon = { workspace = true }
thread_local = { workspace = true } thread_local = { workspace = true }
tokio = { workspace = true, features = ["rt"] }
tokio-util = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
rand = { workspace = true } rand = { workspace = true }
[dev-dependencies] [dev-dependencies]
cuprate-test-utils = { path = "../test-utils" } cuprate-test-utils = { workspace = true }
cuprate-consensus-rules = {path = "./rules", features = ["proptest"]} cuprate-consensus-rules = { workspace = true, features = ["proptest"]}
hex-literal = { workspace = true } hex-literal = { workspace = true }
curve25519-dalek = { workspace = true } curve25519-dalek = { workspace = true }
@ -42,4 +40,4 @@ proptest = { workspace = true }
proptest-derive = { workspace = true } proptest-derive = { workspace = true }
[lints] [lints]
workspace = true workspace = true

View file

@ -0,0 +1,27 @@
[package]
name = "cuprate-consensus-context"
version = "0.1.0"
edition = "2021"
license = "MIT"
authors = ["SyntheticBird","Boog900"]
[dependencies]
cuprate-consensus-rules = { workspace = true, features = ["proptest"]}
cuprate-helper = { workspace = true, default-features = false, features = ["std", "cast", "num", "asynch"] }
cuprate-types = { workspace = true, default-features = false, features = ["blockchain"] }
futures = { workspace = true, features = ["std", "async-await"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"]}
tokio-util = { workspace = true }
tower = { workspace = true, features = ["util"] }
tracing = { workspace = true, features = ["std", "attributes"] }
thiserror = { workspace = true }
monero-serai = { workspace = true, features = ["std"] }
randomx-rs = { workspace = true }
rayon = { workspace = true }
thread_local = { workspace = true }
hex = { workspace = true }
[lints]
workspace = true

View file

@ -9,9 +9,8 @@ use cuprate_types::{
}; };
use crate::{ use crate::{
ExtendedConsensusError, ContextCacheError, __private::Database, difficulty::DifficultyCache, rx_vms::RandomXVm,
__private::Database, weight::BlockWeightsCache,
context::{difficulty::DifficultyCache, rx_vms::RandomXVm, weight::BlockWeightsCache},
}; };
pub(crate) mod sealed { pub(crate) mod sealed {
@ -38,7 +37,7 @@ pub struct AltChainContextCache {
pub chain_height: usize, pub chain_height: usize,
/// The top hash of the alt chain. /// The top hash of the alt chain.
pub top_hash: [u8; 32], pub top_hash: [u8; 32],
/// The [`ChainID`] of the alt chain. /// The [`ChainId`] of the alt chain.
pub chain_id: Option<ChainId>, pub chain_id: Option<ChainId>,
/// The parent [`Chain`] of this alt chain. /// The parent [`Chain`] of this alt chain.
pub parent_chain: Chain, pub parent_chain: Chain,
@ -98,7 +97,7 @@ impl AltChainMap {
&mut self, &mut self,
prev_id: [u8; 32], prev_id: [u8; 32],
database: D, database: D,
) -> Result<Box<AltChainContextCache>, ExtendedConsensusError> { ) -> Result<Box<AltChainContextCache>, ContextCacheError> {
if let Some(cache) = self.alt_cache_map.remove(&prev_id) { if let Some(cache) = self.alt_cache_map.remove(&prev_id) {
return Ok(cache); return Ok(cache);
} }
@ -133,7 +132,7 @@ pub(crate) async fn get_alt_chain_difficulty_cache<D: Database + Clone>(
prev_id: [u8; 32], prev_id: [u8; 32],
main_chain_difficulty_cache: &DifficultyCache, main_chain_difficulty_cache: &DifficultyCache,
mut database: D, mut database: D,
) -> Result<DifficultyCache, ExtendedConsensusError> { ) -> Result<DifficultyCache, ContextCacheError> {
// find the block with hash == prev_id. // find the block with hash == prev_id.
let BlockchainResponse::FindBlock(res) = database let BlockchainResponse::FindBlock(res) = database
.ready() .ready()
@ -180,7 +179,7 @@ pub(crate) async fn get_alt_chain_weight_cache<D: Database + Clone>(
prev_id: [u8; 32], prev_id: [u8; 32],
main_chain_weight_cache: &BlockWeightsCache, main_chain_weight_cache: &BlockWeightsCache,
mut database: D, mut database: D,
) -> Result<BlockWeightsCache, ExtendedConsensusError> { ) -> Result<BlockWeightsCache, ContextCacheError> {
// find the block with hash == prev_id. // find the block with hash == prev_id.
let BlockchainResponse::FindBlock(res) = database let BlockchainResponse::FindBlock(res) = database
.ready() .ready()

View file

@ -17,7 +17,7 @@ use cuprate_types::{
Chain, Chain,
}; };
use crate::{Database, ExtendedConsensusError, HardFork}; use crate::{ContextCacheError, Database, HardFork};
/// The amount of blocks we account for to calculate difficulty /// The amount of blocks we account for to calculate difficulty
const DIFFICULTY_WINDOW: usize = 720; const DIFFICULTY_WINDOW: usize = 720;
@ -33,9 +33,9 @@ const DIFFICULTY_LAG: usize = 15;
/// ///
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct DifficultyCacheConfig { pub struct DifficultyCacheConfig {
pub(crate) window: usize, pub window: usize,
pub(crate) cut: usize, pub cut: usize,
pub(crate) lag: usize, pub lag: usize,
} }
impl DifficultyCacheConfig { impl DifficultyCacheConfig {
@ -73,14 +73,13 @@ impl DifficultyCacheConfig {
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct DifficultyCache { pub struct DifficultyCache {
/// The list of timestamps in the window. /// The list of timestamps in the window.
/// len <= [`DIFFICULTY_BLOCKS_COUNT`] pub timestamps: VecDeque<u64>,
pub(crate) timestamps: VecDeque<u64>,
/// The current cumulative difficulty of the chain. /// The current cumulative difficulty of the chain.
pub(crate) cumulative_difficulties: VecDeque<u128>, pub cumulative_difficulties: VecDeque<u128>,
/// The last height we accounted for. /// The last height we accounted for.
pub(crate) last_accounted_height: usize, pub last_accounted_height: usize,
/// The config /// The config
pub(crate) config: DifficultyCacheConfig, pub config: DifficultyCacheConfig,
} }
impl DifficultyCache { impl DifficultyCache {
@ -91,7 +90,7 @@ impl DifficultyCache {
config: DifficultyCacheConfig, config: DifficultyCacheConfig,
database: D, database: D,
chain: Chain, chain: Chain,
) -> Result<Self, ExtendedConsensusError> { ) -> Result<Self, ContextCacheError> {
tracing::info!("Initializing difficulty cache this may take a while."); tracing::info!("Initializing difficulty cache this may take a while.");
let mut block_start = chain_height.saturating_sub(config.total_block_count()); let mut block_start = chain_height.saturating_sub(config.total_block_count());
@ -134,7 +133,7 @@ impl DifficultyCache {
&mut self, &mut self,
numb_blocks: usize, numb_blocks: usize,
database: D, database: D,
) -> Result<(), ExtendedConsensusError> { ) -> Result<(), ContextCacheError> {
let Some(retained_blocks) = self.timestamps.len().checked_sub(numb_blocks) else { let Some(retained_blocks) = self.timestamps.len().checked_sub(numb_blocks) else {
// More blocks to pop than we have in the cache, so just restart a new cache. // More blocks to pop than we have in the cache, so just restart a new cache.
*self = Self::init_from_chain_height( *self = Self::init_from_chain_height(
@ -330,7 +329,7 @@ fn next_difficulty(
} }
// TODO: do checked operations here and unwrap so we don't silently overflow? // TODO: do checked operations here and unwrap so we don't silently overflow?
(windowed_work * hf.block_time().as_secs() as u128 + time_span - 1) / time_span (windowed_work * u128::from(hf.block_time().as_secs()) + time_span - 1) / time_span
} }
/// Get the start and end of the window to calculate difficulty. /// Get the start and end of the window to calculate difficulty.
@ -361,7 +360,7 @@ async fn get_blocks_in_pow_info<D: Database + Clone>(
database: D, database: D,
block_heights: Range<usize>, block_heights: Range<usize>,
chain: Chain, chain: Chain,
) -> Result<(VecDeque<u64>, VecDeque<u128>), ExtendedConsensusError> { ) -> Result<(VecDeque<u64>, VecDeque<u128>), ContextCacheError> {
tracing::info!("Getting blocks timestamps"); tracing::info!("Getting blocks timestamps");
let BlockchainResponse::BlockExtendedHeaderInRange(ext_header) = database let BlockchainResponse::BlockExtendedHeaderInRange(ext_header) = database

View file

@ -9,7 +9,7 @@ use cuprate_types::{
Chain, Chain,
}; };
use crate::{Database, ExtendedConsensusError}; use crate::{ContextCacheError, Database};
/// The default amount of hard-fork votes to track to decide on activation of a hard-fork. /// The default amount of hard-fork votes to track to decide on activation of a hard-fork.
/// ///
@ -21,9 +21,9 @@ const DEFAULT_WINDOW_SIZE: usize = 10080; // supermajority window check length -
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct HardForkConfig { pub struct HardForkConfig {
/// The network we are on. /// The network we are on.
pub(crate) info: HFsInfo, pub info: HFsInfo,
/// The amount of votes we are taking into account to decide on a fork activation. /// The amount of votes we are taking into account to decide on a fork activation.
pub(crate) window: usize, pub window: usize,
} }
impl HardForkConfig { impl HardForkConfig {
@ -54,17 +54,17 @@ impl HardForkConfig {
/// A struct that keeps track of the current hard-fork and current votes. /// A struct that keeps track of the current hard-fork and current votes.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct HardForkState { pub struct HardForkState {
/// The current active hard-fork. /// The current active hard-fork.
pub(crate) current_hardfork: HardFork, pub current_hardfork: HardFork,
/// The hard-fork config. /// The hard-fork config.
pub(crate) config: HardForkConfig, pub config: HardForkConfig,
/// The votes in the current window. /// The votes in the current window.
pub(crate) votes: HFVotes, pub votes: HFVotes,
/// The last block height accounted for. /// The last block height accounted for.
pub(crate) last_height: usize, pub last_height: usize,
} }
impl HardForkState { impl HardForkState {
@ -74,7 +74,7 @@ impl HardForkState {
chain_height: usize, chain_height: usize,
config: HardForkConfig, config: HardForkConfig,
mut database: D, mut database: D,
) -> Result<Self, ExtendedConsensusError> { ) -> Result<Self, ContextCacheError> {
tracing::info!("Initializing hard-fork state this may take a while."); tracing::info!("Initializing hard-fork state this may take a while.");
let block_start = chain_height.saturating_sub(config.window); let block_start = chain_height.saturating_sub(config.window);
@ -122,11 +122,11 @@ impl HardForkState {
/// # Invariant /// # Invariant
/// ///
/// This _must_ only be used on a main-chain cache. /// This _must_ only be used on a main-chain cache.
pub(crate) async fn pop_blocks_main_chain<D: Database + Clone>( pub async fn pop_blocks_main_chain<D: Database + Clone>(
&mut self, &mut self,
numb_blocks: usize, numb_blocks: usize,
database: D, database: D,
) -> Result<(), ExtendedConsensusError> { ) -> Result<(), ContextCacheError> {
let Some(retained_blocks) = self.votes.total_votes().checked_sub(self.config.window) else { let Some(retained_blocks) = self.votes.total_votes().checked_sub(self.config.window) else {
*self = Self::init_from_chain_height( *self = Self::init_from_chain_height(
self.last_height + 1 - numb_blocks, self.last_height + 1 - numb_blocks,
@ -159,7 +159,7 @@ impl HardForkState {
} }
/// Add a new block to the cache. /// Add a new block to the cache.
pub(crate) fn new_block(&mut self, vote: HardFork, height: usize) { pub fn new_block(&mut self, vote: HardFork, height: usize) {
// We don't _need_ to take in `height` but it's for safety, so we don't silently loose track // We don't _need_ to take in `height` but it's for safety, so we don't silently loose track
// of blocks. // of blocks.
assert_eq!(self.last_height + 1, height); assert_eq!(self.last_height + 1, height);
@ -194,7 +194,7 @@ impl HardForkState {
} }
/// Returns the current hard-fork. /// Returns the current hard-fork.
pub(crate) const fn current_hardfork(&self) -> HardFork { pub const fn current_hardfork(&self) -> HardFork {
self.current_hardfork self.current_hardfork
} }
} }
@ -205,7 +205,7 @@ async fn get_votes_in_range<D: Database>(
database: D, database: D,
block_heights: Range<usize>, block_heights: Range<usize>,
window_size: usize, window_size: usize,
) -> Result<HFVotes, ExtendedConsensusError> { ) -> Result<HFVotes, ContextCacheError> {
let mut votes = HFVotes::new(window_size); let mut votes = HFVotes::new(window_size);
let BlockchainResponse::BlockExtendedHeaderInRange(vote_list) = database let BlockchainResponse::BlockExtendedHeaderInRange(vote_list) = database

View file

@ -1,9 +1,13 @@
//! # Blockchain Context //! # Blockchain Context
//! //!
//! This module contains a service to get cached context from the blockchain: [`BlockChainContext`]. //! This crate contains a service to get cached context from the blockchain: [`BlockChainContext`].
//! This is used during contextual validation, this does not have all the data for contextual validation //! This is used during contextual validation, this does not have all the data for contextual validation
//! (outputs) for that you will need a [`Database`]. //! (outputs) for that you will need a [`Database`].
//!
// Used in documentation references for [`BlockChainContextRequest`]
// FIXME: should we pull in a dependency just to link docs?
use monero_serai as _;
use std::{ use std::{
cmp::min, cmp::min,
collections::HashMap, collections::HashMap,
@ -14,18 +18,19 @@ use std::{
}; };
use futures::{channel::oneshot, FutureExt}; use futures::{channel::oneshot, FutureExt};
use monero_serai::block::Block;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio_util::sync::PollSender; use tokio_util::sync::PollSender;
use tower::Service; use tower::Service;
use cuprate_consensus_rules::{blocks::ContextToVerifyBlock, current_unix_timestamp, HardFork}; use cuprate_consensus_rules::{
blocks::ContextToVerifyBlock, current_unix_timestamp, ConsensusError, HardFork,
};
use crate::{Database, ExtendedConsensusError}; pub mod difficulty;
pub mod hardforks;
pub(crate) mod difficulty; pub mod rx_vms;
pub(crate) mod hardforks; pub mod weight;
pub(crate) mod rx_vms;
pub(crate) mod weight;
mod alt_chains; mod alt_chains;
mod task; mod task;
@ -36,13 +41,13 @@ use difficulty::DifficultyCache;
use rx_vms::RandomXVm; use rx_vms::RandomXVm;
use weight::BlockWeightsCache; use weight::BlockWeightsCache;
pub(crate) use alt_chains::{sealed::AltChainRequestToken, AltChainContextCache}; pub use alt_chains::{sealed::AltChainRequestToken, AltChainContextCache};
pub use difficulty::DifficultyCacheConfig; pub use difficulty::DifficultyCacheConfig;
pub use hardforks::HardForkConfig; pub use hardforks::HardForkConfig;
pub use tokens::*; pub use tokens::*;
pub use weight::BlockWeightsCacheConfig; pub use weight::BlockWeightsCacheConfig;
pub(crate) const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60; pub const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60;
/// Config for the context service. /// Config for the context service.
pub struct ContextConfig { pub struct ContextConfig {
@ -91,7 +96,7 @@ impl ContextConfig {
pub async fn initialize_blockchain_context<D>( pub async fn initialize_blockchain_context<D>(
cfg: ContextConfig, cfg: ContextConfig,
database: D, database: D,
) -> Result<BlockChainContextService, ExtendedConsensusError> ) -> Result<BlockChainContextService, ContextCacheError>
where where
D: Database + Clone + Send + Sync + 'static, D: Database + Clone + Send + Sync + 'static,
D::Future: Send + 'static, D::Future: Send + 'static,
@ -263,6 +268,21 @@ pub enum BlockChainContextRequest {
grace_blocks: u64, 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. /// Clear the alt chain context caches.
ClearAltCache, ClearAltCache,
@ -360,6 +380,9 @@ pub enum BlockChainContextResponse {
/// Response to [`BlockChainContextRequest::FeeEstimate`] /// Response to [`BlockChainContextRequest::FeeEstimate`]
FeeEstimate(FeeEstimate), FeeEstimate(FeeEstimate),
/// Response to [`BlockChainContextRequest::CalculatePow`]
CalculatePow([u8; 32]),
/// Response to [`BlockChainContextRequest::AltChains`] /// Response to [`BlockChainContextRequest::AltChains`]
/// ///
/// If the inner [`Vec::is_empty`], there were no alternate chains. /// If the inner [`Vec::is_empty`], there were no alternate chains.
@ -414,3 +437,52 @@ impl Service<BlockChainContextRequest> for BlockChainContextService {
.boxed() .boxed()
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum ContextCacheError {
/// A consensus error.
#[error("{0}")]
ConErr(#[from] ConsensusError),
/// A database error.
#[error("Database error: {0}")]
DBErr(#[from] tower::BoxError),
}
use __private::Database;
pub mod __private {
use std::future::Future;
use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
/// A type alias trait used to represent a database, so we don't have to write [`tower::Service`] bounds
/// everywhere.
///
/// Automatically implemented for:
/// ```ignore
/// tower::Service<BCReadRequest, Response = BCResponse, Error = tower::BoxError>
/// ```
pub trait Database:
tower::Service<
BlockchainReadRequest,
Response = BlockchainResponse,
Error = tower::BoxError,
Future = Self::Future2,
>
{
type Future2: Future<Output = Result<Self::Response, Self::Error>> + Send + 'static;
}
impl<
T: tower::Service<
BlockchainReadRequest,
Response = BlockchainResponse,
Error = tower::BoxError,
>,
> Database for T
where
T::Future: Future<Output = Result<Self::Response, Self::Error>> + Send + 'static,
{
type Future2 = T::Future;
}
}

View file

@ -26,10 +26,10 @@ use cuprate_types::{
Chain, Chain,
}; };
use crate::{Database, ExtendedConsensusError}; use crate::{ContextCacheError, Database};
/// The amount of randomX VMs to keep in the cache. /// The amount of randomX VMs to keep in the cache.
const RX_SEEDS_CACHED: usize = 2; pub const RX_SEEDS_CACHED: usize = 2;
/// A multithreaded randomX VM. /// A multithreaded randomX VM.
#[derive(Debug)] #[derive(Debug)]
@ -72,14 +72,14 @@ impl RandomX for RandomXVm {
/// The randomX VMs cache, keeps the VM needed to calculate the current block's proof-of-work hash (if a VM is needed) and a /// The randomX VMs cache, keeps the VM needed to calculate the current block's proof-of-work hash (if a VM is needed) and a
/// couple more around this VM. /// couple more around this VM.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct RandomXVmCache { pub struct RandomXVmCache {
/// The top [`RX_SEEDS_CACHED`] RX seeds. /// The top [`RX_SEEDS_CACHED`] RX seeds.
pub(crate) seeds: VecDeque<(usize, [u8; 32])>, pub seeds: VecDeque<(usize, [u8; 32])>,
/// The VMs for `seeds` (if after hf 12, otherwise this will be empty). /// The VMs for `seeds` (if after hf 12, otherwise this will be empty).
pub(crate) vms: HashMap<usize, Arc<RandomXVm>>, pub vms: HashMap<usize, Arc<RandomXVm>>,
/// A single cached VM that was given to us from a part of Cuprate. /// A single cached VM that was given to us from a part of Cuprate.
pub(crate) cached_vm: Option<([u8; 32], Arc<RandomXVm>)>, pub cached_vm: Option<([u8; 32], Arc<RandomXVm>)>,
} }
impl RandomXVmCache { impl RandomXVmCache {
@ -88,7 +88,7 @@ impl RandomXVmCache {
chain_height: usize, chain_height: usize,
hf: &HardFork, hf: &HardFork,
database: D, database: D,
) -> Result<Self, ExtendedConsensusError> { ) -> Result<Self, ContextCacheError> {
let seed_heights = get_last_rx_seed_heights(chain_height - 1, RX_SEEDS_CACHED); let seed_heights = get_last_rx_seed_heights(chain_height - 1, RX_SEEDS_CACHED);
let seed_hashes = get_block_hashes(seed_heights.clone(), database).await?; let seed_hashes = get_block_hashes(seed_heights.clone(), database).await?;
@ -125,18 +125,18 @@ impl RandomXVmCache {
} }
/// Add a randomX VM to the cache, with the seed it was created with. /// Add a randomX VM to the cache, with the seed it was created with.
pub(crate) fn add_vm(&mut self, vm: ([u8; 32], Arc<RandomXVm>)) { pub fn add_vm(&mut self, vm: ([u8; 32], Arc<RandomXVm>)) {
self.cached_vm.replace(vm); self.cached_vm.replace(vm);
} }
/// Creates a RX VM for an alt chain, looking at the main chain RX VMs to see if we can use one /// Creates a RX VM for an alt chain, looking at the main chain RX VMs to see if we can use one
/// of them first. /// of them first.
pub(crate) async fn get_alt_vm<D: Database>( pub async fn get_alt_vm<D: Database>(
&self, &self,
height: usize, height: usize,
chain: Chain, chain: Chain,
database: D, database: D,
) -> Result<Arc<RandomXVm>, ExtendedConsensusError> { ) -> Result<Arc<RandomXVm>, ContextCacheError> {
let seed_height = randomx_seed_height(height); let seed_height = randomx_seed_height(height);
let BlockchainResponse::BlockHash(seed_hash) = database let BlockchainResponse::BlockHash(seed_hash) = database
@ -162,7 +162,7 @@ impl RandomXVmCache {
} }
/// Get the main-chain `RandomX` VMs. /// Get the main-chain `RandomX` VMs.
pub(crate) async fn get_vms(&mut self) -> HashMap<usize, Arc<RandomXVm>> { pub async fn get_vms(&mut self) -> HashMap<usize, Arc<RandomXVm>> {
match self.seeds.len().checked_sub(self.vms.len()) { match self.seeds.len().checked_sub(self.vms.len()) {
// No difference in the amount of seeds to VMs. // No difference in the amount of seeds to VMs.
Some(0) => (), Some(0) => (),
@ -214,7 +214,7 @@ impl RandomXVmCache {
} }
/// Removes all the `RandomX` VMs above the `new_height`. /// Removes all the `RandomX` VMs above the `new_height`.
pub(crate) fn pop_blocks_main_chain(&mut self, new_height: usize) { pub fn pop_blocks_main_chain(&mut self, new_height: usize) {
self.seeds.retain(|(height, _)| *height < new_height); self.seeds.retain(|(height, _)| *height < new_height);
self.vms.retain(|height, _| *height < new_height); self.vms.retain(|height, _| *height < new_height);
} }
@ -222,7 +222,7 @@ impl RandomXVmCache {
/// Add a new block to the VM cache. /// Add a new block to the VM cache.
/// ///
/// hash is the block hash not the blocks proof-of-work hash. /// hash is the block hash not the blocks proof-of-work hash.
pub(crate) fn new_block(&mut self, height: usize, hash: &[u8; 32]) { pub fn new_block(&mut self, height: usize, hash: &[u8; 32]) {
if is_randomx_seed_height(height) { if is_randomx_seed_height(height) {
tracing::debug!("Block {height} is a randomX seed height, adding it to the cache.",); tracing::debug!("Block {height} is a randomX seed height, adding it to the cache.",);
@ -243,7 +243,7 @@ impl RandomXVmCache {
/// Get the last `amount` of RX seeds, the top height returned here will not necessarily be the RX VM for the top block /// Get the last `amount` of RX seeds, the top height returned here will not necessarily be the RX VM for the top block
/// in the chain as VMs include some lag before a seed activates. /// in the chain as VMs include some lag before a seed activates.
pub(crate) fn get_last_rx_seed_heights(mut last_height: usize, mut amount: usize) -> Vec<usize> { pub fn get_last_rx_seed_heights(mut last_height: usize, mut amount: usize) -> Vec<usize> {
let mut seeds = Vec::with_capacity(amount); let mut seeds = Vec::with_capacity(amount);
if is_randomx_seed_height(last_height) { if is_randomx_seed_height(last_height) {
seeds.push(last_height); seeds.push(last_height);
@ -268,7 +268,7 @@ pub(crate) fn get_last_rx_seed_heights(mut last_height: usize, mut amount: usize
async fn get_block_hashes<D: Database + Clone>( async fn get_block_hashes<D: Database + Clone>(
heights: Vec<usize>, heights: Vec<usize>,
database: D, database: D,
) -> Result<Vec<[u8; 32]>, ExtendedConsensusError> { ) -> Result<Vec<[u8; 32]>, ContextCacheError> {
let mut fut = FuturesOrdered::new(); let mut fut = FuturesOrdered::new();
for height in heights { for height in heights {
@ -281,7 +281,7 @@ async fn get_block_hashes<D: Database + Clone>(
else { else {
panic!("Database sent incorrect response!"); panic!("Database sent incorrect response!");
}; };
Result::<_, ExtendedConsensusError>::Ok(hash) Result::<_, ContextCacheError>::Ok(hash)
}); });
} }

View file

@ -16,13 +16,10 @@ use cuprate_types::{
}; };
use crate::{ use crate::{
context::{ alt_chains::{get_alt_chain_difficulty_cache, get_alt_chain_weight_cache, AltChainMap},
alt_chains::{get_alt_chain_difficulty_cache, get_alt_chain_weight_cache, AltChainMap}, difficulty, hardforks, rx_vms, weight, BlockChainContext, BlockChainContextRequest,
difficulty, hardforks, rx_vms, weight, BlockChainContext, BlockChainContextRequest, BlockChainContextResponse, ContextCacheError, ContextConfig, Database, RawBlockChainContext,
BlockChainContextResponse, ContextConfig, RawBlockChainContext, ValidityToken, ValidityToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
},
Database, ExtendedConsensusError,
}; };
/// A request from the context service to the context task. /// A request from the context service to the context task.
@ -68,7 +65,7 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
pub(crate) async fn init_context( pub(crate) async fn init_context(
cfg: ContextConfig, cfg: ContextConfig,
mut database: D, mut database: D,
) -> Result<Self, ExtendedConsensusError> { ) -> Result<Self, ContextCacheError> {
let ContextConfig { let ContextConfig {
difficulty_cfg, difficulty_cfg,
weights_config, weights_config,
@ -327,7 +324,8 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
} }
BlockChainContextRequest::HardForkInfo(_) BlockChainContextRequest::HardForkInfo(_)
| BlockChainContextRequest::FeeEstimate { .. } | BlockChainContextRequest::FeeEstimate { .. }
| BlockChainContextRequest::AltChains => { | BlockChainContextRequest::AltChains
| BlockChainContextRequest::CalculatePow { .. } => {
todo!("finish https://github.com/Cuprate/cuprate/pull/297") todo!("finish https://github.com/Cuprate/cuprate/pull/297")
} }
}) })

View file

@ -21,12 +21,12 @@ use cuprate_types::{
Chain, Chain,
}; };
use crate::{Database, ExtendedConsensusError, HardFork}; use crate::{ContextCacheError, Database, HardFork};
/// The short term block weight window. /// The short term block weight window.
const SHORT_TERM_WINDOW: usize = 100; pub const SHORT_TERM_WINDOW: usize = 100;
/// The long term block weight window. /// The long term block weight window.
const LONG_TERM_WINDOW: usize = 100000; pub const LONG_TERM_WINDOW: usize = 100000;
/// Configuration for the block weight cache. /// Configuration for the block weight cache.
/// ///
@ -80,7 +80,7 @@ impl BlockWeightsCache {
config: BlockWeightsCacheConfig, config: BlockWeightsCacheConfig,
database: D, database: D,
chain: Chain, chain: Chain,
) -> Result<Self, ExtendedConsensusError> { ) -> Result<Self, ContextCacheError> {
tracing::info!("Initializing weight cache this may take a while."); tracing::info!("Initializing weight cache this may take a while.");
let long_term_weights = get_long_term_weight_in_range( let long_term_weights = get_long_term_weight_in_range(
@ -121,7 +121,7 @@ impl BlockWeightsCache {
&mut self, &mut self,
numb_blocks: usize, numb_blocks: usize,
database: D, database: D,
) -> Result<(), ExtendedConsensusError> { ) -> Result<(), ContextCacheError> {
if self.long_term_weights.window_len() <= numb_blocks { if self.long_term_weights.window_len() <= numb_blocks {
// More blocks to pop than we have in the cache, so just restart a new cache. // More blocks to pop than we have in the cache, so just restart a new cache.
*self = Self::init_from_chain_height( *self = Self::init_from_chain_height(
@ -258,7 +258,7 @@ fn calculate_effective_median_block_weight(
} }
/// Calculates a blocks long term weight. /// Calculates a blocks long term weight.
pub(crate) fn calculate_block_long_term_weight( pub fn calculate_block_long_term_weight(
hf: HardFork, hf: HardFork,
block_weight: usize, block_weight: usize,
long_term_median: usize, long_term_median: usize,
@ -287,7 +287,7 @@ async fn get_blocks_weight_in_range<D: Database + Clone>(
range: Range<usize>, range: Range<usize>,
database: D, database: D,
chain: Chain, chain: Chain,
) -> Result<Vec<usize>, ExtendedConsensusError> { ) -> Result<Vec<usize>, ContextCacheError> {
tracing::info!("getting block weights."); tracing::info!("getting block weights.");
let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
@ -311,7 +311,7 @@ async fn get_long_term_weight_in_range<D: Database + Clone>(
range: Range<usize>, range: Range<usize>,
database: D, database: D,
chain: Chain, chain: Chain,
) -> Result<Vec<usize>, ExtendedConsensusError> { ) -> Result<Vec<usize>, ContextCacheError> {
tracing::info!("getting block long term weights."); tracing::info!("getting block long term weights.");
let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database

View file

@ -9,11 +9,12 @@ name = "cuprate-fast-sync-create-hashes"
path = "src/create.rs" path = "src/create.rs"
[dependencies] [dependencies]
cuprate-blockchain = { path = "../../storage/blockchain" } cuprate-blockchain = { workspace = true }
cuprate-consensus = { path = ".." } cuprate-consensus = { workspace = true }
cuprate-consensus-rules = { path = "../rules" } cuprate-consensus-rules = { workspace = true }
cuprate-types = { path = "../../types" } cuprate-consensus-context = { workspace = true }
cuprate-helper = { path = "../../helper", features = ["cast"] } cuprate-types = { workspace = true }
cuprate-helper = { workspace = true, features = ["cast"] }
clap = { workspace = true, features = ["derive", "std"] } clap = { workspace = true, features = ["derive", "std"] }
hex = { workspace = true } hex = { workspace = true }
@ -27,4 +28,4 @@ tower = { workspace = true }
[dev-dependencies] [dev-dependencies]
[lints] [lints]
workspace = true workspace = true

View file

@ -12,10 +12,8 @@ use monero_serai::{
}; };
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use cuprate_consensus::{ use cuprate_consensus::transactions::new_tx_verification_data;
context::{BlockChainContextRequest, BlockChainContextResponse}, use cuprate_consensus_context::{BlockChainContextRequest, BlockChainContextResponse};
transactions::new_tx_verification_data,
};
use cuprate_consensus_rules::{miner_tx::MinerTxError, ConsensusError}; use cuprate_consensus_rules::{miner_tx::MinerTxError, ConsensusError};
use cuprate_helper::cast::u64_to_usize; use cuprate_helper::cast::u64_to_usize;
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};

View file

@ -11,10 +11,10 @@ proptest = ["cuprate-types/proptest"]
rayon = ["dep:rayon"] rayon = ["dep:rayon"]
[dependencies] [dependencies]
cuprate-constants = { path = "../../constants", default-features = false } cuprate-constants = { workspace = true, default-features = false, features = ["block"] }
cuprate-helper = { path = "../../helper", default-features = false, features = ["std", "cast"] } cuprate-helper = { workspace = true, default-features = false, features = ["std", "cast"] }
cuprate-types = { path = "../../types", default-features = false } cuprate-types = { workspace = true, default-features = false }
cuprate-cryptonight = {path = "../../cryptonight"} cuprate-cryptonight = { workspace = true }
monero-serai = { workspace = true, features = ["std"] } monero-serai = { workspace = true, features = ["std"] }
curve25519-dalek = { workspace = true, features = ["alloc", "zeroize", "precomputed-tables"] } curve25519-dalek = { workspace = true, features = ["alloc", "zeroize", "precomputed-tables"] }

View file

@ -63,9 +63,9 @@ where
/// An internal function that returns an iterator or a parallel iterator if the /// An internal function that returns an iterator or a parallel iterator if the
/// `rayon` feature is enabled. /// `rayon` feature is enabled.
#[cfg(not(feature = "rayon"))] #[cfg(not(feature = "rayon"))]
fn try_par_iter<T>(t: T) -> impl std::iter::Iterator<Item = T::Item> fn try_par_iter<T>(t: T) -> impl Iterator<Item = T::Item>
where where
T: std::iter::IntoIterator, T: IntoIterator,
{ {
t.into_iter() t.into_iter()
} }

View file

@ -68,7 +68,7 @@ pub fn calculate_block_reward(
.unwrap(); .unwrap();
let effective_median_bw: u128 = median_bw.try_into().unwrap(); let effective_median_bw: u128 = median_bw.try_into().unwrap();
(((base_reward as u128 * multiplicand) / effective_median_bw) / effective_median_bw) (((u128::from(base_reward) * multiplicand) / effective_median_bw) / effective_median_bw)
.try_into() .try_into()
.unwrap() .unwrap()
} }

View file

@ -14,6 +14,9 @@ use monero_serai::{
}; };
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use cuprate_consensus_context::{
BlockChainContextRequest, BlockChainContextResponse, RawBlockChainContext,
};
use cuprate_helper::asynch::rayon_spawn_async; use cuprate_helper::asynch::rayon_spawn_async;
use cuprate_types::{ use cuprate_types::{
AltBlockInformation, TransactionVerificationData, VerifiedBlockInformation, AltBlockInformation, TransactionVerificationData, VerifiedBlockInformation,
@ -30,7 +33,6 @@ use cuprate_consensus_rules::{
}; };
use crate::{ use crate::{
context::{BlockChainContextRequest, BlockChainContextResponse, RawBlockChainContext},
transactions::{VerifyTxRequest, VerifyTxResponse}, transactions::{VerifyTxRequest, VerifyTxResponse},
Database, ExtendedConsensusError, Database, ExtendedConsensusError,
}; };

View file

@ -7,6 +7,12 @@ use std::{collections::HashMap, sync::Arc};
use monero_serai::{block::Block, transaction::Input}; use monero_serai::{block::Block, transaction::Input};
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use cuprate_consensus_context::{
difficulty::DifficultyCache,
rx_vms::RandomXVm,
weight::{self, BlockWeightsCache},
AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
};
use cuprate_consensus_rules::{ use cuprate_consensus_rules::{
blocks::{ blocks::{
check_block_pow, check_block_weight, check_timestamp, randomx_seed_height, BlockError, check_block_pow, check_block_weight, check_timestamp, randomx_seed_height, BlockError,
@ -22,12 +28,6 @@ use cuprate_types::{
use crate::{ use crate::{
block::{free::pull_ordered_transactions, PreparedBlock}, block::{free::pull_ordered_transactions, PreparedBlock},
context::{
difficulty::DifficultyCache,
rx_vms::RandomXVm,
weight::{self, BlockWeightsCache},
AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
},
BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError, BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError,
VerifyBlockResponse, VerifyBlockResponse,
}; };

View file

@ -5,6 +5,7 @@ use rayon::prelude::*;
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use tracing::instrument; use tracing::instrument;
use cuprate_consensus_context::rx_vms::RandomXVm;
use cuprate_consensus_rules::{ use cuprate_consensus_rules::{
blocks::{check_block_pow, is_randomx_seed_height, randomx_seed_height, BlockError}, blocks::{check_block_pow, is_randomx_seed_height, randomx_seed_height, BlockError},
hard_forks::HardForkError, hard_forks::HardForkError,
@ -15,7 +16,6 @@ use cuprate_helper::asynch::rayon_spawn_async;
use crate::{ use crate::{
block::{free::pull_ordered_transactions, PreparedBlock, PreparedBlockExPow}, block::{free::pull_ordered_transactions, PreparedBlock, PreparedBlockExPow},
context::rx_vms::RandomXVm,
transactions::new_tx_verification_data, transactions::new_tx_verification_data,
BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError, BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError,
VerifyBlockResponse, VerifyBlockResponse,

View file

@ -24,13 +24,12 @@ use cuprate_consensus_rules::ConsensusError;
mod batch_verifier; mod batch_verifier;
pub mod block; pub mod block;
pub mod context;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub mod transactions; pub mod transactions;
pub use block::{BlockVerifierService, VerifyBlockRequest, VerifyBlockResponse}; pub use block::{BlockVerifierService, VerifyBlockRequest, VerifyBlockResponse};
pub use context::{ pub use cuprate_consensus_context::{
initialize_blockchain_context, BlockChainContext, BlockChainContextRequest, initialize_blockchain_context, BlockChainContext, BlockChainContextRequest,
BlockChainContextResponse, BlockChainContextService, ContextConfig, BlockChainContextResponse, BlockChainContextService, ContextConfig,
}; };

View file

@ -2,15 +2,13 @@ use proptest::strategy::ValueTree;
use proptest::{strategy::Strategy, test_runner::TestRunner}; use proptest::{strategy::Strategy, test_runner::TestRunner};
use tower::ServiceExt; use tower::ServiceExt;
use crate::{ use cuprate_consensus_context::{
context::{ initialize_blockchain_context, BlockChainContextRequest, BlockChainContextResponse,
initialize_blockchain_context, BlockChainContextRequest, BlockChainContextResponse, ContextConfig, NewBlockData,
ContextConfig, NewBlockData,
},
tests::mock_db::*,
HardFork,
}; };
use crate::{tests::mock_db::*, HardFork};
pub(crate) mod data; pub(crate) mod data;
mod difficulty; mod difficulty;
mod hardforks; mod hardforks;

View file

@ -4,10 +4,10 @@ use proptest::collection::{size_range, vec};
use proptest::{prelude::*, prop_assert_eq, prop_compose, proptest}; use proptest::{prelude::*, prop_assert_eq, prop_compose, proptest};
use crate::{ use crate::{
context::difficulty::*,
tests::{context::data::DIF_3000000_3002000, mock_db::*}, tests::{context::data::DIF_3000000_3002000, mock_db::*},
HardFork, HardFork,
}; };
use cuprate_consensus_context::difficulty::*;
use cuprate_helper::num::median; use cuprate_helper::num::median;
use cuprate_types::Chain; use cuprate_types::Chain;

View file

@ -1,13 +1,11 @@
use proptest::{collection::vec, prelude::*}; use proptest::{collection::vec, prelude::*};
use cuprate_consensus_context::{hardforks::HardForkState, HardForkConfig};
use cuprate_consensus_rules::hard_forks::{HFInfo, HFsInfo, HardFork, NUMB_OF_HARD_FORKS}; use cuprate_consensus_rules::hard_forks::{HFInfo, HFsInfo, HardFork, NUMB_OF_HARD_FORKS};
use crate::{ use crate::tests::{
context::{hardforks::HardForkState, HardForkConfig}, context::data::{HFS_2678808_2688888, HFS_2688888_2689608},
tests::{ mock_db::*,
context::data::{HFS_2678808_2688888, HFS_2688888_2689608},
mock_db::*,
},
}; };
const TEST_WINDOW_SIZE: usize = 25; const TEST_WINDOW_SIZE: usize = 25;

View file

@ -3,15 +3,13 @@ use std::collections::VecDeque;
use proptest::prelude::*; use proptest::prelude::*;
use tokio::runtime::Builder; use tokio::runtime::Builder;
use cuprate_consensus_context::rx_vms::{get_last_rx_seed_heights, RandomXVmCache};
use cuprate_consensus_rules::{ use cuprate_consensus_rules::{
blocks::{is_randomx_seed_height, randomx_seed_height}, blocks::{is_randomx_seed_height, randomx_seed_height},
HardFork, HardFork,
}; };
use crate::{ use crate::tests::mock_db::*;
context::rx_vms::{get_last_rx_seed_heights, RandomXVmCache},
tests::mock_db::*,
};
#[test] #[test]
fn rx_heights_consistent() { fn rx_heights_consistent() {

View file

@ -1,11 +1,11 @@
use crate::{ use crate::{
context::{
weight::{calculate_block_long_term_weight, BlockWeightsCache},
BlockWeightsCacheConfig,
},
tests::{context::data::BW_2850000_3050000, mock_db::*}, tests::{context::data::BW_2850000_3050000, mock_db::*},
HardFork, HardFork,
}; };
use cuprate_consensus_context::{
weight::{calculate_block_long_term_weight, BlockWeightsCache},
BlockWeightsCacheConfig,
};
use cuprate_types::Chain; use cuprate_types::Chain;
pub(crate) const TEST_WEIGHT_CONFIG: BlockWeightsCacheConfig = pub(crate) const TEST_WEIGHT_CONFIG: BlockWeightsCacheConfig =

View file

@ -19,4 +19,4 @@ rpc = []
[dev-dependencies] [dev-dependencies]
[lints] [lints]
workspace = true workspace = true

View file

@ -16,7 +16,8 @@ atomic = ["dep:crossbeam"]
asynch = ["dep:futures", "dep:rayon"] asynch = ["dep:futures", "dep:rayon"]
cast = [] cast = []
constants = [] constants = []
fs = ["dep:dirs"] crypto = ["dep:curve25519-dalek", "dep:monero-serai", "std"]
fs = ["dep:dirs", "std"]
num = [] num = []
map = ["cast", "dep:monero-serai", "dep:cuprate-constants"] map = ["cast", "dep:monero-serai", "dep:cuprate-constants"]
time = ["dep:chrono", "std"] time = ["dep:chrono", "std"]
@ -24,14 +25,15 @@ thread = ["std", "dep:target_os_lib"]
tx = ["dep:monero-serai"] tx = ["dep:monero-serai"]
[dependencies] [dependencies]
cuprate-constants = { path = "../constants", optional = true, features = ["block"] } cuprate-constants = { workspace = true, optional = true, features = ["block"] }
crossbeam = { workspace = true, optional = true } chrono = { workspace = true, optional = true, features = ["std", "clock"] }
chrono = { workspace = true, optional = true, features = ["std", "clock"] } crossbeam = { workspace = true, optional = true }
dirs = { workspace = true, optional = true } curve25519-dalek = { workspace = true, optional = true }
futures = { workspace = true, optional = true, features = ["std"] } dirs = { workspace = true, optional = true }
monero-serai = { workspace = true, optional = true } futures = { workspace = true, optional = true, features = ["std"] }
rayon = { workspace = true, optional = true } monero-serai = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
# This is kinda a stupid work around. # This is kinda a stupid work around.
# [thread] needs to activate one of these libs (windows|libc) # [thread] needs to activate one of these libs (windows|libc)

View file

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

122
helper/src/crypto.rs Normal file
View file

@ -0,0 +1,122 @@
//! Crypto related functions and runtime initialized constants
//---------------------------------------------------------------------------------------------------- Use
use std::sync::LazyLock;
use curve25519_dalek::{
constants::ED25519_BASEPOINT_POINT, edwards::VartimeEdwardsPrecomputation,
traits::VartimePrecomputedMultiscalarMul, EdwardsPoint, Scalar,
};
use monero_serai::generators::H;
//---------------------------------------------------------------------------------------------------- Pre-computation
/// This is the decomposed amount table containing the mandatory Pre-RCT amounts. It is used to pre-compute
/// zero commitments at runtime.
///
/// Defined at:
/// - <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/ringct/rctOps.cpp#L44>
#[rustfmt::skip]
pub const ZERO_COMMITMENT_DECOMPOSED_AMOUNT: [u64; 172] = [
1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 20, 30, 40, 50, 60, 70, 80, 90,
100, 200, 300, 400, 500, 600, 700, 800, 900,
1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000,
10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000,
100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000,
1000000, 2000000, 3000000, 4000000, 5000000, 6000000, 7000000, 8000000, 9000000,
10000000, 20000000, 30000000, 40000000, 50000000, 60000000, 70000000, 80000000, 90000000,
100000000, 200000000, 300000000, 400000000, 500000000, 600000000, 700000000, 800000000, 900000000,
1000000000, 2000000000, 3000000000, 4000000000, 5000000000, 6000000000, 7000000000, 8000000000, 9000000000,
10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000, 70000000000, 80000000000, 90000000000,
100000000000, 200000000000, 300000000000, 400000000000, 500000000000, 600000000000, 700000000000, 800000000000, 900000000000,
1000000000000, 2000000000000, 3000000000000, 4000000000000, 5000000000000, 6000000000000, 7000000000000, 8000000000000, 9000000000000,
10000000000000, 20000000000000, 30000000000000, 40000000000000, 50000000000000, 60000000000000, 70000000000000, 80000000000000, 90000000000000,
100000000000000, 200000000000000, 300000000000000, 400000000000000, 500000000000000, 600000000000000, 700000000000000, 800000000000000, 900000000000000,
1000000000000000, 2000000000000000, 3000000000000000, 4000000000000000, 5000000000000000, 6000000000000000, 7000000000000000, 8000000000000000, 9000000000000000,
10000000000000000, 20000000000000000, 30000000000000000, 40000000000000000, 50000000000000000, 60000000000000000, 70000000000000000, 80000000000000000, 90000000000000000,
100000000000000000, 200000000000000000, 300000000000000000, 400000000000000000, 500000000000000000, 600000000000000000, 700000000000000000, 800000000000000000, 900000000000000000,
1000000000000000000, 2000000000000000000, 3000000000000000000, 4000000000000000000, 5000000000000000000, 6000000000000000000, 7000000000000000000, 8000000000000000000, 9000000000000000000,
10000000000000000000
];
/// Runtime initialized [`H`] generator.
static H_PRECOMP: LazyLock<VartimeEdwardsPrecomputation> =
LazyLock::new(|| VartimeEdwardsPrecomputation::new([*H, ED25519_BASEPOINT_POINT]));
/// Runtime initialized zero commitment lookup table
///
/// # Invariant
/// This function assumes that the [`ZERO_COMMITMENT_DECOMPOSED_AMOUNT`]
/// table is sorted.
pub static ZERO_COMMITMENT_LOOKUP_TABLE: LazyLock<[EdwardsPoint; 172]> = LazyLock::new(|| {
let mut lookup_table: [EdwardsPoint; 172] = [ED25519_BASEPOINT_POINT; 172];
for (i, amount) in ZERO_COMMITMENT_DECOMPOSED_AMOUNT.into_iter().enumerate() {
lookup_table[i] = ED25519_BASEPOINT_POINT + *H * Scalar::from(amount);
}
lookup_table
});
//---------------------------------------------------------------------------------------------------- Free functions
/// This function computes the zero commitment given a specific amount.
///
/// It will first attempt to lookup into the table of known Pre-RCT value.
/// Compute it otherwise.
#[expect(clippy::cast_possible_truncation)]
pub fn compute_zero_commitment(amount: u64) -> EdwardsPoint {
// OPTIMIZATION: Unlike monerod which execute a linear search across its lookup
// table (O(n)). Cuprate is making use of an arithmetic based constant time
// version (O(1)). It has been benchmarked in both hit and miss scenarios against
// a binary search lookup (O(log2(n))). To understand the following algorithm it
// is important to observe the pattern that follows the values of
// [`ZERO_COMMITMENT_DECOMPOSED_AMOUNT`].
// First obtain the logarithm base 10 of the amount. and extend it back to obtain
// the amount without its most significant digit.
let Some(log) = amount.checked_ilog10() else {
// amount = 0 so H component is 0.
return ED25519_BASEPOINT_POINT;
};
let div = 10_u64.pow(log);
// Extract the most significant digit.
let most_significant_digit = amount / div;
// If the *rounded* version is different than the exact amount. Then
// there aren't only trailing zeroes behind the most significant digit.
// The amount is not part of the table and can calculated apart.
if most_significant_digit * div != amount {
return H_PRECOMP.vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE]);
}
// Calculating the index back by progressing within the powers of 10.
// The index of the first value in the cached amount's row.
let row_start = u64::from(log) * 9;
// The index of the cached amount
let index = (most_significant_digit - 1 + row_start) as usize;
ZERO_COMMITMENT_LOOKUP_TABLE[index]
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
use curve25519_dalek::{traits::VartimePrecomputedMultiscalarMul, Scalar};
use crate::crypto::{compute_zero_commitment, H_PRECOMP, ZERO_COMMITMENT_DECOMPOSED_AMOUNT};
#[test]
/// Compare the output of `compute_zero_commitment` for all
/// preRCT decomposed amounts against their actual computation.
///
/// Assert that the lookup table returns the correct commitments
fn compare_lookup_with_computation() {
for amount in ZERO_COMMITMENT_DECOMPOSED_AMOUNT {
let commitment = H_PRECOMP.vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE]);
assert!(commitment == compute_zero_commitment(amount));
}
}
}

View file

@ -11,7 +11,7 @@ pub mod atomic;
#[cfg(feature = "cast")] #[cfg(feature = "cast")]
pub mod cast; pub mod cast;
#[cfg(feature = "fs")] #[cfg(all(feature = "fs", feature = "std"))]
pub mod fs; pub mod fs;
pub mod network; pub mod network;
@ -30,6 +30,9 @@ pub mod time;
#[cfg(feature = "tx")] #[cfg(feature = "tx")]
pub mod tx; pub mod tx;
#[cfg(feature = "crypto")]
pub mod crypto;
//---------------------------------------------------------------------------------------------------- Private Usage //---------------------------------------------------------------------------------------------------- Private Usage
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------

View file

@ -15,8 +15,8 @@ default = ["std"]
std = ["dep:thiserror", "bytes/std", "cuprate-fixed-bytes/std"] std = ["dep:thiserror", "bytes/std", "cuprate-fixed-bytes/std"]
[dependencies] [dependencies]
cuprate-helper = { path = "../../helper", default-features = false, features = ["cast"] } cuprate-helper = { workspace = true, default-features = false, features = ["cast"] }
cuprate-fixed-bytes = { path = "../fixed-bytes", default-features = false } cuprate-fixed-bytes = { workspace = true, default-features = false }
paste = "1.0.15" paste = "1.0.15"
ref-cast = "1.0.23" ref-cast = "1.0.23"
@ -27,4 +27,4 @@ thiserror = { workspace = true, optional = true}
hex = { workspace = true, features = ["default"] } hex = { workspace = true, features = ["default"] }
[lints] [lints]
workspace = true workspace = true

View file

@ -1,3 +1,5 @@
use alloc::{string::ToString, vec, vec::Vec};
use bytes::{Buf, BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, Bytes, BytesMut};
use ref_cast::RefCast; use ref_cast::RefCast;

View file

@ -1,3 +1,4 @@
use alloc::string::{String, ToString};
use core::{ use core::{
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
num::TryFromIntError, num::TryFromIntError,

View file

@ -64,6 +64,7 @@ use hex as _;
extern crate alloc; extern crate alloc;
use alloc::string::ToString;
use core::str::from_utf8 as str_from_utf8; use core::str::from_utf8 as str_from_utf8;
use bytes::{Buf, BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, Bytes, BytesMut};

View file

@ -1,7 +1,7 @@
//! This module contains a [`EpeeValue`] trait and //! This module contains a [`EpeeValue`] trait and
//! impls for some possible base epee values. //! impls for some possible base epee values.
use alloc::{string::String, vec::Vec}; use alloc::{string::String, vec, vec::Vec};
use core::fmt::Debug; use core::fmt::Debug;
use bytes::{Buf, BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, Bytes, BytesMut};

View file

@ -12,7 +12,7 @@ default = []
tracing = ["dep:tracing", "tokio-util/tracing"] tracing = ["dep:tracing", "tokio-util/tracing"]
[dependencies] [dependencies]
cuprate-helper = { path = "../../helper", default-features = false, features = ["cast"] } cuprate-helper = { workspace = true, default-features = false, features = ["cast"] }
cfg-if = { workspace = true } cfg-if = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
@ -30,4 +30,4 @@ tokio = { workspace = true, features = ["full"] }
futures = { workspace = true, features = ["std"] } futures = { workspace = true, features = ["std"] }
[lints] [lints]
workspace = true workspace = true

View file

@ -11,11 +11,11 @@ default = []
tracing = ["cuprate-levin/tracing"] tracing = ["cuprate-levin/tracing"]
[dependencies] [dependencies]
cuprate-levin = { path = "../levin" } cuprate-levin = { workspace = true }
cuprate-epee-encoding = { path = "../epee-encoding" } cuprate-epee-encoding = { workspace = true }
cuprate-fixed-bytes = { path = "../fixed-bytes" } cuprate-fixed-bytes = { workspace = true }
cuprate-types = { path = "../../types", default-features = false, features = ["epee"] } cuprate-types = { workspace = true, default-features = false, features = ["epee"] }
cuprate-helper = { path = "../../helper", default-features = false, features = ["map"] } cuprate-helper = { workspace = true, default-features = false, features = ["map"] }
bitflags = { workspace = true, features = ["std"] } bitflags = { workspace = true, features = ["std"] }
bytes = { workspace = true, features = ["std"] } bytes = { workspace = true, features = ["std"] }

View file

@ -7,9 +7,9 @@ authors = ["Boog900"]
[dependencies] [dependencies]
cuprate-constants = { path = "../../constants" } cuprate-constants = { workspace = true }
cuprate-pruning = { path = "../../pruning" } cuprate-pruning = { workspace = true }
cuprate-p2p-core = { path = "../p2p-core" } cuprate-p2p-core = { workspace = true, features = ["borsh"] }
tower = { workspace = true, features = ["util"] } tower = { workspace = true, features = ["util"] }
tokio = { workspace = true, features = ["time", "fs", "rt"]} tokio = { workspace = true, features = ["time", "fs", "rt"]}
@ -26,9 +26,9 @@ rand = { workspace = true, features = ["std", "std_rng"] }
borsh = { workspace = true, features = ["derive", "std"]} borsh = { workspace = true, features = ["derive", "std"]}
[dev-dependencies] [dev-dependencies]
cuprate-test-utils = {path = "../../test-utils"} cuprate-test-utils = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"]} tokio = { workspace = true, features = ["rt-multi-thread", "macros"]}
[lints] [lints]
workspace = true workspace = true

View file

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

View file

@ -73,6 +73,15 @@ pub enum TxState<Id> {
Local, Local,
} }
impl<Id> TxState<Id> {
/// Returns `true` if the tx is in the stem stage.
///
/// [`TxState::Local`] & [`TxState::Stem`] are the 2 stem stage states.
pub const fn is_stem_stage(&self) -> bool {
matches!(self, Self::Local | Self::Stem { .. })
}
}
/// A request to route a transaction. /// A request to route a transaction.
pub struct DandelionRouteReq<Tx, Id> { pub struct DandelionRouteReq<Tx, Id> {
/// The transaction. /// The transaction.

View file

@ -10,9 +10,10 @@ default = ["borsh"]
borsh = ["dep:borsh", "cuprate-pruning/borsh"] borsh = ["dep:borsh", "cuprate-pruning/borsh"]
[dependencies] [dependencies]
cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } cuprate-helper = { workspace = true, features = ["asynch"], default-features = false }
cuprate-wire = { path = "../../net/wire", features = ["tracing"] } cuprate-wire = { workspace = true, features = ["tracing"] }
cuprate-pruning = { path = "../../pruning" } cuprate-pruning = { workspace = true }
cuprate-types = { workspace = true }
tokio = { workspace = true, features = ["net", "sync", "macros", "time", "rt", "rt-multi-thread"]} tokio = { workspace = true, features = ["net", "sync", "macros", "time", "rt", "rt-multi-thread"]}
tokio-util = { workspace = true, features = ["codec"] } tokio-util = { workspace = true, features = ["codec"] }
@ -29,10 +30,10 @@ hex-literal = { workspace = true }
borsh = { workspace = true, features = ["derive", "std"], optional = true } borsh = { workspace = true, features = ["derive", "std"], optional = true }
[dev-dependencies] [dev-dependencies]
cuprate-test-utils = { path = "../../test-utils" } cuprate-test-utils = { workspace = true }
hex = { workspace = true, features = ["std"] } hex = { workspace = true, features = ["std"] }
tokio-test = { workspace = true } tokio-test = { workspace = true }
[lints] [lints]
workspace = true workspace = true

View file

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

View file

@ -6,7 +6,7 @@ use cuprate_wire::{CoreSyncData, PeerListEntryBase};
use crate::{ use crate::{
client::InternalPeerID, client::InternalPeerID,
handles::ConnectionHandle, handles::ConnectionHandle,
types::{BanState, ConnectionInfo, SetBan, Span}, types::{BanState, ConnectionInfo, SetBan},
NetZoneAddress, NetworkAddressIncorrectZone, NetworkZone, NetZoneAddress, NetworkAddressIncorrectZone, NetworkZone,
}; };
@ -133,12 +133,6 @@ pub enum AddressBookRequest<Z: NetworkZone> {
/// Get the state of all bans. /// Get the state of all bans.
GetBans, GetBans,
/// 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. /// Get the next [`PruningSeed`] needed for a pruned sync.
NextNeededPruningSeed, NextNeededPruningSeed,
} }
@ -179,9 +173,6 @@ pub enum AddressBookResponse<Z: NetworkZone> {
/// Response to [`AddressBookRequest::GetBans`]. /// Response to [`AddressBookRequest::GetBans`].
GetBans(Vec<BanState<Z::Addr>>), GetBans(Vec<BanState<Z::Addr>>),
/// Response to [`AddressBookRequest::Spans`].
Spans(Vec<Span<Z::Addr>>),
/// Response to [`AddressBookRequest::NextNeededPruningSeed`]. /// Response to [`AddressBookRequest::NextNeededPruningSeed`].
NextNeededPruningSeed(PruningSeed), NextNeededPruningSeed(PruningSeed),
} }

View file

@ -3,6 +3,7 @@
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use cuprate_pruning::PruningSeed; use cuprate_pruning::PruningSeed;
use cuprate_types::{AddressType, ConnectionState};
use crate::NetZoneAddress; use crate::NetZoneAddress;
@ -24,83 +25,23 @@ pub struct BanState<A: NetZoneAddress> {
pub unban_instant: Option<Instant>, pub unban_instant: Option<Instant>,
} }
/// An enumeration of address types.
///
/// Used [`ConnectionInfo::address_type`].
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum AddressType {
#[default]
Invalid,
Ipv4,
Ipv6,
I2p,
Tor,
}
impl AddressType {
/// Convert [`Self`] to a [`u8`].
///
/// ```rust
/// use cuprate_p2p_core::AddressType as A;
///
/// assert_eq!(A::Invalid.to_u8(), 0);
/// assert_eq!(A::Ipv4.to_u8(), 1);
/// assert_eq!(A::Ipv6.to_u8(), 2);
/// assert_eq!(A::I2p.to_u8(), 3);
/// assert_eq!(A::Tor.to_u8(), 4);
/// ```
pub const fn to_u8(self) -> u8 {
self as u8
}
/// Convert a [`u8`] to a [`Self`].
///
/// # Errors
/// This returns [`None`] if `u > 4`.
///
/// ```rust
/// use cuprate_p2p_core::AddressType as A;
///
/// assert_eq!(A::from_u8(0), Some(A::Invalid));
/// assert_eq!(A::from_u8(1), Some(A::Ipv4));
/// assert_eq!(A::from_u8(2), Some(A::Ipv6));
/// assert_eq!(A::from_u8(3), Some(A::I2p));
/// assert_eq!(A::from_u8(4), Some(A::Tor));
/// assert_eq!(A::from_u8(5), None);
/// ```
pub const fn from_u8(u: u8) -> Option<Self> {
Some(match u {
0 => Self::Invalid,
1 => Self::Ipv4,
2 => Self::Ipv6,
3 => Self::I2p,
4 => Self::Tor,
_ => return None,
})
}
}
// TODO: reduce fields and map to RPC type.
//
/// Data within [`crate::services::AddressBookResponse::ConnectionInfo`]. /// Data within [`crate::services::AddressBookResponse::ConnectionInfo`].
pub struct ConnectionInfo<A: NetZoneAddress> { pub struct ConnectionInfo<A: NetZoneAddress> {
// The following fields are mostly the same as `monerod`.
pub address: A, pub address: A,
pub address_type: AddressType, pub address_type: AddressType,
pub avg_download: u64, pub avg_download: u64,
pub avg_upload: u64, pub avg_upload: u64,
pub connection_id: u64, // TODO: boost::uuids::uuid
pub current_download: u64, pub current_download: u64,
pub current_upload: u64, pub current_upload: u64,
pub height: u64, pub height: u64,
/// Either a domain or an IP without the port.
pub host: String, pub host: String,
pub incoming: bool, pub incoming: bool,
pub ip: String,
pub live_time: u64, pub live_time: u64,
pub localhost: bool, pub localhost: bool,
pub local_ip: bool, pub local_ip: bool,
pub peer_id: String, pub peer_id: u64,
pub port: String,
pub pruning_seed: PruningSeed, pub pruning_seed: PruningSeed,
pub recv_count: u64, pub recv_count: u64,
pub recv_idle_time: u64, pub recv_idle_time: u64,
@ -108,16 +49,44 @@ pub struct ConnectionInfo<A: NetZoneAddress> {
pub rpc_port: u16, pub rpc_port: u16,
pub send_count: u64, pub send_count: u64,
pub send_idle_time: u64, pub send_idle_time: u64,
pub state: String, // TODO: what type is this? pub state: ConnectionState,
pub support_flags: u32, pub support_flags: u32,
// The following fields are slightly different than `monerod`.
//
/// [`None`] if Tor/i2p or unknown.
pub socket_addr: Option<std::net::SocketAddr>,
/// This field does not exist for `cuprated`'s RPC, this is just a marker type:
/// - <https://github.com/Cuprate/cuprate/pull/320#discussion_r1811335020>
/// - <https://github.com/Cuprate/cuprate/pull/320#discussion_r1819826080>
///
/// [`ConnectionId::DEFAULT_STR`] is used when mapping to the RPC type.
pub connection_id: ConnectionId,
}
/// Marker type for `monerod`'s connection ID.
///
/// `connection_id` is a 128-bit `uuid` in `monerod`.
/// `cuprated` does not support this field so it returns
/// the default value in the RPC interface, an all 0-bit UUID.
///
/// This default value in string form is [`ConnectionId::DEFAULT_STR`].
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConnectionId;
impl ConnectionId {
/// [`str`] representation of a default connection ID.
pub const DEFAULT_STR: &str = "00000000000000000000000000000000";
} }
/// Used in RPC's `sync_info`. /// Used in RPC's `sync_info`.
/// ///
/// Data within [`crate::services::AddressBookResponse::Spans`]. // TODO: fix docs after <https://github.com/Cuprate/cuprate/pull/320#discussion_r1811089758>
// Data within [`crate::services::AddressBookResponse::Spans`].
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Span<A: NetZoneAddress> { pub struct Span<A: NetZoneAddress> {
pub connection_id: u64, // TODO: boost::uuids::uuid
pub nblocks: u64, pub nblocks: u64,
pub rate: u32, pub rate: u32,
pub remote_address: A, pub remote_address: A,

View file

@ -6,15 +6,15 @@ license = "MIT"
authors = ["Boog900"] authors = ["Boog900"]
[dependencies] [dependencies]
cuprate-constants = { path = "../../constants" } cuprate-constants = { workspace = true }
cuprate-fixed-bytes = { path = "../../net/fixed-bytes" } cuprate-fixed-bytes = { workspace = true }
cuprate-wire = { path = "../../net/wire" } cuprate-wire = { workspace = true }
cuprate-p2p-core = { path = "../p2p-core", features = ["borsh"] } cuprate-p2p-core = { workspace = true, features = ["borsh"] }
cuprate-address-book = { path = "../address-book" } cuprate-address-book = { workspace = true }
cuprate-pruning = { path = "../../pruning" } cuprate-pruning = { workspace = true }
cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } cuprate-helper = { workspace = true, features = ["asynch"], default-features = false }
cuprate-async-buffer = { path = "../async-buffer" } cuprate-async-buffer = { workspace = true }
cuprate-types = { path = "../../types", default-features = false } cuprate-types = { workspace = true, default-features = false }
monero-serai = { workspace = true, features = ["std"] } monero-serai = { workspace = true, features = ["std"] }
@ -35,10 +35,10 @@ tracing = { workspace = true, features = ["std", "attributes"] }
borsh = { workspace = true, features = ["derive", "std"] } borsh = { workspace = true, features = ["derive", "std"] }
[dev-dependencies] [dev-dependencies]
cuprate-test-utils = { path = "../../test-utils" } cuprate-test-utils = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
proptest = { workspace = true } proptest = { workspace = true }
tokio-test = { workspace = true } tokio-test = { workspace = true }
[lints] [lints]
workspace = true workspace = true

View file

@ -18,13 +18,13 @@ use tracing::{Instrument, Span};
use cuprate_p2p_core::{ use cuprate_p2p_core::{
client::{Client, InternalPeerID}, client::{Client, InternalPeerID},
handles::ConnectionHandle, handles::ConnectionHandle,
NetworkZone, ConnectionDirection, NetworkZone,
}; };
pub(crate) mod disconnect_monitor; pub(crate) mod disconnect_monitor;
mod drop_guard_client; mod drop_guard_client;
pub(crate) use drop_guard_client::ClientPoolDropGuard; pub use drop_guard_client::ClientPoolDropGuard;
/// The client pool, which holds currently connected free peers. /// The client pool, which holds currently connected free peers.
/// ///
@ -165,6 +165,17 @@ impl<N: NetworkZone> ClientPool<N> {
sync_data.cumulative_difficulty() > cumulative_difficulty sync_data.cumulative_difficulty() > cumulative_difficulty
}) })
} }
/// Returns the first outbound peer when iterating over the peers.
pub fn outbound_client(self: &Arc<Self>) -> Option<ClientPoolDropGuard<N>> {
let client = self
.clients
.iter()
.find(|element| element.value().info.direction == ConnectionDirection::Outbound)?;
let id = *client.key();
Some(self.borrow_client(&id).unwrap())
}
} }
mod sealed { mod sealed {

View file

@ -18,7 +18,7 @@ use cuprate_p2p_core::{
pub mod block_downloader; pub mod block_downloader;
mod broadcast; mod broadcast;
mod client_pool; pub mod client_pool;
pub mod config; pub mod config;
pub mod connection_maintainer; pub mod connection_maintainer;
pub mod constants; pub mod constants;
@ -26,6 +26,7 @@ mod inbound_server;
use block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse}; use block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse};
pub use broadcast::{BroadcastRequest, BroadcastSvc}; pub use broadcast::{BroadcastRequest, BroadcastSvc};
pub use client_pool::{ClientPool, ClientPoolDropGuard};
pub use config::{AddressBookConfig, P2PConfig}; pub use config::{AddressBookConfig, P2PConfig};
use connection_maintainer::MakeConnectionRequest; use connection_maintainer::MakeConnectionRequest;
@ -82,7 +83,7 @@ where
let outbound_handshaker = outbound_handshaker_builder.build(); let outbound_handshaker = outbound_handshaker_builder.build();
let client_pool = client_pool::ClientPool::new(); let client_pool = ClientPool::new();
let (make_connection_tx, make_connection_rx) = mpsc::channel(3); let (make_connection_tx, make_connection_rx) = mpsc::channel(3);
@ -132,7 +133,7 @@ where
#[derive(Clone)] #[derive(Clone)]
pub struct NetworkInterface<N: NetworkZone> { pub struct NetworkInterface<N: NetworkZone> {
/// A pool of free connected peers. /// A pool of free connected peers.
pool: Arc<client_pool::ClientPool<N>>, pool: Arc<ClientPool<N>>,
/// A [`Service`] that allows broadcasting to all connected peers. /// A [`Service`] that allows broadcasting to all connected peers.
broadcast_svc: BroadcastSvc<N>, broadcast_svc: BroadcastSvc<N>,
/// A channel to request extra connections. /// A channel to request extra connections.
@ -173,7 +174,7 @@ impl<N: NetworkZone> NetworkInterface<N> {
} }
/// Borrows the `ClientPool`, for access to connected peers. /// Borrows the `ClientPool`, for access to connected peers.
pub const fn client_pool(&self) -> &Arc<client_pool::ClientPool<N>> { pub const fn client_pool(&self) -> &Arc<ClientPool<N>> {
&self.pool &self.pool
} }
} }

View file

@ -10,11 +10,11 @@ default = []
borsh = ["dep:borsh"] borsh = ["dep:borsh"]
[dependencies] [dependencies]
cuprate-constants = { path = "../constants" } cuprate-constants = { workspace = true, features = ["block"] }
thiserror = { workspace = true } thiserror = { workspace = true }
borsh = { workspace = true, features = ["derive", "std"], optional = true } borsh = { workspace = true, features = ["derive", "std"], optional = true }
[lints] [lints]
workspace = true workspace = true

View file

@ -10,23 +10,23 @@ keywords = ["cuprate", "rpc", "interface"]
[features] [features]
default = ["dummy", "serde"] default = ["dummy", "serde"]
dummy = [] dummy = ["dep:cuprate-helper", "dep:futures"]
[dependencies] [dependencies]
cuprate-epee-encoding = { path = "../../net/epee-encoding", default-features = false } cuprate-epee-encoding = { workspace = true, default-features = false }
cuprate-json-rpc = { path = "../json-rpc", default-features = false } cuprate-json-rpc = { workspace = true, default-features = false }
cuprate-rpc-types = { path = "../types", features = ["serde", "epee"], default-features = false } cuprate-rpc-types = { workspace = true, features = ["serde", "epee"], default-features = false }
cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } cuprate-helper = { workspace = true, features = ["asynch"], default-features = false, optional = true }
anyhow = { workspace = true } anyhow = { workspace = true }
axum = { version = "0.7.5", features = ["json"], default-features = false } axum = { version = "0.7.5", features = ["json"], default-features = false }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
tower = { workspace = true } tower = { workspace = true, features = ["util"] }
paste = { workspace = true } paste = { workspace = true }
futures = { workspace = true } futures = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
cuprate-test-utils = { path = "../../test-utils" } cuprate-test-utils = { workspace = true }
axum = { version = "0.7.5", features = ["json", "tokio", "http2"] } axum = { version = "0.7.5", features = ["json", "tokio", "http2"] }
serde_json = { workspace = true, features = ["std"] } serde_json = { workspace = true, features = ["std"] }

View file

@ -10,22 +10,22 @@ keywords = ["cuprate", "rpc", "types", "monero"]
[features] [features]
default = ["serde", "epee"] default = ["serde", "epee"]
serde = ["dep:serde", "cuprate-fixed-bytes/serde"] serde = ["dep:serde", "cuprate-fixed-bytes/serde", "cuprate-types/serde"]
epee = ["dep:cuprate-epee-encoding"] epee = ["dep:cuprate-epee-encoding", "cuprate-types/epee"]
[dependencies] [dependencies]
cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true } cuprate-epee-encoding = { workspace = true, optional = true }
cuprate-fixed-bytes = { path = "../../net/fixed-bytes" } cuprate-fixed-bytes = { workspace = true }
cuprate-types = { path = "../../types", default-features = false, features = ["epee", "serde"] } cuprate-types = { workspace = true, default-features = false }
paste = { workspace = true } paste = { workspace = true }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
cuprate-test-utils = { path = "../../test-utils" } cuprate-test-utils = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
[lints] [lints]
workspace = true workspace = true

View file

@ -164,7 +164,7 @@ impl AccessResponseBase {
/// use cuprate_rpc_types::{misc::*, base::*}; /// use cuprate_rpc_types::{misc::*, base::*};
/// ///
/// assert_eq!(AccessResponseBase::OK_UNTRUSTED, AccessResponseBase { /// assert_eq!(AccessResponseBase::OK_UNTRUSTED, AccessResponseBase {
/// response_base: ResponseBase::ok_untrusted(), /// response_base: ResponseBase::OK_UNTRUSTED,
/// credits: 0, /// credits: 0,
/// top_hash: "".into(), /// top_hash: "".into(),
/// }); /// });

View file

@ -20,12 +20,16 @@ use cuprate_types::BlockCompleteEntry;
use crate::{ use crate::{
base::AccessResponseBase, base::AccessResponseBase,
defaults::{default_false, default_zero},
macros::{define_request, define_request_and_response, define_request_and_response_doc}, macros::{define_request, define_request_and_response, define_request_and_response_doc},
misc::{BlockOutputIndices, GetOutputsOut, OutKeyBin, PoolInfoExtent, PoolTxInfo, Status}, misc::{BlockOutputIndices, GetOutputsOut, OutKeyBin, PoolTxInfo, Status},
rpc_call::RpcCallValue, rpc_call::RpcCallValue,
}; };
#[cfg(any(feature = "epee", feature = "serde"))]
use crate::defaults::{default_false, default_zero};
#[cfg(feature = "epee")]
use crate::misc::PoolInfoExtent;
//---------------------------------------------------------------------------------------------------- Definitions //---------------------------------------------------------------------------------------------------- Definitions
define_request_and_response! { define_request_and_response! {
get_blocks_by_heightbin, get_blocks_by_heightbin,

View file

@ -8,10 +8,6 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
base::{AccessResponseBase, ResponseBase}, base::{AccessResponseBase, ResponseBase},
defaults::{
default_false, default_height, default_one, default_string, default_true, default_vec,
default_zero,
},
macros::define_request_and_response, macros::define_request_and_response,
misc::{ misc::{
AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan, AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan,
@ -21,6 +17,12 @@ use crate::{
rpc_call::RpcCallValue, rpc_call::RpcCallValue,
}; };
#[cfg(any(feature = "epee", feature = "serde"))]
use crate::defaults::{
default_false, default_height, default_one, default_string, default_true, default_vec,
default_zero,
};
//---------------------------------------------------------------------------------------------------- Macro //---------------------------------------------------------------------------------------------------- Macro
/// Adds a (de)serialization doc-test to a type in `json.rs`. /// Adds a (de)serialization doc-test to a type in `json.rs`.
/// ///
@ -658,7 +660,7 @@ define_request_and_response! {
connections: vec![ connections: vec![
ConnectionInfo { ConnectionInfo {
address: "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion:18083".into(), address: "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion:18083".into(),
address_type: 4, address_type: cuprate_types::AddressType::Tor,
avg_download: 0, avg_download: 0,
avg_upload: 0, avg_upload: 0,
connection_id: "22ef856d0f1d44cc95e84fecfd065fe2".into(), connection_id: "22ef856d0f1d44cc95e84fecfd065fe2".into(),
@ -680,12 +682,12 @@ define_request_and_response! {
rpc_port: 0, rpc_port: 0,
send_count: 3406572, send_count: 3406572,
send_idle_time: 30, send_idle_time: 30,
state: "normal".into(), state: cuprate_types::ConnectionState::Normal,
support_flags: 0 support_flags: 0
}, },
ConnectionInfo { ConnectionInfo {
address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(), address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(),
address_type: 4, address_type: cuprate_types::AddressType::Tor,
avg_download: 0, avg_download: 0,
avg_upload: 0, avg_upload: 0,
connection_id: "c7734e15936f485a86d2b0534f87e499".into(), connection_id: "c7734e15936f485a86d2b0534f87e499".into(),
@ -707,7 +709,7 @@ define_request_and_response! {
rpc_port: 0, rpc_port: 0,
send_count: 3370566, send_count: 3370566,
send_idle_time: 120, send_idle_time: 120,
state: "normal".into(), state: cuprate_types::ConnectionState::Normal,
support_flags: 0 support_flags: 0
} }
], ],
@ -1251,7 +1253,7 @@ define_request_and_response! {
SyncInfoPeer { SyncInfoPeer {
info: ConnectionInfo { info: ConnectionInfo {
address: "142.93.128.65:44986".into(), address: "142.93.128.65:44986".into(),
address_type: 1, address_type: cuprate_types::AddressType::Ipv4,
avg_download: 1, avg_download: 1,
avg_upload: 1, avg_upload: 1,
connection_id: "a5803c4c2dac49e7b201dccdef54c862".into(), connection_id: "a5803c4c2dac49e7b201dccdef54c862".into(),
@ -1273,14 +1275,14 @@ define_request_and_response! {
rpc_port: 18089, rpc_port: 18089,
send_count: 32235, send_count: 32235,
send_idle_time: 6, send_idle_time: 6,
state: "normal".into(), state: cuprate_types::ConnectionState::Normal,
support_flags: 1 support_flags: 1
} }
}, },
SyncInfoPeer { SyncInfoPeer {
info: ConnectionInfo { info: ConnectionInfo {
address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(), address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(),
address_type: 4, address_type: cuprate_types::AddressType::Tor,
avg_download: 0, avg_download: 0,
avg_upload: 0, avg_upload: 0,
connection_id: "277f7c821bc546878c8bd29977e780f5".into(), connection_id: "277f7c821bc546878c8bd29977e780f5".into(),
@ -1302,7 +1304,7 @@ define_request_and_response! {
rpc_port: 0, rpc_port: 0,
send_count: 99120, send_count: 99120,
send_idle_time: 15, send_idle_time: 15,
state: "normal".into(), state: cuprate_types::ConnectionState::Normal,
support_flags: 0 support_flags: 0
} }
} }

View file

@ -6,6 +6,7 @@
)] )]
mod constants; mod constants;
#[cfg(any(feature = "serde", feature = "epee"))]
mod defaults; mod defaults;
mod free; mod free;
mod macros; mod macros;

View file

@ -20,8 +20,8 @@ use cuprate_epee_encoding::{
"rpc/core_rpc_server_commands_defs.h", "rpc/core_rpc_server_commands_defs.h",
45..=55 45..=55
)] )]
#[cfg(feature = "epee")] #[cfg(any(feature = "epee", feature = "serde"))]
fn compress_integer_array(_: &[u64]) -> error::Result<Vec<u8>> { fn compress_integer_array(_: &[u64]) -> Vec<u8> {
todo!() todo!()
} }
@ -33,6 +33,7 @@ fn compress_integer_array(_: &[u64]) -> error::Result<Vec<u8>> {
"rpc/core_rpc_server_commands_defs.h", "rpc/core_rpc_server_commands_defs.h",
57..=72 57..=72
)] )]
#[cfg(any(feature = "epee", feature = "serde"))]
fn decompress_integer_array(_: &[u8]) -> Vec<u64> { fn decompress_integer_array(_: &[u8]) -> Vec<u64> {
todo!() todo!()
} }
@ -135,12 +136,7 @@ fn serialize_distribution_as_compressed_data<S>(v: &Vec<u64>, s: S) -> Result<S:
where where
S: serde::Serializer, S: serde::Serializer,
{ {
match compress_integer_array(v) { compress_integer_array(v).serialize(s)
Ok(compressed_data) => compressed_data.serialize(s),
Err(_) => Err(serde::ser::Error::custom(
"error compressing distribution array",
)),
}
} }
/// Deserializer function for [`DistributionCompressedBinary::distribution`]. /// Deserializer function for [`DistributionCompressedBinary::distribution`].
@ -256,7 +252,7 @@ impl EpeeObject for Distribution {
distribution, distribution,
amount, amount,
}) => { }) => {
let compressed_data = compress_integer_array(&distribution)?; let compressed_data = compress_integer_array(&distribution);
start_height.write(w)?; start_height.write(w)?;
base.write(w)?; base.write(w)?;

View file

@ -11,10 +11,10 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "epee")] #[cfg(feature = "epee")]
use cuprate_epee_encoding::epee_object; use cuprate_epee_encoding::epee_object;
use crate::{ use crate::macros::monero_definition_link;
defaults::{default_string, default_zero},
macros::monero_definition_link, #[cfg(any(feature = "epee", feature = "serde"))]
}; use crate::defaults::default_zero;
//---------------------------------------------------------------------------------------------------- Macros //---------------------------------------------------------------------------------------------------- Macros
/// This macro (local to this file) defines all the misc types. /// This macro (local to this file) defines all the misc types.
@ -110,7 +110,7 @@ define_struct_and_impl_epee! {
/// Used in [`crate::json::GetConnectionsResponse`]. /// Used in [`crate::json::GetConnectionsResponse`].
ConnectionInfo { ConnectionInfo {
address: String, address: String,
address_type: crate::misc::AddressType, address_type: cuprate_types::AddressType,
avg_download: u64, avg_download: u64,
avg_upload: u64, avg_upload: u64,
connection_id: String, connection_id: String,
@ -135,7 +135,7 @@ define_struct_and_impl_epee! {
// Exists in the original definition, but isn't // Exists in the original definition, but isn't
// used or (de)serialized for RPC purposes. // used or (de)serialized for RPC purposes.
// ssl: bool, // ssl: bool,
state: String, state: cuprate_types::ConnectionState,
support_flags: u32, support_flags: u32,
} }
} }
@ -148,7 +148,7 @@ define_struct_and_impl_epee! {
)] )]
/// Used in [`crate::json::SetBansRequest`]. /// Used in [`crate::json::SetBansRequest`].
SetBan { SetBan {
#[cfg_attr(feature = "serde", serde(default = "default_string"))] #[cfg_attr(feature = "serde", serde(default = "crate::defaults::default_string"))]
host: String, host: String,
#[cfg_attr(feature = "serde", serde(default = "default_zero"))] #[cfg_attr(feature = "serde", serde(default = "default_zero"))]
ip: u32, ip: u32,

View file

@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
base::{AccessResponseBase, ResponseBase}, base::{AccessResponseBase, ResponseBase},
defaults::{default_false, default_string, default_true, default_vec, default_zero},
macros::define_request_and_response, macros::define_request_and_response,
misc::{ misc::{
GetOutputsOut, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo, GetOutputsOut, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo,
@ -17,6 +16,9 @@ use crate::{
RpcCallValue, RpcCallValue,
}; };
#[cfg(any(feature = "serde", feature = "epee"))]
use crate::defaults::{default_false, default_string, default_true, default_vec, default_zero};
//---------------------------------------------------------------------------------------------------- Macro //---------------------------------------------------------------------------------------------------- Macro
/// Adds a (de)serialization doc-test to a type in `other.rs`. /// Adds a (de)serialization doc-test to a type in `other.rs`.
/// ///

View file

@ -9,37 +9,36 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/storage/cuprate-bloc
keywords = ["cuprate", "blockchain", "database"] keywords = ["cuprate", "blockchain", "database"]
[features] [features]
default = ["heed", "service"] default = ["heed"]
# default = ["redb", "service"] # default = ["redb", "service"]
# default = ["redb-memory", "service"] # default = ["redb-memory", "service"]
heed = ["cuprate-database/heed"] heed = ["cuprate-database/heed"]
redb = ["cuprate-database/redb"] redb = ["cuprate-database/redb"]
redb-memory = ["cuprate-database/redb-memory"] redb-memory = ["cuprate-database/redb-memory"]
service = ["dep:thread_local", "dep:rayon", "cuprate-helper/thread"] serde = ["dep:serde", "cuprate-database/serde", "cuprate-database-service/serde"]
[dependencies] [dependencies]
cuprate-database = { path = "../database" } cuprate-database = { workspace = true }
cuprate-database-service = { path = "../service" } cuprate-database-service = { workspace = true }
cuprate-helper = { path = "../../helper", features = ["fs", "map"] } cuprate-helper = { workspace = true, features = ["fs", "map", "crypto", "tx", "thread"] }
cuprate-types = { path = "../../types", features = ["blockchain"] } cuprate-types = { workspace = true, features = ["blockchain"] }
cuprate-pruning = { path = "../../pruning" } cuprate-pruning = { workspace = true }
bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] } bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] }
bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
curve25519-dalek = { workspace = true } curve25519-dalek = { workspace = true }
rand = { workspace = true } rand = { workspace = true, features = ["std", "std_rng"] }
monero-serai = { workspace = true, features = ["std"] } monero-serai = { workspace = true, features = ["std"] }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
# `service` feature.
tower = { workspace = true } tower = { workspace = true }
thread_local = { workspace = true, optional = true } thread_local = { workspace = true }
rayon = { workspace = true, optional = true } rayon = { workspace = true }
[dev-dependencies] [dev-dependencies]
cuprate-constants = { path = "../../constants" } cuprate-constants = { workspace = true }
cuprate-helper = { path = "../../helper", features = ["thread", "cast"] } cuprate-helper = { workspace = true, features = ["thread", "cast"] }
cuprate-test-utils = { path = "../../test-utils" } cuprate-test-utils = { workspace = true }
tokio = { workspace = true, features = ["full"] } tokio = { workspace = true, features = ["full"] }
tempfile = { workspace = true } tempfile = { workspace = true }

View file

@ -32,9 +32,6 @@ use cuprate_blockchain::{
This ensures the types/traits used from `cuprate_database` are the same ones used by `cuprate_blockchain` internally. This ensures the types/traits used from `cuprate_database` are the same ones used by `cuprate_blockchain` internally.
# Feature flags # Feature flags
The `service` module requires the `service` feature to be enabled.
See the module for more documentation.
Different database backends are enabled by the feature flags: Different database backends are enabled by the feature flags:
- `heed` (LMDB) - `heed` (LMDB)
- `redb` - `redb`
@ -45,7 +42,7 @@ The default is `heed`.
<!-- FIXME: tracing should be behind a feature flag --> <!-- FIXME: tracing should be behind a feature flag -->
# Invariants when not using `service` # Invariants when not using `service`
`cuprate_blockchain` can be used without the `service` feature enabled but `cuprate_blockchain` can be used without the `service` module but
there are some things that must be kept in mind when doing so. there are some things that must be kept in mind when doing so.
Failing to uphold these invariants may cause panics. Failing to uphold these invariants may cause panics.

View file

@ -29,16 +29,12 @@ pub use free::open;
pub mod config; pub mod config;
pub mod ops; pub mod ops;
pub mod service;
pub mod tables; pub mod tables;
pub mod types; pub mod types;
//---------------------------------------------------------------------------------------------------- Feature-gated
#[cfg(feature = "service")]
pub mod service;
//---------------------------------------------------------------------------------------------------- Private //---------------------------------------------------------------------------------------------------- Private
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests; pub(crate) mod tests;
#[cfg(feature = "service")] // only needed in `service` for now
pub(crate) mod unsafe_sendable; pub(crate) mod unsafe_sendable;

View file

@ -1,12 +1,13 @@
//! Output functions. //! Output functions.
//---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Import
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY, Scalar}; use curve25519_dalek::edwards::CompressedEdwardsY;
use monero_serai::{generators::H, transaction::Timelock}; use monero_serai::transaction::Timelock;
use cuprate_database::{ use cuprate_database::{
RuntimeError, {DatabaseRo, DatabaseRw}, RuntimeError, {DatabaseRo, DatabaseRw},
}; };
use cuprate_helper::crypto::compute_zero_commitment;
use cuprate_helper::map::u64_to_timelock; use cuprate_helper::map::u64_to_timelock;
use cuprate_types::OutputOnChain; use cuprate_types::OutputOnChain;
@ -155,9 +156,7 @@ pub fn output_to_output_on_chain(
amount: Amount, amount: Amount,
table_tx_unlock_time: &impl DatabaseRo<TxUnlockTime>, table_tx_unlock_time: &impl DatabaseRo<TxUnlockTime>,
) -> Result<OutputOnChain, RuntimeError> { ) -> Result<OutputOnChain, RuntimeError> {
// FIXME: implement lookup table for common values: let commitment = compute_zero_commitment(amount);
// <https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/ringct/rctOps.cpp#L322>
let commitment = ED25519_BASEPOINT_POINT + *H * Scalar::from(amount);
let time_lock = if output let time_lock = if output
.output_flags .output_flags

View file

@ -2,10 +2,10 @@
//---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Import
use bytemuck::TransparentWrapper; use bytemuck::TransparentWrapper;
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, Scalar};
use monero_serai::transaction::{Input, Timelock, Transaction}; use monero_serai::transaction::{Input, Timelock, Transaction};
use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec}; use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec};
use cuprate_helper::crypto::compute_zero_commitment;
use crate::{ use crate::{
ops::{ ops::{
@ -136,12 +136,9 @@ pub fn add_tx(
.enumerate() .enumerate()
.map(|(i, output)| { .map(|(i, output)| {
// Create commitment. // Create commitment.
// <https://github.com/Cuprate/cuprate/pull/102#discussion_r1559489302>
// FIXME: implement lookup table for common values:
// <https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/ringct/rctOps.cpp#L322>
let commitment = if miner_tx { let commitment = if miner_tx {
ED25519_BASEPOINT_POINT compute_zero_commitment(output.amount.unwrap_or(0))
+ *monero_serai::generators::H * Scalar::from(output.amount.unwrap_or(0))
} else { } else {
proofs proofs
.as_ref() .as_ref()

View file

@ -10,8 +10,6 @@
//! //!
//! The system is managed by this crate, and only requires [`init`] by the user. //! The system is managed by this crate, and only requires [`init`] by the user.
//! //!
//! This module must be enabled with the `service` feature.
//!
//! ## Handles //! ## Handles
//! The 2 handles to the database are: //! The 2 handles to the database are:
//! - [`BlockchainReadHandle`] //! - [`BlockchainReadHandle`]

View file

@ -9,10 +9,10 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/storage/database"
keywords = ["cuprate", "database"] keywords = ["cuprate", "database"]
[features] [features]
# default = ["heed"] default = ["heed"]
# default = ["redb"] # default = ["redb"]
# default = ["redb-memory"] # default = ["redb-memory"]
heed = ["dep:heed"] heed = []
redb = ["dep:redb"] redb = ["dep:redb"]
redb-memory = ["redb"] redb-memory = ["redb"]
@ -25,7 +25,7 @@ paste = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
# Optional features. # Optional features.
heed = { version = "0.20.5", features = ["read-txn-no-tls"], optional = true } heed = { version = "0.20.5", features = ["read-txn-no-tls"] }
redb = { version = "2.1.3", optional = true } redb = { version = "2.1.3", optional = true }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }

View file

@ -4,6 +4,8 @@ cfg_if::cfg_if! {
// If both backends are enabled, fallback to `heed`. // If both backends are enabled, fallback to `heed`.
// This is useful when using `--all-features`. // This is useful when using `--all-features`.
if #[cfg(all(feature = "redb", not(feature = "heed")))] { if #[cfg(all(feature = "redb", not(feature = "heed")))] {
use heed as _;
mod redb; mod redb;
pub use redb::ConcreteEnv; pub use redb::ConcreteEnv;
} else { } else {

View file

@ -8,14 +8,20 @@ authors = ["Boog900"]
repository = "https://github.com/Cuprate/cuprate/tree/main/storage/service" repository = "https://github.com/Cuprate/cuprate/tree/main/storage/service"
keywords = ["cuprate", "service", "database"] keywords = ["cuprate", "service", "database"]
[features]
default = ["heed"]
heed = ["cuprate-database/heed"]
redb = ["cuprate-database/redb"]
redb-memorey = ["cuprate-database/redb-memory"]
[dependencies] [dependencies]
cuprate-database = { path = "../database" } cuprate-database = { workspace = true }
cuprate-helper = { path = "../../helper", features = ["fs", "thread", "map"] } cuprate-helper = { workspace = true, features = ["fs", "thread", "map", "asynch"] }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
rayon = { workspace = true } rayon = { workspace = true }
tower = { workspace = true } tower = { workspace = true }
futures = { workspace = true } futures = { workspace = true, features = ["std"] }
crossbeam = { workspace = true, features = ["std"] } crossbeam = { workspace = true, features = ["std"] }
[lints] [lints]

View file

@ -30,6 +30,14 @@ pub struct DatabaseWriteHandle<Req, Res> {
crossbeam::channel::Sender<(Req, oneshot::Sender<Result<Res, RuntimeError>>)>, crossbeam::channel::Sender<(Req, oneshot::Sender<Result<Res, RuntimeError>>)>,
} }
impl<Req, Res> Clone for DatabaseWriteHandle<Req, Res> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
}
}
}
impl<Req, Res> DatabaseWriteHandle<Req, Res> impl<Req, Res> DatabaseWriteHandle<Req, Res>
where where
Req: Send + 'static, Req: Send + 'static,

View file

@ -9,38 +9,38 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/storage/txpool"
keywords = ["cuprate", "txpool", "transaction", "pool", "database"] keywords = ["cuprate", "txpool", "transaction", "pool", "database"]
[features] [features]
default = ["heed", "service"] default = ["heed"]
# default = ["redb", "service"] # default = ["redb", "service"]
# default = ["redb-memory", "service"] # default = ["redb-memory", "service"]
heed = ["cuprate-database/heed"] heed = ["cuprate-database/heed"]
redb = ["cuprate-database/redb"] redb = ["cuprate-database/redb"]
redb-memory = ["cuprate-database/redb-memory"] redb-memory = ["cuprate-database/redb-memory"]
service = ["dep:tower", "dep:rayon", "dep:cuprate-database-service"]
serde = ["dep:serde", "cuprate-database/serde", "cuprate-database-service/serde"] serde = ["dep:serde", "cuprate-database/serde", "cuprate-database-service/serde"]
[dependencies] [dependencies]
cuprate-database = { path = "../database", features = ["heed"] } cuprate-database = { workspace = true, features = ["heed"] }
cuprate-database-service = { path = "../service", optional = true } cuprate-database-service = { workspace = true }
cuprate-types = { path = "../../types" } cuprate-types = { workspace = true }
cuprate-helper = { path = "../../helper", default-features = false, features = ["constants"] } cuprate-helper = { workspace = true, default-features = false, features = ["constants"] }
monero-serai = { workspace = true, features = ["std"] } monero-serai = { workspace = true, features = ["std"] }
bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] } bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] }
thiserror = { workspace = true } thiserror = { workspace = true }
hex = { workspace = true } hex = { workspace = true, features = ["std"] }
blake3 = { workspace = true, features = ["std"] }
tower = { workspace = true, optional = true } tower = { workspace = true }
rayon = { workspace = true, optional = true } rayon = { workspace = true }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
cuprate-test-utils = { path = "../../test-utils" } cuprate-test-utils = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }
hex-literal = { workspace = true } hex-literal = { workspace = true }
[lints] [lints]
workspace = true workspace = true

View file

@ -37,10 +37,6 @@ use cuprate_txpool::{
This ensures the types/traits used from `cuprate_database` are the same ones used by `cuprate_txpool` internally. This ensures the types/traits used from `cuprate_database` are the same ones used by `cuprate_txpool` internally.
# Feature flags # Feature flags
The `service` module requires the `service` feature to be enabled.
See the module for more documentation.
Different database backends are enabled by the feature flags: Different database backends are enabled by the feature flags:
- `heed` (LMDB) - `heed` (LMDB)

View file

@ -3,7 +3,7 @@
//---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Import
use cuprate_database::{ConcreteEnv, Env, EnvInner, InitError, RuntimeError, TxRw}; use cuprate_database::{ConcreteEnv, Env, EnvInner, InitError, RuntimeError, TxRw};
use crate::{config::Config, tables::OpenTables}; use crate::{config::Config, tables::OpenTables, types::TransactionBlobHash};
//---------------------------------------------------------------------------------------------------- Free functions //---------------------------------------------------------------------------------------------------- Free functions
/// Open the txpool database using the passed [`Config`]. /// Open the txpool database using the passed [`Config`].
@ -60,3 +60,13 @@ pub fn open(config: Config) -> Result<ConcreteEnv, InitError> {
Ok(env) Ok(env)
} }
/// Calculate the transaction blob hash.
///
/// This value is supposed to be quick to compute just based of the tx-blob without needing to parse the tx.
///
/// The exact way the hash is calculated is not stable and is subject to change, as such it should not be exposed
/// as a way to interact with Cuprate externally.
pub fn transaction_blob_hash(tx_blob: &[u8]) -> TransactionBlobHash {
blake3::hash(tx_blob).into()
}

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