Compare commits

...

7 commits

Author SHA1 Message Date
hinto.janai
250edbaec4
Merge branch 'main' into benches
Some checks failed
Deny / audit (push) Has been cancelled
2024-11-12 17:15:11 -05:00
5a5f88cb13
types: fix pruned BlockCompleteEntry (#338)
Some checks failed
Architecture mdBook / build (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
fix pruned `BlockCompleteEntry`
2024-11-11 23:16:08 +00:00
525e20e841
Fix ci and loosen version requirements (#335)
* add deny exception + loosen version requirements

* add a comment

* remove `expect`
2024-11-04 15:22:43 +00:00
SyntheticBird
b6c4adc83a
p2p: Implement P2P Bucket data structure (#329)
Implements P2P Bucket data structure

This commit implements a "Bucket" data structure that is a collection
of data that discriminates its items into "buckets" (vector of size N)
following a defined function.

- Implements Bucket data structure and Bucketable trait
- Implements Bucketable for Ipv4Addr
- Added the crate to the workspace dependencies
- Added arrayvec as a dependency
2024-11-02 00:45:56 +00: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
100 changed files with 2702 additions and 733 deletions

View file

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

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

@ -121,11 +121,14 @@ 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.**

422
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,7 @@ members = [
# P2P # P2P
"p2p/p2p", "p2p/p2p",
"p2p/p2p-core", "p2p/p2p-core",
"p2p/bucket",
"p2p/dandelion-tower", "p2p/dandelion-tower",
"p2p/async-buffer", "p2p/async-buffer",
"p2p/address-book", "p2p/address-book",
@ -76,6 +77,7 @@ cuprate-levin = { path = "net/levin" ,default-feature
cuprate-wire = { path = "net/wire" ,default-features = false} cuprate-wire = { path = "net/wire" ,default-features = false}
cuprate-p2p = { path = "p2p/p2p" ,default-features = false} cuprate-p2p = { path = "p2p/p2p" ,default-features = false}
cuprate-p2p-core = { path = "p2p/p2p-core" ,default-features = false} cuprate-p2p-core = { path = "p2p/p2p-core" ,default-features = false}
cuprate-p2p-bucket = { path = "p2p/p2p-bucket" ,default-features = false}
cuprate-dandelion-tower = { path = "p2p/dandelion-tower" ,default-features = false} cuprate-dandelion-tower = { path = "p2p/dandelion-tower" ,default-features = false}
cuprate-async-buffer = { path = "p2p/async-buffer" ,default-features = false} cuprate-async-buffer = { path = "p2p/async-buffer" ,default-features = false}
cuprate-address-book = { path = "p2p/address-book" ,default-features = false} cuprate-address-book = { path = "p2p/address-book" ,default-features = false}
@ -91,55 +93,57 @@ cuprate-rpc-types = { path = "rpc/types" ,default-feature
cuprate-rpc-interface = { path = "rpc/interface" ,default-features = false} cuprate-rpc-interface = { path = "rpc/interface" ,default-features = false}
# External dependencies # External dependencies
anyhow = { version = "1.0.89", default-features = false } anyhow = { version = "1", default-features = false }
async-trait = { version = "0.1.82", default-features = false } arrayvec = { version = "0.7", default-features = false }
bitflags = { version = "2.6.0", default-features = false } async-trait = { version = "0.1", default-features = false }
borsh = { version = "1.5.1", default-features = false } bitflags = { version = "2", default-features = false }
bytemuck = { version = "1.18.0", default-features = false } blake3 = { version = "1", default-features = false }
bytes = { version = "1.7.2", default-features = false } borsh = { version = "1", default-features = false }
cfg-if = { version = "1.0.0", default-features = false } bytemuck = { version = "1", default-features = false }
clap = { version = "4.5.17", default-features = false } bytes = { version = "1", default-features = false }
chrono = { version = "0.4.38", default-features = false } cfg-if = { version = "1", default-features = false }
crypto-bigint = { version = "0.5.5", default-features = false } clap = { version = "4", default-features = false }
crossbeam = { version = "0.8.4", default-features = false } chrono = { version = "0.4", default-features = false }
const_format = { version = "0.2.33", default-features = false } crypto-bigint = { version = "0.5", default-features = false }
curve25519-dalek = { version = "4.1.3", default-features = false } crossbeam = { version = "0.8", default-features = false }
dashmap = { version = "5.5.3", default-features = false } const_format = { version = "0.2", default-features = false }
dirs = { version = "5.0.1", default-features = false } curve25519-dalek = { version = "4", default-features = false }
futures = { version = "0.3.30", default-features = false } dashmap = { version = "6", default-features = false }
hex = { version = "0.4.3", default-features = false } dirs = { version = "5", default-features = false }
futures = { version = "0.3", default-features = false }
hex = { version = "0.4", default-features = false }
hex-literal = { version = "0.4", default-features = false } hex-literal = { version = "0.4", default-features = false }
indexmap = { version = "2.5.0", default-features = false } indexmap = { version = "2", default-features = false }
monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce", default-features = false } monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce", default-features = false }
paste = { version = "1.0.15", default-features = false } paste = { version = "1", default-features = false }
pin-project = { version = "1.1.5", default-features = false } pin-project = { version = "1", default-features = false }
randomx-rs = { git = "https://github.com/Cuprate/randomx-rs.git", rev = "0028464", default-features = false } randomx-rs = { git = "https://github.com/Cuprate/randomx-rs.git", rev = "0028464", default-features = false }
rand = { version = "0.8.5", default-features = false } rand = { version = "0.8", default-features = false }
rand_distr = { version = "0.4.3", default-features = false } rand_distr = { version = "0.4", default-features = false }
rayon = { version = "1.10.0", default-features = false } rayon = { version = "1", default-features = false }
serde_bytes = { version = "0.11.15", default-features = false } serde_bytes = { version = "0.11", default-features = false }
serde_json = { version = "1.0.128", default-features = false } serde_json = { version = "1", default-features = false }
serde = { version = "1.0.210", default-features = false } serde = { version = "1", default-features = false }
strum = { version = "0.26.3", default-features = false } strum = { version = "0.26", default-features = false }
thiserror = { version = "1.0.63", default-features = false } thiserror = { version = "1", default-features = false }
thread_local = { version = "1.1.8", default-features = false } thread_local = { version = "1", default-features = false }
tokio-util = { version = "0.7.12", default-features = false } tokio-util = { version = "0.7", default-features = false }
tokio-stream = { version = "0.1.16", default-features = false } tokio-stream = { version = "0.1", default-features = false }
tokio = { version = "1.40.0", default-features = false } tokio = { version = "1", default-features = false }
tower = { git = "https://github.com/Cuprate/tower.git", rev = "6c7faf0", default-features = false } # <https://github.com/tower-rs/tower/pull/796> tower = { git = "https://github.com/Cuprate/tower.git", rev = "6c7faf0", default-features = false } # <https://github.com/tower-rs/tower/pull/796>
tracing-subscriber = { version = "0.3.18", default-features = false } tracing-subscriber = { version = "0.3", default-features = false }
tracing = { version = "0.1.40", default-features = false } tracing = { version = "0.1", default-features = false }
## workspace.dev-dependencies ## workspace.dev-dependencies
criterion = { version = "0.5.1" } criterion = { version = "0.5" }
function_name = { version = "0.3.0" } function_name = { version = "0.3" }
tempfile = { version = "3.13.0" }
monero-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" } monero-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" } monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
pretty_assertions = { version = "1.4.1" } tempfile = { version = "3" }
proptest = { version = "1.5.0" } pretty_assertions = { version = "1" }
proptest-derive = { version = "0.4.0" } proptest = { version = "1" }
tokio-test = { version = "0.4.4" } proptest-derive = { version = "0.5" }
tokio-test = { version = "0.4" }
## TODO: ## TODO:
## Potential dependencies. ## Potential dependencies.

View file

@ -13,6 +13,7 @@ cuprate-consensus = { workspace = true }
cuprate-fast-sync = { workspace = true } cuprate-fast-sync = { workspace = true }
cuprate-consensus-context = { workspace = true } cuprate-consensus-context = { workspace = true }
cuprate-consensus-rules = { workspace = true } cuprate-consensus-rules = { workspace = true }
cuprate-constants = { workspace = true }
cuprate-cryptonight = { workspace = true } cuprate-cryptonight = { workspace = true }
cuprate-helper = { workspace = true } cuprate-helper = { workspace = true }
cuprate-epee-encoding = { workspace = true } cuprate-epee-encoding = { workspace = true }
@ -21,10 +22,10 @@ cuprate-levin = { workspace = true }
cuprate-wire = { workspace = true } cuprate-wire = { workspace = true }
cuprate-p2p = { workspace = true } cuprate-p2p = { workspace = true }
cuprate-p2p-core = { workspace = true } cuprate-p2p-core = { workspace = true }
cuprate-dandelion-tower = { workspace = true } cuprate-dandelion-tower = { workspace = true, features = ["txpool"] }
cuprate-async-buffer = { workspace = true } cuprate-async-buffer = { workspace = true }
cuprate-address-book = { workspace = true } cuprate-address-book = { workspace = true }
cuprate-blockchain = { workspace = true, features = ["service"] } cuprate-blockchain = { workspace = true }
cuprate-database-service = { workspace = true } cuprate-database-service = { workspace = true }
cuprate-txpool = { workspace = true } cuprate-txpool = { workspace = true }
cuprate-database = { workspace = true } cuprate-database = { 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

@ -18,6 +18,7 @@ use cuprate_p2p::{
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,
@ -46,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,
@ -80,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,
@ -102,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};
@ -17,16 +20,14 @@ use cuprate_consensus::{
use cuprate_consensus_context::NewBlockData; 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

@ -9,6 +9,10 @@
unused_variables, unused_variables,
clippy::needless_pass_by_value, clippy::needless_pass_by_value,
clippy::unused_async, clippy::unused_async,
clippy::diverging_sub_expression,
unused_mut,
clippy::let_unit_value,
clippy::needless_pass_by_ref_mut,
reason = "TODO: remove after v1.0.0" reason = "TODO: remove after v1.0.0"
)] )]

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 json; mod json;
mod other; mod other;

View file

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

View file

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

View file

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

View file

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

View file

@ -2,27 +2,30 @@
use std::convert::Infallible; use std::convert::Infallible;
use 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.
/// [`BlockChainContextRequest::Context`]. /// [`BlockChainContextRequest::Context`].
pub(super) async fn context( pub(crate) async fn context(
service: &mut BlockChainContextService, blockchain_context: &mut BlockChainContextService,
height: u64,
) -> Result<BlockChainContext, Error> { ) -> Result<BlockChainContext, Error> {
let BlockChainContextResponse::Context(context) = service let BlockChainContextResponse::Context(context) = blockchain_context
.ready() .ready()
.await .await
.expect("TODO") .map_err(|e| anyhow!(e))?
.call(BlockChainContextRequest::Context) .call(BlockChainContextRequest::Context)
.await .await
.expect("TODO") .map_err(|e| anyhow!(e))?
else { else {
unreachable!(); unreachable!();
}; };
@ -31,17 +34,17 @@ pub(super) async fn context(
} }
/// [`BlockChainContextRequest::HardForkInfo`]. /// [`BlockChainContextRequest::HardForkInfo`].
pub(super) async fn hard_fork_info( pub(crate) async fn hard_fork_info(
service: &mut BlockChainContextService, blockchain_context: &mut BlockChainContextService,
hard_fork: HardFork, hard_fork: HardFork,
) -> Result<HardForkInfo, Error> { ) -> Result<HardForkInfo, Error> {
let BlockChainContextResponse::HardForkInfo(hf_info) = service let BlockChainContextResponse::HardForkInfo(hf_info) = blockchain_context
.ready() .ready()
.await .await
.expect("TODO") .map_err(|e| anyhow!(e))?
.call(BlockChainContextRequest::HardForkInfo(hard_fork)) .call(BlockChainContextRequest::HardForkInfo(hard_fork))
.await .await
.expect("TODO") .map_err(|e| anyhow!(e))?
else { else {
unreachable!(); unreachable!();
}; };
@ -50,20 +53,47 @@ pub(super) async fn hard_fork_info(
} }
/// [`BlockChainContextRequest::FeeEstimate`]. /// [`BlockChainContextRequest::FeeEstimate`].
pub(super) async fn fee_estimate( pub(crate) async fn fee_estimate(
service: &mut BlockChainContextService, blockchain_context: &mut BlockChainContextService,
grace_blocks: u64, grace_blocks: u64,
) -> Result<FeeEstimate, Error> { ) -> Result<FeeEstimate, Error> {
let BlockChainContextResponse::FeeEstimate(fee) = service let BlockChainContextResponse::FeeEstimate(fee) = blockchain_context
.ready() .ready()
.await .await
.expect("TODO") .map_err(|e| anyhow!(e))?
.call(BlockChainContextRequest::FeeEstimate { grace_blocks }) .call(BlockChainContextRequest::FeeEstimate { grace_blocks })
.await .await
.expect("TODO") .map_err(|e| anyhow!(e))?
else { else {
unreachable!(); unreachable!();
}; };
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

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

View file

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

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

@ -35,6 +35,7 @@ cargo doc --open --package cuprate-blockchain
| [`cuprate-async-buffer`](https://doc.cuprate.org/cuprate_async_buffer) | [`p2p/async-buffer/`](https://github.com/Cuprate/cuprate/tree/main/p2p/async-buffer) | A bounded SPSC, FIFO, asynchronous buffer that supports arbitrary weights for values | [`cuprate-async-buffer`](https://doc.cuprate.org/cuprate_async_buffer) | [`p2p/async-buffer/`](https://github.com/Cuprate/cuprate/tree/main/p2p/async-buffer) | A bounded SPSC, FIFO, asynchronous buffer that supports arbitrary weights for values
| [`cuprate-dandelion-tower`](https://doc.cuprate.org/cuprate_dandelion_tower) | [`p2p/dandelion-tower/`](https://github.com/Cuprate/cuprate/tree/main/p2p/dandelion-tower) | TODO | [`cuprate-dandelion-tower`](https://doc.cuprate.org/cuprate_dandelion_tower) | [`p2p/dandelion-tower/`](https://github.com/Cuprate/cuprate/tree/main/p2p/dandelion-tower) | TODO
| [`cuprate-p2p`](https://doc.cuprate.org/cuprate_p2p) | [`p2p/p2p/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p) | TODO | [`cuprate-p2p`](https://doc.cuprate.org/cuprate_p2p) | [`p2p/p2p/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p) | TODO
| [`cuprate-p2p-bucket`](https://doc.cuprate.org/cuprate_p2p_bucket) | [`p2p/bucket/`](https://github.com/Cuprate/cuprate/tree/main/p2p/bucket) | A collection data structure discriminating its items into "buckets" of limited size.
| [`cuprate-p2p-core`](https://doc.cuprate.org/cuprate_p2p_core) | [`p2p/p2p-core/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p-core) | TODO | [`cuprate-p2p-core`](https://doc.cuprate.org/cuprate_p2p_core) | [`p2p/p2p-core/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p-core) | TODO
## Storage ## Storage

View file

@ -18,6 +18,7 @@ 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;
@ -267,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,
@ -364,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.

View file

@ -324,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

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

View file

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

View file

@ -17,7 +17,7 @@ asynch = ["dep:futures", "dep:rayon"]
cast = [] cast = []
constants = [] constants = []
crypto = ["dep:curve25519-dalek", "dep:monero-serai", "std"] crypto = ["dep:curve25519-dalek", "dep:monero-serai", "std"]
fs = ["dep:dirs"] 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"]

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 {

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;

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

@ -9,7 +9,7 @@ authors = ["Boog900"]
[dependencies] [dependencies]
cuprate-constants = { workspace = true } cuprate-constants = { workspace = true }
cuprate-pruning = { workspace = true } cuprate-pruning = { workspace = true }
cuprate-p2p-core = { workspace = true } 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"]}

View file

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

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

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

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

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

View file

@ -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

@ -13,6 +13,7 @@ borsh = ["dep:borsh", "cuprate-pruning/borsh"]
cuprate-helper = { workspace = true, features = ["asynch"], default-features = false } cuprate-helper = { workspace = true, features = ["asynch"], default-features = false }
cuprate-wire = { workspace = true, features = ["tracing"] } cuprate-wire = { workspace = true, features = ["tracing"] }
cuprate-pruning = { workspace = true } 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"] }

View file

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

View file

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

View file

@ -75,7 +75,6 @@ use cuprate_wire::{
NetworkAddress, NetworkAddress,
}; };
pub mod ban;
pub mod client; pub mod client;
mod constants; mod constants;
pub mod error; pub mod error;
@ -83,6 +82,7 @@ pub mod handles;
mod network_zones; mod network_zones;
pub mod protocol; pub mod protocol;
pub mod services; pub mod services;
pub mod types;
pub use error::*; pub use error::*;
pub use network_zones::{ClearNet, ClearNetServerCfg}; pub use network_zones::{ClearNet, ClearNetServerCfg};

View file

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

96
p2p/p2p-core/src/types.rs Normal file
View file

@ -0,0 +1,96 @@
//! General data structures.
use std::time::{Duration, Instant};
use cuprate_pruning::PruningSeed;
use cuprate_types::{AddressType, ConnectionState};
use crate::NetZoneAddress;
/// Data within [`crate::services::AddressBookRequest::SetBan`].
pub struct SetBan<A: NetZoneAddress> {
/// Address of the peer.
pub address: A,
/// - If [`Some`], how long this peer should be banned for
/// - If [`None`], the peer will be unbanned
pub ban: Option<Duration>,
}
/// Data within [`crate::services::AddressBookResponse::GetBans`].
pub struct BanState<A: NetZoneAddress> {
/// Address of the peer.
pub address: A,
/// - If [`Some`], the peer is banned until this [`Instant`]
/// - If [`None`], the peer is not currently banned
pub unban_instant: Option<Instant>,
}
/// Data within [`crate::services::AddressBookResponse::ConnectionInfo`].
pub struct ConnectionInfo<A: NetZoneAddress> {
// The following fields are mostly the same as `monerod`.
pub address: A,
pub address_type: AddressType,
pub avg_download: u64,
pub avg_upload: u64,
pub current_download: u64,
pub current_upload: u64,
pub height: u64,
/// Either a domain or an IP without the port.
pub host: String,
pub incoming: bool,
pub live_time: u64,
pub localhost: bool,
pub local_ip: bool,
pub peer_id: u64,
pub pruning_seed: PruningSeed,
pub recv_count: u64,
pub recv_idle_time: u64,
pub rpc_credits_per_hash: u32,
pub rpc_port: u16,
pub send_count: u64,
pub send_idle_time: u64,
pub state: ConnectionState,
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`.
///
// 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)]
pub struct Span<A: NetZoneAddress> {
pub nblocks: u64,
pub rate: u32,
pub remote_address: A,
pub size: u64,
pub speed: u32,
pub start_block_height: u64,
}

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,7 +10,7 @@ default = []
borsh = ["dep:borsh"] borsh = ["dep:borsh"]
[dependencies] [dependencies]
cuprate-constants = { workspace = true } cuprate-constants = { workspace = true, features = ["block"] }
thiserror = { workspace = true } thiserror = { workspace = true }

View file

@ -10,20 +10,20 @@ keywords = ["cuprate", "rpc", "interface"]
[features] [features]
default = ["dummy", "serde"] default = ["dummy", "serde"]
dummy = [] dummy = ["dep:cuprate-helper", "dep:futures"]
[dependencies] [dependencies]
cuprate-epee-encoding = { workspace = true, default-features = false } cuprate-epee-encoding = { workspace = true, default-features = false }
cuprate-json-rpc = { workspace = true, default-features = false } cuprate-json-rpc = { workspace = true, default-features = false }
cuprate-rpc-types = { workspace = true, features = ["serde", "epee"], default-features = false } cuprate-rpc-types = { workspace = true, features = ["serde", "epee"], default-features = false }
cuprate-helper = { workspace = true, 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 = { workspace = true } cuprate-test-utils = { workspace = true }

View file

@ -10,13 +10,13 @@ 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 = { workspace = true, optional = true } cuprate-epee-encoding = { workspace = true, optional = true }
cuprate-fixed-bytes = { workspace = true } cuprate-fixed-bytes = { workspace = true }
cuprate-types = { workspace = true, 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 }

View file

@ -58,61 +58,37 @@ pub struct ResponseBase {
} }
impl ResponseBase { impl ResponseBase {
/// `const` version of [`Default::default`]. /// [`Status::Ok`] and trusted [`Self`].
///
/// ```rust
/// use cuprate_rpc_types::{misc::*, base::*};
///
/// let new = ResponseBase::new();
/// assert_eq!(new, ResponseBase {
/// status: Status::Ok,
/// untrusted: false,
/// });
/// ```
pub const fn new() -> Self {
Self {
status: Status::Ok,
untrusted: false,
}
}
/// Returns OK and trusted [`Self`].
/// ///
/// This is the most common version of [`Self`]. /// This is the most common version of [`Self`].
/// ///
/// ```rust /// ```rust
/// use cuprate_rpc_types::{misc::*, base::*}; /// use cuprate_rpc_types::{misc::*, base::*};
/// ///
/// let ok = ResponseBase::ok(); /// assert_eq!(ResponseBase::OK, ResponseBase {
/// assert_eq!(ok, ResponseBase {
/// status: Status::Ok, /// status: Status::Ok,
/// untrusted: false, /// untrusted: false,
/// }); /// });
/// ``` /// ```
pub const fn ok() -> Self { pub const OK: Self = Self {
Self {
status: Status::Ok, status: Status::Ok,
untrusted: false, untrusted: false,
} };
}
/// Same as [`Self::ok`] but with [`Self::untrusted`] set to `true`. /// Same as [`Self::OK`] but with [`Self::untrusted`] set to `true`.
/// ///
/// ```rust /// ```rust
/// use cuprate_rpc_types::{misc::*, base::*}; /// use cuprate_rpc_types::{misc::*, base::*};
/// ///
/// let ok_untrusted = ResponseBase::ok_untrusted(); /// assert_eq!(ResponseBase::OK_UNTRUSTED, ResponseBase {
/// assert_eq!(ok_untrusted, ResponseBase {
/// status: Status::Ok, /// status: Status::Ok,
/// untrusted: true, /// untrusted: true,
/// }); /// });
/// ``` /// ```
pub const fn ok_untrusted() -> Self { pub const OK_UNTRUSTED: Self = Self {
Self {
status: Status::Ok, status: Status::Ok,
untrusted: true, untrusted: true,
} };
}
} }
#[cfg(feature = "epee")] #[cfg(feature = "epee")]
@ -148,9 +124,9 @@ impl AccessResponseBase {
/// ```rust /// ```rust
/// use cuprate_rpc_types::{misc::*, base::*}; /// use cuprate_rpc_types::{misc::*, base::*};
/// ///
/// let new = AccessResponseBase::new(ResponseBase::ok()); /// let new = AccessResponseBase::new(ResponseBase::OK);
/// assert_eq!(new, AccessResponseBase { /// assert_eq!(new, AccessResponseBase {
/// response_base: ResponseBase::ok(), /// response_base: ResponseBase::OK,
/// credits: 0, /// credits: 0,
/// top_hash: "".into(), /// top_hash: "".into(),
/// }); /// });
@ -163,47 +139,41 @@ impl AccessResponseBase {
} }
} }
/// Returns OK and trusted [`Self`]. /// [`Status::Ok`] and trusted [`Self`].
/// ///
/// This is the most common version of [`Self`]. /// This is the most common version of [`Self`].
/// ///
/// ```rust /// ```rust
/// use cuprate_rpc_types::{misc::*, base::*}; /// use cuprate_rpc_types::{misc::*, base::*};
/// ///
/// let ok = AccessResponseBase::ok(); /// assert_eq!(AccessResponseBase::OK, AccessResponseBase {
/// assert_eq!(ok, AccessResponseBase { /// response_base: ResponseBase::OK,
/// response_base: ResponseBase::ok(),
/// credits: 0, /// credits: 0,
/// top_hash: "".into(), /// top_hash: "".into(),
/// }); /// });
/// ``` /// ```
pub const fn ok() -> Self { pub const OK: Self = Self {
Self { response_base: ResponseBase::OK,
response_base: ResponseBase::ok(),
credits: 0, credits: 0,
top_hash: String::new(), top_hash: String::new(),
} };
}
/// Same as [`Self::ok`] but with `untrusted` set to `true`. /// Same as [`Self::OK`] but with `untrusted` set to `true`.
/// ///
/// ```rust /// ```rust
/// use cuprate_rpc_types::{misc::*, base::*}; /// use cuprate_rpc_types::{misc::*, base::*};
/// ///
/// let ok_untrusted = AccessResponseBase::ok_untrusted(); /// assert_eq!(AccessResponseBase::OK_UNTRUSTED, AccessResponseBase {
/// assert_eq!(ok_untrusted, AccessResponseBase { /// response_base: ResponseBase::OK_UNTRUSTED,
/// response_base: ResponseBase::ok_untrusted(),
/// credits: 0, /// credits: 0,
/// top_hash: "".into(), /// top_hash: "".into(),
/// }); /// });
/// ``` /// ```
pub const fn ok_untrusted() -> Self { pub const OK_UNTRUSTED: Self = Self {
Self { response_base: ResponseBase::OK_UNTRUSTED,
response_base: ResponseBase::ok_untrusted(),
credits: 0, credits: 0,
top_hash: String::new(), top_hash: String::new(),
} };
}
} }
#[cfg(feature = "epee")] #[cfg(feature = "epee")]

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`.
/// ///
@ -184,7 +186,7 @@ define_request_and_response! {
// <https://serde.rs/field-attrs.html#flatten>. // <https://serde.rs/field-attrs.html#flatten>.
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_BLOCK_TEMPLATE_RESPONSE => GetBlockTemplateResponse { GET_BLOCK_TEMPLATE_RESPONSE => GetBlockTemplateResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
blockhashing_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a00000000e0c20372be23d356347091025c5b5e8f2abf83ab618378565cce2b703491523401".into(), blockhashing_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a00000000e0c20372be23d356347091025c5b5e8f2abf83ab618378565cce2b703491523401".into(),
blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(), blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(),
difficulty_top64: 0, difficulty_top64: 0,
@ -240,7 +242,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_BLOCK_COUNT_RESPONSE => GetBlockCountResponse { GET_BLOCK_COUNT_RESPONSE => GetBlockCountResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
count: 3195019, count: 3195019,
} }
)] )]
@ -332,7 +334,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GENERATE_BLOCKS_RESPONSE => GenerateBlocksResponse { GENERATE_BLOCKS_RESPONSE => GenerateBlocksResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
blocks: vec!["49b712db7760e3728586f8434ee8bc8d7b3d410dac6bb6e98bf5845c83b917e4".into()], blocks: vec!["49b712db7760e3728586f8434ee8bc8d7b3d410dac6bb6e98bf5845c83b917e4".into()],
height: 9783, height: 9783,
} }
@ -357,7 +359,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_LAST_BLOCK_HEADER_RESPONSE => GetLastBlockHeaderResponse { GET_LAST_BLOCK_HEADER_RESPONSE => GetLastBlockHeaderResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
block_header: BlockHeader { block_header: BlockHeader {
block_size: 200419, block_size: 200419,
block_weight: 200419, block_weight: 200419,
@ -409,7 +411,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_BLOCK_HEADER_BY_HASH_RESPONSE => GetBlockHeaderByHashResponse { GET_BLOCK_HEADER_BY_HASH_RESPONSE => GetBlockHeaderByHashResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
block_headers: vec![], block_headers: vec![],
block_header: BlockHeader { block_header: BlockHeader {
block_size: 210, block_size: 210,
@ -464,7 +466,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_BLOCK_HEADER_BY_HEIGHT_RESPONSE => GetBlockHeaderByHeightResponse { GET_BLOCK_HEADER_BY_HEIGHT_RESPONSE => GetBlockHeaderByHeightResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
block_header: BlockHeader { block_header: BlockHeader {
block_size: 210, block_size: 210,
block_weight: 210, block_weight: 210,
@ -519,7 +521,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_BLOCK_HEADERS_RANGE_RESPONSE => GetBlockHeadersRangeResponse { GET_BLOCK_HEADERS_RANGE_RESPONSE => GetBlockHeadersRangeResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
headers: vec![ headers: vec![
BlockHeader { BlockHeader {
block_size: 301413, block_size: 301413,
@ -601,7 +603,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_BLOCK_RESPONSE => GetBlockResponse { GET_BLOCK_RESPONSE => GetBlockResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
blob: "1010c58bab9b06b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7807e07f502cef8a70101ff92f8a7010180e0a596bb1103d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85d034019f629d8b36bd16a2bfce3ea80c31dc4d8762c67165aec21845494e32b7582fe00211000000297a787a000000000000000000000000".into(), blob: "1010c58bab9b06b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7807e07f502cef8a70101ff92f8a7010180e0a596bb1103d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85d034019f629d8b36bd16a2bfce3ea80c31dc4d8762c67165aec21845494e32b7582fe00211000000297a787a000000000000000000000000".into(),
block_header: BlockHeader { block_header: BlockHeader {
block_size: 106, block_size: 106,
@ -654,11 +656,11 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_CONNECTIONS_RESPONSE => GetConnectionsResponse { GET_CONNECTIONS_RESPONSE => GetConnectionsResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
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
} }
], ],
@ -728,7 +730,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_INFO_RESPONSE => GetInfoResponse { GET_INFO_RESPONSE => GetInfoResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
adjusted_time: 1721245289, adjusted_time: 1721245289,
alt_blocks_count: 16, alt_blocks_count: 16,
block_size_limit: 600000, block_size_limit: 600000,
@ -831,7 +833,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
HARD_FORK_INFO_RESPONSE => HardForkInfoResponse { HARD_FORK_INFO_RESPONSE => HardForkInfoResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
earliest_height: 2689608, earliest_height: 2689608,
enabled: true, enabled: true,
state: 0, state: 0,
@ -877,7 +879,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
SET_BANS_RESPONSE => SetBansResponse { SET_BANS_RESPONSE => SetBansResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
} }
)] )]
ResponseBase {} ResponseBase {}
@ -892,7 +894,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_BANS_RESPONSE => GetBansResponse { GET_BANS_RESPONSE => GetBansResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
bans: vec![ bans: vec![
GetBan { GetBan {
host: "104.248.206.131".into(), host: "104.248.206.131".into(),
@ -994,7 +996,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_OUTPUT_HISTOGRAM_RESPONSE => GetOutputHistogramResponse { GET_OUTPUT_HISTOGRAM_RESPONSE => GetOutputHistogramResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
histogram: vec![HistogramEntry { histogram: vec![HistogramEntry {
amount: 20000000000, amount: 20000000000,
recent_instances: 0, recent_instances: 0,
@ -1028,7 +1030,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_COINBASE_TX_SUM_RESPONSE => GetCoinbaseTxSumResponse { GET_COINBASE_TX_SUM_RESPONSE => GetCoinbaseTxSumResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
emission_amount: 9387854817320, emission_amount: 9387854817320,
emission_amount_top64: 0, emission_amount_top64: 0,
fee_amount: 83981380000, fee_amount: 83981380000,
@ -1057,7 +1059,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_VERSION_RESPONSE => GetVersionResponse { GET_VERSION_RESPONSE => GetVersionResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
current_height: 3195051, current_height: 3195051,
hard_forks: vec![ hard_forks: vec![
HardforkEntry { HardforkEntry {
@ -1143,12 +1145,16 @@ define_request_and_response! {
get_fee_estimate, get_fee_estimate,
cc73fe71162d564ffda8e549b79a350bca53c454 => cc73fe71162d564ffda8e549b79a350bca53c454 =>
core_rpc_server_commands_defs.h => 2250..=2277, core_rpc_server_commands_defs.h => 2250..=2277,
GetFeeEstimate (empty),
Request {}, GetFeeEstimate,
Request {
grace_blocks: u64 = default_zero::<u64>(), "default_zero",
},
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_FEE_ESTIMATE_RESPONSE => GetFeeEstimateResponse { GET_FEE_ESTIMATE_RESPONSE => GetFeeEstimateResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
fee: 20000, fee: 20000,
fees: vec![20000,80000,320000,4000000], fees: vec![20000,80000,320000,4000000],
quantization_mask: 10000, quantization_mask: 10000,
@ -1170,7 +1176,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_ALTERNATE_CHAINS_RESPONSE => GetAlternateChainsResponse { GET_ALTERNATE_CHAINS_RESPONSE => GetAlternateChainsResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
chains: vec![ chains: vec![
ChainInfo { ChainInfo {
block_hash: "4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce".into(), block_hash: "4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce".into(),
@ -1238,7 +1244,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
SYNC_INFO_RESPONSE => SyncInfoResponse { SYNC_INFO_RESPONSE => SyncInfoResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
height: 3195157, height: 3195157,
next_needed_pruning_seed: 0, next_needed_pruning_seed: 0,
overview: "[]".into(), overview: "[]".into(),
@ -1247,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(),
@ -1269,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(),
@ -1298,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
} }
} }
@ -1328,7 +1334,7 @@ define_request_and_response! {
// TODO: enable test after binary string impl. // TODO: enable test after binary string impl.
// #[doc = serde_doc_test!( // #[doc = serde_doc_test!(
// GET_TRANSACTION_POOL_BACKLOG_RESPONSE => GetTransactionPoolBacklogResponse { // GET_TRANSACTION_POOL_BACKLOG_RESPONSE => GetTransactionPoolBacklogResponse {
// base: ResponseBase::ok(), // base: ResponseBase::OK,
// backlog: "...Binary...".into(), // backlog: "...Binary...".into(),
// } // }
// )] // )]
@ -1370,7 +1376,7 @@ define_request_and_response! {
// TODO: enable test after binary string impl. // TODO: enable test after binary string impl.
// #[doc = serde_doc_test!( // #[doc = serde_doc_test!(
// GET_OUTPUT_DISTRIBUTION_RESPONSE => GetOutputDistributionResponse { // GET_OUTPUT_DISTRIBUTION_RESPONSE => GetOutputDistributionResponse {
// base: AccessResponseBase::ok(), // base: AccessResponseBase::OK,
// distributions: vec![Distribution::Uncompressed(DistributionUncompressed { // distributions: vec![Distribution::Uncompressed(DistributionUncompressed {
// start_height: 1462078, // start_height: 1462078,
// base: 0, // base: 0,
@ -1394,7 +1400,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_MINER_DATA_RESPONSE => GetMinerDataResponse { GET_MINER_DATA_RESPONSE => GetMinerDataResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
already_generated_coins: 18186022843595960691, already_generated_coins: 18186022843595960691,
difficulty: "0x48afae42de".into(), difficulty: "0x48afae42de".into(),
height: 2731375, height: 2731375,
@ -1447,7 +1453,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
PRUNE_BLOCKCHAIN_RESPONSE => PruneBlockchainResponse { PRUNE_BLOCKCHAIN_RESPONSE => PruneBlockchainResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
pruned: true, pruned: true,
pruning_seed: 387, pruning_seed: 387,
} }
@ -1513,7 +1519,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
FLUSH_CACHE_RESPONSE => FlushCacheResponse { FLUSH_CACHE_RESPONSE => FlushCacheResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
} }
)] )]
ResponseBase {} ResponseBase {}
@ -1542,7 +1548,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
ADD_AUX_POW_RESPONSE => AddAuxPowResponse { ADD_AUX_POW_RESPONSE => AddAuxPowResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
aux_pow: vec![AuxPow { aux_pow: vec![AuxPow {
hash: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into(), hash: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into(),
id: "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8".into(), id: "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8".into(),

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: u8, 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`.
/// ///
@ -102,7 +104,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_HEIGHT_RESPONSE => GetHeightResponse { GET_HEIGHT_RESPONSE => GetHeightResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
hash: "68bb1a1cff8e2a44c3221e8e1aff80bc6ca45d06fa8eff4d2a3a7ac31d4efe3f".into(), hash: "68bb1a1cff8e2a44c3221e8e1aff80bc6ca45d06fa8eff4d2a3a7ac31d4efe3f".into(),
height: 3195160, height: 3195160,
} }
@ -157,7 +159,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_ALT_BLOCKS_HASHES_RESPONSE => GetAltBlocksHashesResponse { GET_ALT_BLOCKS_HASHES_RESPONSE => GetAltBlocksHashesResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
blks_hashes: vec!["8ee10db35b1baf943f201b303890a29e7d45437bd76c2bd4df0d2f2ee34be109".into()], blks_hashes: vec!["8ee10db35b1baf943f201b303890a29e7d45437bd76c2bd4df0d2f2ee34be109".into()],
} }
)] )]
@ -187,7 +189,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
IS_KEY_IMAGE_SPENT_RESPONSE => IsKeyImageSpentResponse { IS_KEY_IMAGE_SPENT_RESPONSE => IsKeyImageSpentResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
spent_status: vec![1, 1], spent_status: vec![1, 1],
} }
)] )]
@ -283,7 +285,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
START_MINING_RESPONSE => StartMiningResponse { START_MINING_RESPONSE => StartMiningResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
} }
)] )]
ResponseBase {} ResponseBase {}
@ -298,7 +300,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
STOP_MINING_RESPONSE => StopMiningResponse { STOP_MINING_RESPONSE => StopMiningResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
} }
)] )]
ResponseBase {} ResponseBase {}
@ -313,7 +315,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
MINING_STATUS_RESPONSE => MiningStatusResponse { MINING_STATUS_RESPONSE => MiningStatusResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
active: false, active: false,
address: "".into(), address: "".into(),
bg_idle_threshold: 0, bg_idle_threshold: 0,
@ -359,7 +361,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
SAVE_BC_RESPONSE => SaveBcResponse { SAVE_BC_RESPONSE => SaveBcResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
} }
)] )]
ResponseBase {} ResponseBase {}
@ -385,7 +387,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_PEER_LIST_RESPONSE => GetPeerListResponse { GET_PEER_LIST_RESPONSE => GetPeerListResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
gray_list: vec![ gray_list: vec![
Peer { Peer {
host: "161.97.193.0".into(), host: "161.97.193.0".into(),
@ -467,7 +469,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
SET_LOG_HASH_RATE_RESPONSE => SetLogHashRateResponse { SET_LOG_HASH_RATE_RESPONSE => SetLogHashRateResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
} }
)] )]
ResponseBase {} ResponseBase {}
@ -492,7 +494,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
SET_LOG_LEVEL_RESPONSE => SetLogLevelResponse { SET_LOG_LEVEL_RESPONSE => SetLogLevelResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
} }
)] )]
ResponseBase {} ResponseBase {}
@ -516,7 +518,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
SET_LOG_CATEGORIES_RESPONSE => SetLogCategoriesResponse { SET_LOG_CATEGORIES_RESPONSE => SetLogCategoriesResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
categories: "*:INFO".into(), categories: "*:INFO".into(),
} }
)] )]
@ -582,7 +584,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_TRANSACTION_POOL_STATS_RESPONSE => GetTransactionPoolStatsResponse { GET_TRANSACTION_POOL_STATS_RESPONSE => GetTransactionPoolStatsResponse {
base: AccessResponseBase::ok(), base: AccessResponseBase::OK,
pool_stats: TxpoolStats { pool_stats: TxpoolStats {
bytes_max: 11843, bytes_max: 11843,
bytes_med: 2219, bytes_med: 2219,
@ -644,7 +646,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_LIMIT_RESPONSE => GetLimitResponse { GET_LIMIT_RESPONSE => GetLimitResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
limit_down: 1280000, limit_down: 1280000,
limit_up: 1280000, limit_up: 1280000,
} }
@ -676,7 +678,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
SET_LIMIT_RESPONSE => SetLimitResponse { SET_LIMIT_RESPONSE => SetLimitResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
limit_down: 1024, limit_down: 1024,
limit_up: 128, limit_up: 128,
} }
@ -707,7 +709,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
OUT_PEERS_RESPONSE => OutPeersResponse { OUT_PEERS_RESPONSE => OutPeersResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
out_peers: 3232235535, out_peers: 3232235535,
} }
)] )]
@ -740,7 +742,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_NET_STATS_RESPONSE => GetNetStatsResponse { GET_NET_STATS_RESPONSE => GetNetStatsResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
start_time: 1721251858, start_time: 1721251858,
total_bytes_in: 16283817214, total_bytes_in: 16283817214,
total_bytes_out: 34225244079, total_bytes_out: 34225244079,
@ -779,7 +781,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_OUTS_RESPONSE => GetOutsResponse { GET_OUTS_RESPONSE => GetOutsResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
outs: vec![ outs: vec![
OutKey { OutKey {
height: 51941, height: 51941,
@ -823,7 +825,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
UPDATE_RESPONSE => UpdateResponse { UPDATE_RESPONSE => UpdateResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
auto_uri: "".into(), auto_uri: "".into(),
hash: "".into(), hash: "".into(),
path: "".into(), path: "".into(),
@ -860,7 +862,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
POP_BLOCKS_RESPONSE => PopBlocksResponse { POP_BLOCKS_RESPONSE => PopBlocksResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
height: 76482, height: 76482,
} }
)] )]
@ -879,7 +881,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_TRANSACTION_POOL_HASHES_RESPONSE => GetTransactionPoolHashesResponse { GET_TRANSACTION_POOL_HASHES_RESPONSE => GetTransactionPoolHashesResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
tx_hashes: vec![ tx_hashes: vec![
"aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03".into(), "aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03".into(),
"794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11".into(), "794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11".into(),
@ -929,7 +931,7 @@ define_request_and_response! {
#[doc = serde_doc_test!( #[doc = serde_doc_test!(
GET_PUBLIC_NODES_RESPONSE => GetPublicNodesResponse { GET_PUBLIC_NODES_RESPONSE => GetPublicNodesResponse {
base: ResponseBase::ok(), base: ResponseBase::OK,
gray: vec![], gray: vec![],
white: vec![ white: vec![
PublicNode { PublicNode {

View file

@ -9,32 +9,31 @@ 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 = { workspace = true } cuprate-database = { workspace = true }
cuprate-database-service = { workspace = true } cuprate-database-service = { workspace = true }
cuprate-helper = { workspace = true, features = ["fs", "map", "crypto"] } cuprate-helper = { workspace = true, features = ["fs", "map", "crypto", "tx", "thread"] }
cuprate-types = { workspace = true, features = ["blockchain"] } cuprate-types = { workspace = true, features = ["blockchain"] }
cuprate-pruning = { workspace = true } 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 = { workspace = true } cuprate-constants = { 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

@ -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

@ -121,6 +121,8 @@ fn map_request(
R::DatabaseSize => database_size(env), R::DatabaseSize => database_size(env),
R::OutputHistogram(input) => output_histogram(env, input), R::OutputHistogram(input) => output_histogram(env, input),
R::CoinbaseTxSum { height, count } => coinbase_tx_sum(env, height, count), R::CoinbaseTxSum { height, count } => coinbase_tx_sum(env, height, count),
R::AltChains => alt_chains(env),
R::AltChainCount => alt_chain_count(env),
} }
/* SOMEDAY: post-request handling, run some code for each request? */ /* SOMEDAY: post-request handling, run some code for each request? */
@ -648,3 +650,13 @@ fn output_histogram(env: &ConcreteEnv, input: OutputHistogramInput) -> ResponseR
fn coinbase_tx_sum(env: &ConcreteEnv, height: usize, count: u64) -> ResponseResult { fn coinbase_tx_sum(env: &ConcreteEnv, height: usize, count: u64) -> ResponseResult {
Ok(BlockchainResponse::CoinbaseTxSum(todo!())) Ok(BlockchainResponse::CoinbaseTxSum(todo!()))
} }
/// [`BlockchainReadRequest::AltChains`]
fn alt_chains(env: &ConcreteEnv) -> ResponseResult {
Ok(BlockchainResponse::AltChains(todo!()))
}
/// [`BlockchainReadRequest::AltChainCount`]
fn alt_chain_count(env: &ConcreteEnv) -> ResponseResult {
Ok(BlockchainResponse::AltChainCount(todo!()))
}

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 = { workspace = true } cuprate-database = { workspace = true }
cuprate-helper = { workspace = true, 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,18 +9,17 @@ 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 = { workspace = true, features = ["heed"] } cuprate-database = { workspace = true, features = ["heed"] }
cuprate-database-service = { workspace = true, optional = true } cuprate-database-service = { workspace = true }
cuprate-types = { workspace = true } cuprate-types = { workspace = true }
cuprate-helper = { workspace = true, default-features = false, features = ["constants"] } cuprate-helper = { workspace = true, default-features = false, features = ["constants"] }
@ -28,10 +27,11 @@ 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 }

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()
}

View file

@ -4,24 +4,24 @@
clippy::significant_drop_tightening clippy::significant_drop_tightening
)] )]
// Used in docs: <https://github.com/Cuprate/cuprate/pull/170#discussion_r1823644357>.
use tower as _;
pub mod config; pub mod config;
mod free; mod free;
pub mod ops; pub mod ops;
#[cfg(feature = "service")]
pub mod service; pub mod service;
pub mod tables; pub mod tables;
mod tx; mod tx;
pub mod types; pub mod types;
pub use config::Config; pub use config::Config;
pub use free::open; pub use free::{open, transaction_blob_hash};
pub use tx::TxEntry; pub use tx::TxEntry;
//re-exports //re-exports
pub use cuprate_database; pub use cuprate_database;
// TODO: remove when used.
use tower as _;
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use cuprate_test_utils as _; use cuprate_test_utils as _;

View file

@ -85,7 +85,7 @@ mod key_images;
mod tx_read; mod tx_read;
mod tx_write; mod tx_write;
pub use tx_read::get_transaction_verification_data; pub use tx_read::{get_transaction_verification_data, in_stem_pool};
pub use tx_write::{add_transaction, remove_transaction}; pub use tx_write::{add_transaction, remove_transaction};
/// An error that can occur on some tx-write ops. /// An error that can occur on some tx-write ops.

View file

@ -8,7 +8,10 @@ use monero_serai::transaction::Transaction;
use cuprate_database::{DatabaseRo, RuntimeError}; use cuprate_database::{DatabaseRo, RuntimeError};
use cuprate_types::{TransactionVerificationData, TxVersion}; use cuprate_types::{TransactionVerificationData, TxVersion};
use crate::{tables::Tables, types::TransactionHash}; use crate::{
tables::{Tables, TransactionInfos},
types::{TransactionHash, TxStateFlags},
};
/// Gets the [`TransactionVerificationData`] of a transaction in the tx-pool, leaving the tx in the pool. /// Gets the [`TransactionVerificationData`] of a transaction in the tx-pool, leaving the tx in the pool.
pub fn get_transaction_verification_data( pub fn get_transaction_verification_data(
@ -34,3 +37,17 @@ pub fn get_transaction_verification_data(
cached_verification_state: Mutex::new(cached_verification_state), cached_verification_state: Mutex::new(cached_verification_state),
}) })
} }
/// Returns `true` if the transaction with the given hash is in the stem pool.
///
/// # Errors
/// This will return an [`Err`] if the transaction is not in the pool.
pub fn in_stem_pool(
tx_hash: &TransactionHash,
tx_infos: &impl DatabaseRo<TransactionInfos>,
) -> Result<bool, RuntimeError> {
Ok(tx_infos
.get(tx_hash)?
.flags
.contains(TxStateFlags::STATE_STEM))
}

View file

@ -8,6 +8,7 @@ use cuprate_database::{DatabaseRw, RuntimeError, StorableVec};
use cuprate_types::TransactionVerificationData; use cuprate_types::TransactionVerificationData;
use crate::{ use crate::{
free::transaction_blob_hash,
ops::{ ops::{
key_images::{add_tx_key_images, remove_tx_key_images}, key_images::{add_tx_key_images, remove_tx_key_images},
TxPoolWriteError, TxPoolWriteError,
@ -56,6 +57,12 @@ pub fn add_transaction(
let kis_table = tables.spent_key_images_mut(); let kis_table = tables.spent_key_images_mut();
add_tx_key_images(&tx.tx.prefix().inputs, &tx.tx_hash, kis_table)?; add_tx_key_images(&tx.tx.prefix().inputs, &tx.tx_hash, kis_table)?;
// Add the blob hash to table 4.
let blob_hash = transaction_blob_hash(&tx.tx_blob);
tables
.known_blob_hashes_mut()
.put(&blob_hash, &tx.tx_hash)?;
Ok(()) Ok(())
} }
@ -79,5 +86,9 @@ pub fn remove_transaction(
let kis_table = tables.spent_key_images_mut(); let kis_table = tables.spent_key_images_mut();
remove_tx_key_images(&tx.prefix().inputs, kis_table)?; remove_tx_key_images(&tx.prefix().inputs, kis_table)?;
// Remove the blob hash from table 4.
let blob_hash = transaction_blob_hash(&tx_blob);
tables.known_blob_hashes_mut().delete(&blob_hash)?;
Ok(()) Ok(())
} }

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:
//! - [`TxpoolReadHandle`] //! - [`TxpoolReadHandle`]
@ -42,7 +40,7 @@
//! To interact with the database (whether reading or writing data), //! To interact with the database (whether reading or writing data),
//! a `Request` can be sent using one of the above handles. //! a `Request` can be sent using one of the above handles.
//! //!
//! Both the handles implement `tower::Service`, so they can be [`tower::Service::call`]ed. //! Both the handles implement [`tower::Service`], so they can be [`tower::Service::call`]ed.
//! //!
//! An `async`hronous channel will be returned from the call. //! An `async`hronous channel will be returned from the call.
//! This channel can be `.await`ed upon to (eventually) receive //! This channel can be `.await`ed upon to (eventually) receive

View file

@ -1,44 +1,76 @@
//! Tx-pool [`service`](super) interface. //! Tx-pool [`service`](super) interface.
//! //!
//! This module contains `cuprate_txpool`'s [`tower::Service`] request and response enums. //! This module contains `cuprate_txpool`'s [`tower::Service`] request and response enums.
use std::sync::Arc; use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use cuprate_types::TransactionVerificationData; use cuprate_types::TransactionVerificationData;
use crate::{tx::TxEntry, types::TransactionHash}; use crate::{
tx::TxEntry,
types::{KeyImage, TransactionBlobHash, TransactionHash},
};
//---------------------------------------------------------------------------------------------------- TxpoolReadRequest //---------------------------------------------------------------------------------------------------- TxpoolReadRequest
/// The transaction pool [`tower::Service`] read request type. /// The transaction pool [`tower::Service`] read request type.
#[derive(Clone)]
pub enum TxpoolReadRequest { pub enum TxpoolReadRequest {
/// A request for the blob (raw bytes) of a transaction with the given hash. /// Get the blob (raw bytes) of a transaction with the given hash.
TxBlob(TransactionHash), TxBlob(TransactionHash),
/// A request for the [`TransactionVerificationData`] of a transaction in the tx pool. /// Get the [`TransactionVerificationData`] of a transaction in the tx pool.
TxVerificationData(TransactionHash), TxVerificationData(TransactionHash),
/// Filter (remove) all **known** transactions from the set.
///
/// The hash is **not** the transaction hash, it is the hash of the serialized tx-blob.
FilterKnownTxBlobHashes(HashSet<TransactionBlobHash>),
/// Get some transactions for an incoming block.
TxsForBlock(Vec<TransactionHash>),
/// Get information on all transactions in the pool. /// Get information on all transactions in the pool.
Backlog, Backlog,
/// Get the number of transactions in the pool. /// Get the number of transactions in the pool.
Size, Size {
/// If this is [`true`], the size returned will
/// include private transactions in the pool.
include_sensitive_txs: bool,
},
} }
//---------------------------------------------------------------------------------------------------- TxpoolReadResponse //---------------------------------------------------------------------------------------------------- TxpoolReadResponse
/// The transaction pool [`tower::Service`] read response type. /// The transaction pool [`tower::Service`] read response type.
#[expect(clippy::large_enum_variant)] #[expect(clippy::large_enum_variant)]
pub enum TxpoolReadResponse { pub enum TxpoolReadResponse {
/// Response to [`TxpoolReadRequest::TxBlob`]. /// The response for [`TxpoolReadRequest::TxBlob`].
/// TxBlob { tx_blob: Vec<u8>, state_stem: bool },
/// The inner value is the raw bytes of a transaction.
// TODO: use bytes::Bytes.
TxBlob(Vec<u8>),
/// Response to [`TxpoolReadRequest::TxVerificationData`]. /// The response for [`TxpoolReadRequest::TxVerificationData`].
TxVerificationData(TransactionVerificationData), TxVerificationData(TransactionVerificationData),
/// The response for [`TxpoolReadRequest::FilterKnownTxBlobHashes`].
FilterKnownTxBlobHashes {
/// The blob hashes that are unknown.
unknown_blob_hashes: HashSet<TransactionBlobHash>,
/// The tx hashes of the blob hashes that were known but were in the stem pool.
stem_pool_hashes: Vec<TransactionHash>,
},
/// The response for [`TxpoolReadRequest::TxsForBlock`].
TxsForBlock {
/// The txs we had in the txpool.
txs: HashMap<[u8; 32], TransactionVerificationData>,
/// The indexes of the missing txs.
missing: Vec<usize>,
},
/// Response to [`TxpoolReadRequest::Backlog`]. /// Response to [`TxpoolReadRequest::Backlog`].
/// ///
/// The inner `Vec` contains information on all /// The inner [`Vec`] contains information on all
/// the transactions currently in the pool. /// the transactions currently in the pool.
Backlog(Vec<TxEntry>), Backlog(Vec<TxEntry>),
@ -66,9 +98,17 @@ pub enum TxpoolWriteRequest {
}, },
/// Remove a transaction with the given hash from the pool. /// Remove a transaction with the given hash from the pool.
///
/// Returns [`TxpoolWriteResponse::Ok`].
RemoveTransaction(TransactionHash), RemoveTransaction(TransactionHash),
/// Promote a transaction from the stem pool to the fluff pool.
/// If the tx is already in the fluff pool this does nothing.
Promote(TransactionHash),
/// Tell the tx-pool about a new block.
NewBlock {
/// The spent key images in the new block.
spent_key_images: Vec<KeyImage>,
},
} }
//---------------------------------------------------------------------------------------------------- TxpoolWriteResponse //---------------------------------------------------------------------------------------------------- TxpoolWriteResponse
@ -77,6 +117,8 @@ pub enum TxpoolWriteRequest {
pub enum TxpoolWriteResponse { pub enum TxpoolWriteResponse {
/// Response to: /// Response to:
/// - [`TxpoolWriteRequest::RemoveTransaction`] /// - [`TxpoolWriteRequest::RemoveTransaction`]
/// - [`TxpoolWriteRequest::Promote`]
/// - [`TxpoolWriteRequest::NewBlock`]
Ok, Ok,
/// Response to [`TxpoolWriteRequest::AddTransaction`]. /// Response to [`TxpoolWriteRequest::AddTransaction`].

View file

@ -4,22 +4,24 @@
clippy::unnecessary_wraps, clippy::unnecessary_wraps,
reason = "TODO: finish implementing the signatures from <https://github.com/Cuprate/cuprate/pull/297>" reason = "TODO: finish implementing the signatures from <https://github.com/Cuprate/cuprate/pull/297>"
)] )]
use std::{
use std::sync::Arc; collections::{HashMap, HashSet},
sync::Arc,
};
use rayon::ThreadPool; use rayon::ThreadPool;
use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner}; use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner, RuntimeError};
use cuprate_database_service::{init_thread_pool, DatabaseReadService, ReaderThreads}; use cuprate_database_service::{init_thread_pool, DatabaseReadService, ReaderThreads};
use crate::{ use crate::{
ops::get_transaction_verification_data, ops::{get_transaction_verification_data, in_stem_pool},
service::{ service::{
interface::{TxpoolReadRequest, TxpoolReadResponse}, interface::{TxpoolReadRequest, TxpoolReadResponse},
types::{ReadResponseResult, TxpoolReadHandle}, types::{ReadResponseResult, TxpoolReadHandle},
}, },
tables::{OpenTables, TransactionBlobs}, tables::{KnownBlobHashes, OpenTables, TransactionBlobs, TransactionInfos},
types::TransactionHash, types::{TransactionBlobHash, TransactionHash},
}; };
// TODO: update the docs here // TODO: update the docs here
@ -57,7 +59,6 @@ fn init_read_service_with_pool(env: Arc<ConcreteEnv>, pool: Arc<ThreadPool>) ->
/// 1. `Request` is mapped to a handler function /// 1. `Request` is mapped to a handler function
/// 2. Handler function is called /// 2. Handler function is called
/// 3. [`TxpoolReadResponse`] is returned /// 3. [`TxpoolReadResponse`] is returned
#[expect(clippy::needless_pass_by_value)]
fn map_request( fn map_request(
env: &ConcreteEnv, // Access to the database env: &ConcreteEnv, // Access to the database
request: TxpoolReadRequest, // The request we must fulfill request: TxpoolReadRequest, // The request we must fulfill
@ -65,8 +66,14 @@ fn map_request(
match request { match request {
TxpoolReadRequest::TxBlob(tx_hash) => tx_blob(env, &tx_hash), TxpoolReadRequest::TxBlob(tx_hash) => tx_blob(env, &tx_hash),
TxpoolReadRequest::TxVerificationData(tx_hash) => tx_verification_data(env, &tx_hash), TxpoolReadRequest::TxVerificationData(tx_hash) => tx_verification_data(env, &tx_hash),
TxpoolReadRequest::FilterKnownTxBlobHashes(blob_hashes) => {
filter_known_tx_blob_hashes(env, blob_hashes)
}
TxpoolReadRequest::TxsForBlock(txs_needed) => txs_for_block(env, txs_needed),
TxpoolReadRequest::Backlog => backlog(env), TxpoolReadRequest::Backlog => backlog(env),
TxpoolReadRequest::Size => size(env), TxpoolReadRequest::Size {
include_sensitive_txs,
} => size(env, include_sensitive_txs),
} }
} }
@ -94,10 +101,14 @@ fn tx_blob(env: &ConcreteEnv, tx_hash: &TransactionHash) -> ReadResponseResult {
let tx_ro = inner_env.tx_ro()?; let tx_ro = inner_env.tx_ro()?;
let tx_blobs_table = inner_env.open_db_ro::<TransactionBlobs>(&tx_ro)?; let tx_blobs_table = inner_env.open_db_ro::<TransactionBlobs>(&tx_ro)?;
let tx_infos_table = inner_env.open_db_ro::<TransactionInfos>(&tx_ro)?;
tx_blobs_table let tx_blob = tx_blobs_table.get(tx_hash)?.0;
.get(tx_hash)
.map(|blob| TxpoolReadResponse::TxBlob(blob.0)) Ok(TxpoolReadResponse::TxBlob {
tx_blob,
state_stem: in_stem_pool(tx_hash, &tx_infos_table)?,
})
} }
/// [`TxpoolReadRequest::TxVerificationData`]. /// [`TxpoolReadRequest::TxVerificationData`].
@ -111,6 +122,79 @@ fn tx_verification_data(env: &ConcreteEnv, tx_hash: &TransactionHash) -> ReadRes
get_transaction_verification_data(tx_hash, &tables).map(TxpoolReadResponse::TxVerificationData) get_transaction_verification_data(tx_hash, &tables).map(TxpoolReadResponse::TxVerificationData)
} }
/// [`TxpoolReadRequest::FilterKnownTxBlobHashes`].
fn filter_known_tx_blob_hashes(
env: &ConcreteEnv,
mut blob_hashes: HashSet<TransactionBlobHash>,
) -> ReadResponseResult {
let inner_env = env.env_inner();
let tx_ro = inner_env.tx_ro()?;
let tx_blob_hashes = inner_env.open_db_ro::<KnownBlobHashes>(&tx_ro)?;
let tx_infos = inner_env.open_db_ro::<TransactionInfos>(&tx_ro)?;
let mut stem_pool_hashes = Vec::new();
// A closure that returns `true` if a tx with a certain blob hash is unknown.
// This also fills in `stem_tx_hashes`.
let mut tx_unknown = |blob_hash| -> Result<bool, RuntimeError> {
match tx_blob_hashes.get(&blob_hash) {
Ok(tx_hash) => {
if in_stem_pool(&tx_hash, &tx_infos)? {
stem_pool_hashes.push(tx_hash);
}
Ok(false)
}
Err(RuntimeError::KeyNotFound) => Ok(true),
Err(e) => Err(e),
}
};
let mut err = None;
blob_hashes.retain(|blob_hash| match tx_unknown(*blob_hash) {
Ok(res) => res,
Err(e) => {
err = Some(e);
false
}
});
if let Some(e) = err {
return Err(e);
}
Ok(TxpoolReadResponse::FilterKnownTxBlobHashes {
unknown_blob_hashes: blob_hashes,
stem_pool_hashes,
})
}
/// [`TxpoolReadRequest::TxsForBlock`].
fn txs_for_block(env: &ConcreteEnv, txs: Vec<TransactionHash>) -> ReadResponseResult {
let inner_env = env.env_inner();
let tx_ro = inner_env.tx_ro()?;
let tables = inner_env.open_tables(&tx_ro)?;
let mut missing_tx_indexes = Vec::with_capacity(txs.len());
let mut txs_verification_data = HashMap::with_capacity(txs.len());
for (i, tx_hash) in txs.into_iter().enumerate() {
match get_transaction_verification_data(&tx_hash, &tables) {
Ok(tx) => {
txs_verification_data.insert(tx_hash, tx);
}
Err(RuntimeError::KeyNotFound) => missing_tx_indexes.push(i),
Err(e) => return Err(e),
}
}
Ok(TxpoolReadResponse::TxsForBlock {
txs: txs_verification_data,
missing: missing_tx_indexes,
})
}
/// [`TxpoolReadRequest::Backlog`]. /// [`TxpoolReadRequest::Backlog`].
#[inline] #[inline]
fn backlog(env: &ConcreteEnv) -> ReadResponseResult { fn backlog(env: &ConcreteEnv) -> ReadResponseResult {
@ -119,6 +203,6 @@ fn backlog(env: &ConcreteEnv) -> ReadResponseResult {
/// [`TxpoolReadRequest::Size`]. /// [`TxpoolReadRequest::Size`].
#[inline] #[inline]
fn size(env: &ConcreteEnv) -> ReadResponseResult { fn size(env: &ConcreteEnv, include_sensitive_txs: bool) -> ReadResponseResult {
Ok(TxpoolReadResponse::Size(todo!())) Ok(TxpoolReadResponse::Size(todo!()))
} }

View file

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use cuprate_database::{ConcreteEnv, Env, EnvInner, RuntimeError, TxRw}; use cuprate_database::{ConcreteEnv, DatabaseRo, DatabaseRw, Env, EnvInner, RuntimeError, TxRw};
use cuprate_database_service::DatabaseWriteHandle; use cuprate_database_service::DatabaseWriteHandle;
use cuprate_types::TransactionVerificationData; use cuprate_types::TransactionVerificationData;
@ -10,8 +10,8 @@ use crate::{
interface::{TxpoolWriteRequest, TxpoolWriteResponse}, interface::{TxpoolWriteRequest, TxpoolWriteResponse},
types::TxpoolWriteHandle, types::TxpoolWriteHandle,
}, },
tables::OpenTables, tables::{OpenTables, Tables, TransactionInfos},
types::TransactionHash, types::{KeyImage, TransactionHash, TxStateFlags},
}; };
//---------------------------------------------------------------------------------------------------- init_write_service //---------------------------------------------------------------------------------------------------- init_write_service
@ -31,6 +31,8 @@ fn handle_txpool_request(
add_transaction(env, tx, *state_stem) add_transaction(env, tx, *state_stem)
} }
TxpoolWriteRequest::RemoveTransaction(tx_hash) => remove_transaction(env, tx_hash), TxpoolWriteRequest::RemoveTransaction(tx_hash) => remove_transaction(env, tx_hash),
TxpoolWriteRequest::Promote(tx_hash) => promote(env, tx_hash),
TxpoolWriteRequest::NewBlock { spent_key_images } => new_block(env, spent_key_images),
} }
} }
@ -101,3 +103,68 @@ fn remove_transaction(
TxRw::commit(tx_rw)?; TxRw::commit(tx_rw)?;
Ok(TxpoolWriteResponse::Ok) Ok(TxpoolWriteResponse::Ok)
} }
/// [`TxpoolWriteRequest::Promote`]
fn promote(
env: &ConcreteEnv,
tx_hash: &TransactionHash,
) -> Result<TxpoolWriteResponse, RuntimeError> {
let env_inner = env.env_inner();
let tx_rw = env_inner.tx_rw()?;
let res = || {
let mut tx_infos = env_inner.open_db_rw::<TransactionInfos>(&tx_rw)?;
tx_infos.update(tx_hash, |mut info| {
info.flags.remove(TxStateFlags::STATE_STEM);
Some(info)
})
};
if let Err(e) = res() {
// error promoting the tx, abort the DB transaction.
TxRw::abort(tx_rw)
.expect("could not maintain database atomicity by aborting write transaction");
return Err(e);
}
TxRw::commit(tx_rw)?;
Ok(TxpoolWriteResponse::Ok)
}
/// [`TxpoolWriteRequest::NewBlock`]
fn new_block(
env: &ConcreteEnv,
spent_key_images: &[KeyImage],
) -> Result<TxpoolWriteResponse, RuntimeError> {
let env_inner = env.env_inner();
let tx_rw = env_inner.tx_rw()?;
// FIXME: use try blocks once stable.
let result = || {
let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?;
// Remove all txs which spend key images that were spent in the new block.
for key_image in spent_key_images {
match tables_mut
.spent_key_images()
.get(key_image)
.and_then(|tx_hash| ops::remove_transaction(&tx_hash, &mut tables_mut))
{
Ok(()) | Err(RuntimeError::KeyNotFound) => (),
Err(e) => return Err(e),
}
}
Ok(())
};
if let Err(e) = result() {
TxRw::abort(tx_rw)?;
return Err(e);
}
TxRw::commit(tx_rw)?;
Ok(TxpoolWriteResponse::Ok)
}

View file

@ -16,7 +16,9 @@
//! accessing _all_ tables defined here at once. //! accessing _all_ tables defined here at once.
use cuprate_database::{define_tables, StorableVec}; use cuprate_database::{define_tables, StorableVec};
use crate::types::{KeyImage, RawCachedVerificationState, TransactionHash, TransactionInfo}; use crate::types::{
KeyImage, RawCachedVerificationState, TransactionBlobHash, TransactionHash, TransactionInfo,
};
define_tables! { define_tables! {
/// Serialized transaction blobs. /// Serialized transaction blobs.
@ -41,5 +43,9 @@ define_tables! {
/// ///
/// This table contains the spent key images from all transactions in the pool. /// This table contains the spent key images from all transactions in the pool.
3 => SpentKeyImages, 3 => SpentKeyImages,
KeyImage => TransactionHash KeyImage => TransactionHash,
/// Transaction blob hashes that are in the pool.
4 => KnownBlobHashes,
TransactionBlobHash => TransactionHash,
} }

View file

@ -5,6 +5,8 @@
/// Used in [`TxpoolReadResponse::Backlog`](crate::service::interface::TxpoolReadResponse::Backlog). /// Used in [`TxpoolReadResponse::Backlog`](crate::service::interface::TxpoolReadResponse::Backlog).
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct TxEntry { pub struct TxEntry {
/// The transaction's ID (hash).
pub id: [u8; 32],
/// The transaction's weight. /// The transaction's weight.
pub weight: u64, pub weight: u64,
/// The transaction's fee. /// The transaction's fee.

View file

@ -6,7 +6,6 @@
//! //!
//! <!-- FIXME: Add schema here or a link to it when complete --> //! <!-- FIXME: Add schema here or a link to it when complete -->
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use monero_serai::transaction::Timelock; use monero_serai::transaction::Timelock;
use cuprate_types::{CachedVerificationState, HardFork}; use cuprate_types::{CachedVerificationState, HardFork};
@ -17,6 +16,9 @@ pub type KeyImage = [u8; 32];
/// A transaction hash. /// A transaction hash.
pub type TransactionHash = [u8; 32]; pub type TransactionHash = [u8; 32];
/// A transaction blob hash.
pub type TransactionBlobHash = [u8; 32];
bitflags::bitflags! { bitflags::bitflags! {
/// Flags representing the state of the transaction in the pool. /// Flags representing the state of the transaction in the pool.
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)]

View file

@ -12,21 +12,23 @@ keywords = ["cuprate", "types"]
default = ["blockchain", "epee", "serde", "json", "hex"] default = ["blockchain", "epee", "serde", "json", "hex"]
blockchain = [] blockchain = []
epee = ["dep:cuprate-epee-encoding"] epee = ["dep:cuprate-epee-encoding"]
serde = ["dep:serde"] serde = ["dep:serde", "hex"]
proptest = ["dep:proptest", "dep:proptest-derive"] proptest = ["dep:proptest", "dep:proptest-derive"]
json = ["hex", "dep:cuprate-helper"] json = ["hex", "dep:cuprate-helper"]
hex = ["dep:hex"] # We sadly have no choice but to enable serde here as otherwise we will get warnings from the `hex` dep being unused.
# This isn't too bad as `HexBytes` only makes sense with serde anyway.
hex = ["serde", "dep:hex"]
[dependencies] [dependencies]
cuprate-epee-encoding = { workspace = true, optional = true, features = ["std"] } cuprate-epee-encoding = { workspace = true, optional = true, features = ["std"] }
cuprate-helper = { workspace = true, optional = true, features = ["cast"] } cuprate-helper = { workspace = true, optional = true, features = ["cast"] }
cuprate-fixed-bytes = { workspace = true } cuprate-fixed-bytes = { workspace = true, features = ["std", "serde"] }
bytes = { workspace = true } bytes = { workspace = true }
curve25519-dalek = { workspace = true } curve25519-dalek = { workspace = true }
monero-serai = { workspace = true } monero-serai = { workspace = true }
hex = { workspace = true, features = ["serde", "alloc"], optional = true } hex = { workspace = true, features = ["serde", "alloc"], optional = true }
serde = { workspace = true, features = ["derive"], optional = true } serde = { workspace = true, features = ["std", "derive"], optional = true }
strum = { workspace = true, features = ["derive"] } strum = { workspace = true, features = ["derive"] }
thiserror = { workspace = true } thiserror = { workspace = true }

147
types/src/address_type.rs Normal file
View file

@ -0,0 +1,147 @@
//! Types of network addresses; used in P2P.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "epee")]
use cuprate_epee_encoding::{
error,
macros::bytes::{Buf, BufMut},
EpeeValue, Marker,
};
use strum::{
AsRefStr, Display, EnumCount, EnumIs, EnumString, FromRepr, IntoStaticStr, VariantArray,
};
/// An enumeration of address types.
///
/// Used in `cuprate_p2p` and `cuprate_types`
///
/// Original definition:
/// - <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/epee/include/net/enums.h/#L49>
///
/// # Serde
/// This type's `serde` implementation (de)serializes from a [`u8`].
///
/// ```rust
/// use cuprate_types::AddressType as A;
/// use serde_json::{to_string, from_str};
///
/// assert_eq!(from_str::<A>(&"0").unwrap(), A::Invalid);
/// assert_eq!(from_str::<A>(&"1").unwrap(), A::Ipv4);
/// assert_eq!(from_str::<A>(&"2").unwrap(), A::Ipv6);
/// assert_eq!(from_str::<A>(&"3").unwrap(), A::I2p);
/// assert_eq!(from_str::<A>(&"4").unwrap(), A::Tor);
///
/// assert_eq!(to_string(&A::Invalid).unwrap(), "0");
/// assert_eq!(to_string(&A::Ipv4).unwrap(), "1");
/// assert_eq!(to_string(&A::Ipv6).unwrap(), "2");
/// assert_eq!(to_string(&A::I2p).unwrap(), "3");
/// assert_eq!(to_string(&A::Tor).unwrap(), "4");
/// ```
#[derive(
Copy,
Clone,
Default,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRefStr,
Display,
EnumCount,
EnumIs,
EnumString,
FromRepr,
IntoStaticStr,
VariantArray,
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged, try_from = "u8", into = "u8"))]
#[repr(u8)]
pub enum AddressType {
#[default]
Invalid,
Ipv4,
Ipv6,
I2p,
Tor,
}
impl AddressType {
/// Convert [`Self`] to a [`u8`].
///
/// ```rust
/// use cuprate_types::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_types::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,
})
}
}
impl From<AddressType> for u8 {
fn from(value: AddressType) -> Self {
value.to_u8()
}
}
impl TryFrom<u8> for AddressType {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match Self::from_u8(value) {
Some(s) => Ok(s),
None => Err(value),
}
}
}
#[cfg(feature = "epee")]
impl EpeeValue for AddressType {
const MARKER: Marker = u8::MARKER;
fn read<B: Buf>(r: &mut B, marker: &Marker) -> error::Result<Self> {
let u = u8::read(r, marker)?;
Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 4"))
}
fn write<B: BufMut>(self, w: &mut B) -> error::Result<()> {
let u = self.to_u8();
u8::write(u, w)?;
Ok(())
}
}

View file

@ -136,7 +136,7 @@ impl TransactionBlobs {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PrunedTxBlobEntry { pub struct PrunedTxBlobEntry {
/// The transaction. /// The transaction.
pub tx: Bytes, pub blob: Bytes,
/// The prunable transaction hash. /// The prunable transaction hash.
pub prunable_hash: ByteArray<32>, pub prunable_hash: ByteArray<32>,
} }
@ -144,7 +144,7 @@ pub struct PrunedTxBlobEntry {
#[cfg(feature = "epee")] #[cfg(feature = "epee")]
epee_object!( epee_object!(
PrunedTxBlobEntry, PrunedTxBlobEntry,
tx: Bytes, blob: Bytes,
prunable_hash: ByteArray<32>, prunable_hash: ByteArray<32>,
); );

View file

@ -12,7 +12,8 @@ use monero_serai::block::Block;
use crate::{ use crate::{
types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation}, types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation},
AltBlockInformation, ChainId, CoinbaseTxSum, OutputHistogramEntry, OutputHistogramInput, AltBlockInformation, ChainId, ChainInfo, CoinbaseTxSum, OutputHistogramEntry,
OutputHistogramInput,
}; };
//---------------------------------------------------------------------------------------------------- ReadRequest //---------------------------------------------------------------------------------------------------- ReadRequest
@ -128,6 +129,12 @@ pub enum BlockchainReadRequest {
/// ///
/// TODO: document fields after impl. /// TODO: document fields after impl.
CoinbaseTxSum { height: usize, count: u64 }, CoinbaseTxSum { height: usize, count: u64 },
/// Get information on all alternative chains.
AltChains,
/// Get the amount of alternative chains that exist.
AltChainCount,
} }
//---------------------------------------------------------------------------------------------------- WriteRequest //---------------------------------------------------------------------------------------------------- WriteRequest
@ -276,6 +283,12 @@ pub enum BlockchainResponse {
/// Response to [`BlockchainReadRequest::CoinbaseTxSum`]. /// Response to [`BlockchainReadRequest::CoinbaseTxSum`].
CoinbaseTxSum(CoinbaseTxSum), CoinbaseTxSum(CoinbaseTxSum),
/// Response to [`BlockchainReadRequest::AltChains`].
AltChains(Vec<ChainInfo>),
/// Response to [`BlockchainReadRequest::AltChainCount`].
AltChainCount(usize),
//------------------------------------------------------ Writes //------------------------------------------------------ Writes
/// A generic Ok response to indicate a request was successfully handled. /// A generic Ok response to indicate a request was successfully handled.
/// ///

View file

@ -0,0 +1,148 @@
//! [`ConnectionState`].
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "epee")]
use cuprate_epee_encoding::{
error,
macros::bytes::{Buf, BufMut},
EpeeValue, Marker,
};
use strum::{
AsRefStr, Display, EnumCount, EnumIs, EnumString, FromRepr, IntoStaticStr, VariantArray,
};
/// An enumeration of P2P connection states.
///
/// Used in `cuprate_p2p` and `cuprate_rpc_types`.
///
/// Original definition:
/// - <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/cryptonote_basic/connection_context.h#L49>
///
/// # Serde
/// This type's `serde` implementation depends on `snake_case`.
///
/// ```rust
/// use cuprate_types::ConnectionState as C;
/// use serde_json::to_string;
///
/// assert_eq!(to_string(&C::BeforeHandshake).unwrap(), r#""before_handshake""#);
/// assert_eq!(to_string(&C::Synchronizing).unwrap(), r#""synchronizing""#);
/// assert_eq!(to_string(&C::Standby).unwrap(), r#""standby""#);
/// assert_eq!(to_string(&C::Idle).unwrap(), r#""idle""#);
/// assert_eq!(to_string(&C::Normal).unwrap(), r#""normal""#);
///
/// assert_eq!(C::BeforeHandshake.to_string(), "before_handshake");
/// assert_eq!(C::Synchronizing.to_string(), "synchronizing");
/// assert_eq!(C::Standby.to_string(), "standby");
/// assert_eq!(C::Idle.to_string(), "idle");
/// assert_eq!(C::Normal.to_string(), "normal");
/// ```
#[derive(
Copy,
Clone,
Default,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRefStr,
Display,
EnumCount,
EnumIs,
EnumString,
FromRepr,
IntoStaticStr,
VariantArray,
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] // cuprate-rpc-types depends on snake_case
#[strum(serialize_all = "snake_case")]
#[repr(u8)]
pub enum ConnectionState {
BeforeHandshake,
Synchronizing,
Standby,
Idle,
#[default]
Normal,
}
impl ConnectionState {
/// Convert [`Self`] to a [`u8`].
///
/// ```rust
/// use cuprate_types::ConnectionState as C;
///
/// assert_eq!(C::BeforeHandshake.to_u8(), 0);
/// assert_eq!(C::Synchronizing.to_u8(), 1);
/// assert_eq!(C::Standby.to_u8(), 2);
/// assert_eq!(C::Idle.to_u8(), 3);
/// assert_eq!(C::Normal.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_types::ConnectionState as C;
///
/// assert_eq!(C::from_u8(0), Some(C::BeforeHandshake));
/// assert_eq!(C::from_u8(1), Some(C::Synchronizing));
/// assert_eq!(C::from_u8(2), Some(C::Standby));
/// assert_eq!(C::from_u8(3), Some(C::Idle));
/// assert_eq!(C::from_u8(4), Some(C::Normal));
/// assert_eq!(C::from_u8(5), None);
/// ```
pub const fn from_u8(u: u8) -> Option<Self> {
Some(match u {
0 => Self::BeforeHandshake,
1 => Self::Synchronizing,
2 => Self::Standby,
3 => Self::Idle,
4 => Self::Normal,
_ => return None,
})
}
}
impl From<ConnectionState> for u8 {
fn from(value: ConnectionState) -> Self {
value.to_u8()
}
}
impl TryFrom<u8> for ConnectionState {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match Self::from_u8(value) {
Some(s) => Ok(s),
None => Err(value),
}
}
}
#[cfg(feature = "epee")]
impl EpeeValue for ConnectionState {
const MARKER: Marker = u8::MARKER;
fn read<B: Buf>(r: &mut B, marker: &Marker) -> error::Result<Self> {
let u = u8::read(r, marker)?;
Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 4"))
}
fn write<B: BufMut>(self, w: &mut B) -> error::Result<()> {
let u = self.to_u8();
u8::write(u, w)?;
Ok(())
}
}

View file

@ -22,6 +22,7 @@ pub struct HexBytes<const N: usize>(
#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] pub [u8; N], #[cfg_attr(feature = "serde", serde(with = "hex::serde"))] pub [u8; N],
); );
#[cfg(feature = "serde")]
impl<'de, const N: usize> Deserialize<'de> for HexBytes<N> impl<'de, const N: usize> Deserialize<'de> for HexBytes<N>
where where
[u8; N]: hex::FromHex, [u8; N]: hex::FromHex,

View file

@ -51,17 +51,17 @@ impl From<block::Block> for Block {
/// [`Block::miner_tx`]. /// [`Block::miner_tx`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[serde(untagged)] #[cfg_attr(feature = "serde", serde(untagged))]
pub enum MinerTransaction { pub enum MinerTransaction {
V1 { V1 {
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten). /// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[serde(flatten)] #[cfg_attr(feature = "serde", serde(flatten))]
prefix: MinerTransactionPrefix, prefix: MinerTransactionPrefix,
signatures: [(); 0], signatures: [(); 0],
}, },
V2 { V2 {
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten). /// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[serde(flatten)] #[cfg_attr(feature = "serde", serde(flatten))]
prefix: MinerTransactionPrefix, prefix: MinerTransactionPrefix,
rct_signatures: MinerTransactionRctSignatures, rct_signatures: MinerTransactionRctSignatures,
}, },

View file

@ -20,7 +20,7 @@ pub struct Output {
/// [`Output::target`]. /// [`Output::target`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[serde(untagged)] #[cfg_attr(feature = "serde", serde(untagged))]
pub enum Target { pub enum Target {
Key { key: HexBytes<32> }, Key { key: HexBytes<32> },
TaggedKey { tagged_key: TaggedKey }, TaggedKey { tagged_key: TaggedKey },

View file

@ -24,17 +24,17 @@ use crate::{
/// - [`/get_transaction_pool` -> `tx_json`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_transaction_pool) /// - [`/get_transaction_pool` -> `tx_json`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_transaction_pool)
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[serde(untagged)] #[cfg_attr(feature = "serde", serde(untagged))]
pub enum Transaction { pub enum Transaction {
V1 { V1 {
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten). /// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[serde(flatten)] #[cfg_attr(feature = "serde", serde(flatten))]
prefix: TransactionPrefix, prefix: TransactionPrefix,
signatures: Vec<HexBytes<64>>, signatures: Vec<HexBytes<64>>,
}, },
V2 { V2 {
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten). /// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[serde(flatten)] #[cfg_attr(feature = "serde", serde(flatten))]
prefix: TransactionPrefix, prefix: TransactionPrefix,
rct_signatures: RctSignatures, rct_signatures: RctSignatures,
/// This field is [`Some`] if [`Self::V2::rct_signatures`] /// This field is [`Some`] if [`Self::V2::rct_signatures`]

View file

@ -9,20 +9,25 @@
// //
// Documentation for each module is located in the respective file. // Documentation for each module is located in the respective file.
mod address_type;
mod block_complete_entry; mod block_complete_entry;
mod connection_state;
mod hard_fork; mod hard_fork;
mod transaction_verification_data; mod transaction_verification_data;
mod types; mod types;
pub use address_type::AddressType;
pub use block_complete_entry::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs}; pub use block_complete_entry::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs};
pub use connection_state::ConnectionState;
pub use hard_fork::{HardFork, HardForkError}; pub use hard_fork::{HardFork, HardForkError};
pub use transaction_verification_data::{ pub use transaction_verification_data::{
CachedVerificationState, TransactionVerificationData, TxVersion, CachedVerificationState, TransactionVerificationData, TxVersion,
}; };
pub use types::{ pub use types::{
AltBlockInformation, Chain, ChainId, ChainInfo, CoinbaseTxSum, ExtendedBlockHeader, AddAuxPow, AltBlockInformation, AuxPow, Chain, ChainId, ChainInfo, CoinbaseTxSum,
FeeEstimate, HardForkInfo, MinerData, MinerDataTxBacklogEntry, OutputHistogramEntry, ExtendedBlockHeader, FeeEstimate, HardForkInfo, MinerData, MinerDataTxBacklogEntry,
OutputHistogramInput, OutputOnChain, VerifiedBlockInformation, VerifiedTransactionInformation, OutputHistogramEntry, OutputHistogramInput, OutputOnChain, VerifiedBlockInformation,
VerifiedTransactionInformation,
}; };
//---------------------------------------------------------------------------------------------------- Feature-gated //---------------------------------------------------------------------------------------------------- Feature-gated

View file

@ -177,8 +177,6 @@ pub struct OutputHistogramEntry {
pub struct CoinbaseTxSum { pub struct CoinbaseTxSum {
pub emission_amount: u128, pub emission_amount: u128,
pub fee_amount: u128, pub fee_amount: u128,
pub wide_emission_amount: u128,
pub wide_fee_amount: u128,
} }
/// Data to create a custom block template. /// Data to create a custom block template.
@ -242,7 +240,23 @@ pub struct ChainInfo {
pub height: u64, pub height: u64,
pub length: u64, pub length: u64,
pub main_chain_parent_block: [u8; 32], pub main_chain_parent_block: [u8; 32],
pub wide_difficulty: u128, }
/// Used in RPC's `add_aux_pow`.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AuxPow {
pub id: [u8; 32],
pub hash: [u8; 32],
}
/// Used in RPC's `add_aux_pow`.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AddAuxPow {
pub blocktemplate_blob: Vec<u8>,
pub blockhashing_blob: Vec<u8>,
pub merkle_root: [u8; 32],
pub merkle_tree_depth: u64,
pub aux_pow: Vec<AuxPow>,
} }
//---------------------------------------------------------------------------------------------------- Tests //---------------------------------------------------------------------------------------------------- Tests