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
run: |
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`
- 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:
| Command | Does what |
|------------------------------------------------------------------------|-----------|
| `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 test --all-features --workspace` | Runs all tests
| `cargo build --all-features --all-targets --workspace` | Builds all code
|------------------------------------------------------------------------|-------------------------------------------------------------------------|
| `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 test --all-features --workspace` | Runs all tests |
| `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.**

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

View file

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

View file

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

View file

@ -8,17 +8,16 @@ use std::{
};
use monero_serai::{block::Block, transaction::Transaction};
use rayon::prelude::*;
use tokio::sync::{mpsc, oneshot};
use tower::{Service, ServiceExt};
use cuprate_blockchain::service::BlockchainReadHandle;
use cuprate_consensus::transactions::new_tx_verification_data;
use cuprate_helper::cast::usize_to_u64;
use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse},
Chain,
use cuprate_txpool::service::{
interface::{TxpoolReadRequest, TxpoolReadResponse},
TxpoolReadHandle,
};
use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
use crate::{
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.
#[error("Unknown transactions in block.")]
UnknownTransactions([u8; 32], Vec<u64>),
UnknownTransactions([u8; 32], Vec<usize>),
/// We are missing the block's parent.
#[error("The block has an unknown parent.")]
Orphan,
@ -59,8 +58,9 @@ pub enum IncomingBlockError {
/// - the block's parent is unknown
pub async fn handle_incoming_block(
block: Block,
given_txs: Vec<Transaction>,
mut given_txs: HashMap<[u8; 32], Transaction>,
blockchain_read_handle: &mut BlockchainReadHandle,
txpool_read_handle: &mut TxpoolReadHandle,
) -> Result<IncomingBlockOk, IncomingBlockError> {
/// 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.
static BLOCKS_BEING_HANDLED: LazyLock<Mutex<HashSet<[u8; 32]>>> =
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)
.await
@ -90,23 +95,36 @@ pub async fn handle_incoming_block(
return Ok(IncomingBlockOk::AlreadyHave);
}
// TODO: remove this when we have a working tx-pool.
if given_txs.len() != block.transactions.len() {
return Err(IncomingBlockError::UnknownTransactions(
block_hash,
(0..usize_to_u64(block.transactions.len())).collect(),
));
}
let TxpoolReadResponse::TxsForBlock { mut txs, missing } = txpool_read_handle
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(TxpoolReadRequest::TxsForBlock(block.transactions.clone()))
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
else {
unreachable!()
};
// TODO: check we actually got given the right txs.
let prepped_txs = given_txs
.into_par_iter()
.map(|tx| {
let tx = new_tx_verification_data(tx)?;
Ok((tx.tx_hash, tx))
})
.collect::<Result<_, anyhow::Error>>()
.map_err(IncomingBlockError::InvalidBlock)?;
if !missing.is_empty() {
let needed_hashes = missing.iter().map(|index| block.transactions[*index]);
for needed_hash in needed_hashes {
let Some(tx) = given_txs.remove(&needed_hash) else {
// 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
// these txs to the pool as this will only happen with a misbehaving peer or if the txpool reaches
// 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 {
// We could still be starting up the blockchain manager.
@ -119,28 +137,37 @@ pub async fn handle_incoming_block(
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();
incoming_block_tx
.send(BlockchainManagerCommand::AddBlock {
block,
prepped_txs,
prepped_txs: txs,
response_tx,
})
.await
.expect("TODO: don't actually panic here, an err means we are shutting down");
let res = response_rx
response_rx
.await
.expect("The blockchain manager will always respond")
.map_err(IncomingBlockError::InvalidBlock);
// Remove the block hash from the blocks being handled.
BLOCKS_BEING_HANDLED.lock().unwrap().remove(&block_hash);
res
.map_err(IncomingBlockError::InvalidBlock)
}
/// Check if we have a block with the given hash.

View file

@ -18,6 +18,7 @@ use cuprate_p2p::{
BroadcastSvc, NetworkInterface,
};
use cuprate_p2p_core::ClearNet;
use cuprate_txpool::service::TxpoolWriteHandle;
use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse},
Chain, TransactionVerificationData,
@ -46,6 +47,7 @@ pub async fn init_blockchain_manager(
clearnet_interface: NetworkInterface<ClearNet>,
blockchain_write_handle: BlockchainWriteHandle,
blockchain_read_handle: BlockchainReadHandle,
txpool_write_handle: TxpoolWriteHandle,
mut blockchain_context_service: BlockChainContextService,
block_verifier_service: ConcreteBlockVerifierService,
block_downloader_config: BlockDownloaderConfig,
@ -80,6 +82,7 @@ pub async fn init_blockchain_manager(
let manager = BlockchainManager {
blockchain_write_handle,
blockchain_read_handle,
txpool_write_handle,
blockchain_context_service,
cached_blockchain_context: blockchain_context.unchecked_blockchain_context().clone(),
block_verifier_service,
@ -102,6 +105,8 @@ pub struct BlockchainManager {
blockchain_write_handle: BlockchainWriteHandle,
/// A [`BlockchainReadHandle`].
blockchain_read_handle: BlockchainReadHandle,
/// A [`TxpoolWriteHandle`].
txpool_write_handle: TxpoolWriteHandle,
// TODO: Improve the API of the cache service.
// TODO: rename the cache service -> `BlockchainContextService`.
/// 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.
use bytes::Bytes;
use futures::{TryFutureExt, TryStreamExt};
use monero_serai::{block::Block, transaction::Transaction};
use monero_serai::{
block::Block,
transaction::{Input, Transaction},
};
use rayon::prelude::*;
use std::ops::ControlFlow;
use std::{collections::HashMap, sync::Arc};
@ -17,16 +20,14 @@ use cuprate_consensus::{
use cuprate_consensus_context::NewBlockData;
use cuprate_helper::cast::usize_to_u64;
use cuprate_p2p::{block_downloader::BlockBatch, constants::LONG_BAN, BroadcastRequest};
use cuprate_txpool::service::interface::TxpoolWriteRequest;
use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest},
AltBlockInformation, HardFork, TransactionVerificationData, VerifiedBlockInformation,
};
use crate::blockchain::manager::commands::IncomingBlockOk;
use crate::{
blockchain::{
manager::commands::BlockchainManagerCommand, types::ConsensusBlockchainReadHandle,
},
blockchain::manager::commands::{BlockchainManagerCommand, IncomingBlockOk},
constants::PANIC_CRITICAL_SERVICE_ERROR,
signals::REORG_LOCK,
};
@ -434,6 +435,18 @@ impl super::BlockchainManager {
&mut self,
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
.ready()
.await
@ -472,6 +485,14 @@ impl super::BlockchainManager {
};
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.
use std::{pin::pin, sync::Arc, time::Duration};
use std::{sync::Arc, time::Duration};
use futures::StreamExt;
use tokio::time::interval;
use tokio::{
sync::{mpsc, Notify},
time::sleep,
time::interval,
};
use tower::{Service, ServiceExt};
use tracing::instrument;

View file

@ -1,13 +1,7 @@
use std::task::{Context, Poll};
use futures::future::BoxFuture;
use futures::{FutureExt, TryFutureExt};
use tower::{util::MapErr, Service};
use tower::util::MapErr;
use cuprate_blockchain::{cuprate_database::RuntimeError, service::BlockchainReadHandle};
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.
pub type ConcreteBlockVerifierService = BlockVerifierService<

View file

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

View file

@ -2,4 +2,7 @@
//!
//! Will handle initiating the P2P and contains a protocol request handler.
mod network_address;
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.
mod bin;
mod constants;
mod handler;
mod json;
mod other;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,15 @@
//! 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-dandelion-tower`](https://doc.cuprate.org/cuprate_dandelion_tower) | [`p2p/dandelion-tower/`](https://github.com/Cuprate/cuprate/tree/main/p2p/dandelion-tower) | TODO
| [`cuprate-p2p`](https://doc.cuprate.org/cuprate_p2p) | [`p2p/p2p/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p) | TODO
| [`cuprate-p2p-bucket`](https://doc.cuprate.org/cuprate_p2p_bucket) | [`p2p/bucket/`](https://github.com/Cuprate/cuprate/tree/main/p2p/bucket) | A collection data structure discriminating its items into "buckets" of limited size.
| [`cuprate-p2p-core`](https://doc.cuprate.org/cuprate_p2p_core) | [`p2p/p2p-core/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p-core) | TODO
## Storage

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ asynch = ["dep:futures", "dep:rayon"]
cast = []
constants = []
crypto = ["dep:curve25519-dalek", "dep:monero-serai", "std"]
fs = ["dep:dirs"]
fs = ["dep:dirs", "std"]
num = []
map = ["cast", "dep:monero-serai", "dep:cuprate-constants"]
time = ["dep:chrono", "std"]

View file

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

View file

@ -11,7 +11,7 @@ pub mod atomic;
#[cfg(feature = "cast")]
pub mod cast;
#[cfg(feature = "fs")]
#[cfg(all(feature = "fs", feature = "std"))]
pub mod fs;
pub mod network;

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
//! This module contains a [`EpeeValue`] trait and
//! 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 bytes::{Buf, BufMut, Bytes, BytesMut};

View file

@ -9,7 +9,7 @@ authors = ["Boog900"]
[dependencies]
cuprate-constants = { workspace = true }
cuprate-pruning = { workspace = true }
cuprate-p2p-core = { workspace = true }
cuprate-p2p-core = { workspace = true, features = ["borsh"] }
tower = { workspace = true, features = ["util"] }
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::ConnectionCount
| AddressBookRequest::SetBan(_)
| AddressBookRequest::GetBans => {
| AddressBookRequest::GetBans
| AddressBookRequest::ConnectionInfo => {
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,
}
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.
pub struct DandelionRouteReq<Tx, Id> {
/// The transaction.

View file

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

View file

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

View file

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

View file

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

View file

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

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::{
client::{Client, InternalPeerID},
handles::ConnectionHandle,
NetworkZone,
ConnectionDirection, NetworkZone,
};
pub(crate) mod disconnect_monitor;
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.
///
@ -165,6 +165,17 @@ impl<N: NetworkZone> ClientPool<N> {
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 {

View file

@ -18,7 +18,7 @@ use cuprate_p2p_core::{
pub mod block_downloader;
mod broadcast;
mod client_pool;
pub mod client_pool;
pub mod config;
pub mod connection_maintainer;
pub mod constants;
@ -26,6 +26,7 @@ mod inbound_server;
use block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse};
pub use broadcast::{BroadcastRequest, BroadcastSvc};
pub use client_pool::{ClientPool, ClientPoolDropGuard};
pub use config::{AddressBookConfig, P2PConfig};
use connection_maintainer::MakeConnectionRequest;
@ -82,7 +83,7 @@ where
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);
@ -132,7 +133,7 @@ where
#[derive(Clone)]
pub struct NetworkInterface<N: NetworkZone> {
/// 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.
broadcast_svc: BroadcastSvc<N>,
/// A channel to request extra connections.
@ -173,7 +174,7 @@ impl<N: NetworkZone> NetworkInterface<N> {
}
/// 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
}
}

View file

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

View file

@ -10,20 +10,20 @@ keywords = ["cuprate", "rpc", "interface"]
[features]
default = ["dummy", "serde"]
dummy = []
dummy = ["dep:cuprate-helper", "dep:futures"]
[dependencies]
cuprate-epee-encoding = { 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-helper = { workspace = true, features = ["asynch"], default-features = false }
cuprate-helper = { workspace = true, features = ["asynch"], default-features = false, optional = true }
anyhow = { workspace = true }
axum = { version = "0.7.5", features = ["json"], default-features = false }
serde = { workspace = true, optional = true }
tower = { workspace = true }
tower = { workspace = true, features = ["util"] }
paste = { workspace = true }
futures = { workspace = true }
futures = { workspace = true, optional = true }
[dev-dependencies]
cuprate-test-utils = { workspace = true }

View file

@ -10,13 +10,13 @@ keywords = ["cuprate", "rpc", "types", "monero"]
[features]
default = ["serde", "epee"]
serde = ["dep:serde", "cuprate-fixed-bytes/serde"]
epee = ["dep:cuprate-epee-encoding"]
serde = ["dep:serde", "cuprate-fixed-bytes/serde", "cuprate-types/serde"]
epee = ["dep:cuprate-epee-encoding", "cuprate-types/epee"]
[dependencies]
cuprate-epee-encoding = { workspace = true, optional = 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 }
serde = { workspace = true, optional = true }

View file

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

View file

@ -20,12 +20,16 @@ use cuprate_types::BlockCompleteEntry;
use crate::{
base::AccessResponseBase,
defaults::{default_false, default_zero},
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,
};
#[cfg(any(feature = "epee", feature = "serde"))]
use crate::defaults::{default_false, default_zero};
#[cfg(feature = "epee")]
use crate::misc::PoolInfoExtent;
//---------------------------------------------------------------------------------------------------- Definitions
define_request_and_response! {
get_blocks_by_heightbin,

View file

@ -8,10 +8,6 @@ use serde::{Deserialize, Serialize};
use crate::{
base::{AccessResponseBase, ResponseBase},
defaults::{
default_false, default_height, default_one, default_string, default_true, default_vec,
default_zero,
},
macros::define_request_and_response,
misc::{
AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan,
@ -21,6 +17,12 @@ use crate::{
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
/// 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>.
#[doc = serde_doc_test!(
GET_BLOCK_TEMPLATE_RESPONSE => GetBlockTemplateResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
blockhashing_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a00000000e0c20372be23d356347091025c5b5e8f2abf83ab618378565cce2b703491523401".into(),
blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(),
difficulty_top64: 0,
@ -240,7 +242,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_BLOCK_COUNT_RESPONSE => GetBlockCountResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
count: 3195019,
}
)]
@ -332,7 +334,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GENERATE_BLOCKS_RESPONSE => GenerateBlocksResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
blocks: vec!["49b712db7760e3728586f8434ee8bc8d7b3d410dac6bb6e98bf5845c83b917e4".into()],
height: 9783,
}
@ -357,7 +359,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_LAST_BLOCK_HEADER_RESPONSE => GetLastBlockHeaderResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
block_header: BlockHeader {
block_size: 200419,
block_weight: 200419,
@ -409,7 +411,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_BLOCK_HEADER_BY_HASH_RESPONSE => GetBlockHeaderByHashResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
block_headers: vec![],
block_header: BlockHeader {
block_size: 210,
@ -464,7 +466,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_BLOCK_HEADER_BY_HEIGHT_RESPONSE => GetBlockHeaderByHeightResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
block_header: BlockHeader {
block_size: 210,
block_weight: 210,
@ -519,7 +521,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_BLOCK_HEADERS_RANGE_RESPONSE => GetBlockHeadersRangeResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
headers: vec![
BlockHeader {
block_size: 301413,
@ -601,7 +603,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_BLOCK_RESPONSE => GetBlockResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
blob: "1010c58bab9b06b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7807e07f502cef8a70101ff92f8a7010180e0a596bb1103d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85d034019f629d8b36bd16a2bfce3ea80c31dc4d8762c67165aec21845494e32b7582fe00211000000297a787a000000000000000000000000".into(),
block_header: BlockHeader {
block_size: 106,
@ -654,11 +656,11 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_CONNECTIONS_RESPONSE => GetConnectionsResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
connections: vec![
ConnectionInfo {
address: "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion:18083".into(),
address_type: 4,
address_type: cuprate_types::AddressType::Tor,
avg_download: 0,
avg_upload: 0,
connection_id: "22ef856d0f1d44cc95e84fecfd065fe2".into(),
@ -680,12 +682,12 @@ define_request_and_response! {
rpc_port: 0,
send_count: 3406572,
send_idle_time: 30,
state: "normal".into(),
state: cuprate_types::ConnectionState::Normal,
support_flags: 0
},
ConnectionInfo {
address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(),
address_type: 4,
address_type: cuprate_types::AddressType::Tor,
avg_download: 0,
avg_upload: 0,
connection_id: "c7734e15936f485a86d2b0534f87e499".into(),
@ -707,7 +709,7 @@ define_request_and_response! {
rpc_port: 0,
send_count: 3370566,
send_idle_time: 120,
state: "normal".into(),
state: cuprate_types::ConnectionState::Normal,
support_flags: 0
}
],
@ -728,7 +730,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_INFO_RESPONSE => GetInfoResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
adjusted_time: 1721245289,
alt_blocks_count: 16,
block_size_limit: 600000,
@ -831,7 +833,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
HARD_FORK_INFO_RESPONSE => HardForkInfoResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
earliest_height: 2689608,
enabled: true,
state: 0,
@ -877,7 +879,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
SET_BANS_RESPONSE => SetBansResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
}
)]
ResponseBase {}
@ -892,7 +894,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_BANS_RESPONSE => GetBansResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
bans: vec![
GetBan {
host: "104.248.206.131".into(),
@ -994,7 +996,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_OUTPUT_HISTOGRAM_RESPONSE => GetOutputHistogramResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
histogram: vec![HistogramEntry {
amount: 20000000000,
recent_instances: 0,
@ -1028,7 +1030,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_COINBASE_TX_SUM_RESPONSE => GetCoinbaseTxSumResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
emission_amount: 9387854817320,
emission_amount_top64: 0,
fee_amount: 83981380000,
@ -1057,7 +1059,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_VERSION_RESPONSE => GetVersionResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
current_height: 3195051,
hard_forks: vec![
HardforkEntry {
@ -1143,12 +1145,16 @@ define_request_and_response! {
get_fee_estimate,
cc73fe71162d564ffda8e549b79a350bca53c454 =>
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!(
GET_FEE_ESTIMATE_RESPONSE => GetFeeEstimateResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
fee: 20000,
fees: vec![20000,80000,320000,4000000],
quantization_mask: 10000,
@ -1170,7 +1176,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_ALTERNATE_CHAINS_RESPONSE => GetAlternateChainsResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
chains: vec![
ChainInfo {
block_hash: "4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce".into(),
@ -1238,7 +1244,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
SYNC_INFO_RESPONSE => SyncInfoResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
height: 3195157,
next_needed_pruning_seed: 0,
overview: "[]".into(),
@ -1247,7 +1253,7 @@ define_request_and_response! {
SyncInfoPeer {
info: ConnectionInfo {
address: "142.93.128.65:44986".into(),
address_type: 1,
address_type: cuprate_types::AddressType::Ipv4,
avg_download: 1,
avg_upload: 1,
connection_id: "a5803c4c2dac49e7b201dccdef54c862".into(),
@ -1269,14 +1275,14 @@ define_request_and_response! {
rpc_port: 18089,
send_count: 32235,
send_idle_time: 6,
state: "normal".into(),
state: cuprate_types::ConnectionState::Normal,
support_flags: 1
}
},
SyncInfoPeer {
info: ConnectionInfo {
address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(),
address_type: 4,
address_type: cuprate_types::AddressType::Tor,
avg_download: 0,
avg_upload: 0,
connection_id: "277f7c821bc546878c8bd29977e780f5".into(),
@ -1298,7 +1304,7 @@ define_request_and_response! {
rpc_port: 0,
send_count: 99120,
send_idle_time: 15,
state: "normal".into(),
state: cuprate_types::ConnectionState::Normal,
support_flags: 0
}
}
@ -1328,7 +1334,7 @@ define_request_and_response! {
// TODO: enable test after binary string impl.
// #[doc = serde_doc_test!(
// GET_TRANSACTION_POOL_BACKLOG_RESPONSE => GetTransactionPoolBacklogResponse {
// base: ResponseBase::ok(),
// base: ResponseBase::OK,
// backlog: "...Binary...".into(),
// }
// )]
@ -1370,7 +1376,7 @@ define_request_and_response! {
// TODO: enable test after binary string impl.
// #[doc = serde_doc_test!(
// GET_OUTPUT_DISTRIBUTION_RESPONSE => GetOutputDistributionResponse {
// base: AccessResponseBase::ok(),
// base: AccessResponseBase::OK,
// distributions: vec![Distribution::Uncompressed(DistributionUncompressed {
// start_height: 1462078,
// base: 0,
@ -1394,7 +1400,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_MINER_DATA_RESPONSE => GetMinerDataResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
already_generated_coins: 18186022843595960691,
difficulty: "0x48afae42de".into(),
height: 2731375,
@ -1447,7 +1453,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
PRUNE_BLOCKCHAIN_RESPONSE => PruneBlockchainResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
pruned: true,
pruning_seed: 387,
}
@ -1513,7 +1519,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
FLUSH_CACHE_RESPONSE => FlushCacheResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
}
)]
ResponseBase {}
@ -1542,7 +1548,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
ADD_AUX_POW_RESPONSE => AddAuxPowResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
aux_pow: vec![AuxPow {
hash: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into(),
id: "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8".into(),

View file

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

View file

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

View file

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

View file

@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize};
use crate::{
base::{AccessResponseBase, ResponseBase},
defaults::{default_false, default_string, default_true, default_vec, default_zero},
macros::define_request_and_response,
misc::{
GetOutputsOut, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo,
@ -17,6 +16,9 @@ use crate::{
RpcCallValue,
};
#[cfg(any(feature = "serde", feature = "epee"))]
use crate::defaults::{default_false, default_string, default_true, default_vec, default_zero};
//---------------------------------------------------------------------------------------------------- Macro
/// Adds a (de)serialization doc-test to a type in `other.rs`.
///
@ -102,7 +104,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_HEIGHT_RESPONSE => GetHeightResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
hash: "68bb1a1cff8e2a44c3221e8e1aff80bc6ca45d06fa8eff4d2a3a7ac31d4efe3f".into(),
height: 3195160,
}
@ -157,7 +159,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_ALT_BLOCKS_HASHES_RESPONSE => GetAltBlocksHashesResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
blks_hashes: vec!["8ee10db35b1baf943f201b303890a29e7d45437bd76c2bd4df0d2f2ee34be109".into()],
}
)]
@ -187,7 +189,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
IS_KEY_IMAGE_SPENT_RESPONSE => IsKeyImageSpentResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
spent_status: vec![1, 1],
}
)]
@ -283,7 +285,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
START_MINING_RESPONSE => StartMiningResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
}
)]
ResponseBase {}
@ -298,7 +300,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
STOP_MINING_RESPONSE => StopMiningResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
}
)]
ResponseBase {}
@ -313,7 +315,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
MINING_STATUS_RESPONSE => MiningStatusResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
active: false,
address: "".into(),
bg_idle_threshold: 0,
@ -359,7 +361,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
SAVE_BC_RESPONSE => SaveBcResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
}
)]
ResponseBase {}
@ -385,7 +387,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_PEER_LIST_RESPONSE => GetPeerListResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
gray_list: vec![
Peer {
host: "161.97.193.0".into(),
@ -467,7 +469,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
SET_LOG_HASH_RATE_RESPONSE => SetLogHashRateResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
}
)]
ResponseBase {}
@ -492,7 +494,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
SET_LOG_LEVEL_RESPONSE => SetLogLevelResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
}
)]
ResponseBase {}
@ -516,7 +518,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
SET_LOG_CATEGORIES_RESPONSE => SetLogCategoriesResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
categories: "*:INFO".into(),
}
)]
@ -582,7 +584,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_TRANSACTION_POOL_STATS_RESPONSE => GetTransactionPoolStatsResponse {
base: AccessResponseBase::ok(),
base: AccessResponseBase::OK,
pool_stats: TxpoolStats {
bytes_max: 11843,
bytes_med: 2219,
@ -644,7 +646,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_LIMIT_RESPONSE => GetLimitResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
limit_down: 1280000,
limit_up: 1280000,
}
@ -676,7 +678,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
SET_LIMIT_RESPONSE => SetLimitResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
limit_down: 1024,
limit_up: 128,
}
@ -707,7 +709,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
OUT_PEERS_RESPONSE => OutPeersResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
out_peers: 3232235535,
}
)]
@ -740,7 +742,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_NET_STATS_RESPONSE => GetNetStatsResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
start_time: 1721251858,
total_bytes_in: 16283817214,
total_bytes_out: 34225244079,
@ -779,7 +781,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_OUTS_RESPONSE => GetOutsResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
outs: vec![
OutKey {
height: 51941,
@ -823,7 +825,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
UPDATE_RESPONSE => UpdateResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
auto_uri: "".into(),
hash: "".into(),
path: "".into(),
@ -860,7 +862,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
POP_BLOCKS_RESPONSE => PopBlocksResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
height: 76482,
}
)]
@ -879,7 +881,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_TRANSACTION_POOL_HASHES_RESPONSE => GetTransactionPoolHashesResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
tx_hashes: vec![
"aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03".into(),
"794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11".into(),
@ -929,7 +931,7 @@ define_request_and_response! {
#[doc = serde_doc_test!(
GET_PUBLIC_NODES_RESPONSE => GetPublicNodesResponse {
base: ResponseBase::ok(),
base: ResponseBase::OK,
gray: vec![],
white: vec![
PublicNode {

View file

@ -9,32 +9,31 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/storage/cuprate-bloc
keywords = ["cuprate", "blockchain", "database"]
[features]
default = ["heed", "service"]
default = ["heed"]
# default = ["redb", "service"]
# default = ["redb-memory", "service"]
heed = ["cuprate-database/heed"]
redb = ["cuprate-database/redb"]
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]
cuprate-database = { 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-pruning = { workspace = true }
bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] }
bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
curve25519-dalek = { workspace = true }
rand = { workspace = true }
rand = { workspace = true, features = ["std", "std_rng"] }
monero-serai = { workspace = true, features = ["std"] }
serde = { workspace = true, optional = true }
# `service` feature.
tower = { workspace = true }
thread_local = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
thread_local = { workspace = true }
rayon = { workspace = true }
[dev-dependencies]
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.
# 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:
- `heed` (LMDB)
- `redb`
@ -45,7 +42,7 @@ The default is `heed`.
<!-- FIXME: tracing should be behind a feature flag -->
# 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.
Failing to uphold these invariants may cause panics.

View file

@ -29,16 +29,12 @@ pub use free::open;
pub mod config;
pub mod ops;
pub mod service;
pub mod tables;
pub mod types;
//---------------------------------------------------------------------------------------------------- Feature-gated
#[cfg(feature = "service")]
pub mod service;
//---------------------------------------------------------------------------------------------------- Private
#[cfg(test)]
pub(crate) mod tests;
#[cfg(feature = "service")] // only needed in `service` for now
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.
//!
//! This module must be enabled with the `service` feature.
//!
//! ## Handles
//! The 2 handles to the database are:
//! - [`BlockchainReadHandle`]

View file

@ -121,6 +121,8 @@ fn map_request(
R::DatabaseSize => database_size(env),
R::OutputHistogram(input) => output_histogram(env, input),
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? */
@ -648,3 +650,13 @@ fn output_histogram(env: &ConcreteEnv, input: OutputHistogramInput) -> ResponseR
fn coinbase_tx_sum(env: &ConcreteEnv, height: usize, count: u64) -> ResponseResult {
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"]
[features]
# default = ["heed"]
default = ["heed"]
# default = ["redb"]
# default = ["redb-memory"]
heed = ["dep:heed"]
heed = []
redb = ["dep:redb"]
redb-memory = ["redb"]
@ -25,7 +25,7 @@ paste = { workspace = true }
thiserror = { workspace = true }
# 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 }
serde = { workspace = true, optional = true }

View file

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

View file

@ -8,14 +8,20 @@ authors = ["Boog900"]
repository = "https://github.com/Cuprate/cuprate/tree/main/storage/service"
keywords = ["cuprate", "service", "database"]
[features]
default = ["heed"]
heed = ["cuprate-database/heed"]
redb = ["cuprate-database/redb"]
redb-memorey = ["cuprate-database/redb-memory"]
[dependencies]
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 }
rayon = { workspace = true }
tower = { workspace = true }
futures = { workspace = true }
futures = { workspace = true, features = ["std"] }
crossbeam = { workspace = true, features = ["std"] }
[lints]

View file

@ -30,6 +30,14 @@ pub struct DatabaseWriteHandle<Req, Res> {
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>
where
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"]
[features]
default = ["heed", "service"]
default = ["heed"]
# default = ["redb", "service"]
# default = ["redb-memory", "service"]
heed = ["cuprate-database/heed"]
redb = ["cuprate-database/redb"]
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"]
[dependencies]
cuprate-database = { workspace = true, features = ["heed"] }
cuprate-database-service = { workspace = true, optional = true }
cuprate-database-service = { workspace = true }
cuprate-types = { workspace = true }
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"] }
bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] }
thiserror = { workspace = true }
hex = { workspace = true }
hex = { workspace = true, features = ["std"] }
blake3 = { workspace = true, features = ["std"] }
tower = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
tower = { workspace = true }
rayon = { workspace = 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.
# 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:
- `heed` (LMDB)

View file

@ -3,7 +3,7 @@
//---------------------------------------------------------------------------------------------------- Import
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
/// Open the txpool database using the passed [`Config`].
@ -60,3 +60,13 @@ pub fn open(config: Config) -> Result<ConcreteEnv, InitError> {
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
)]
// Used in docs: <https://github.com/Cuprate/cuprate/pull/170#discussion_r1823644357>.
use tower as _;
pub mod config;
mod free;
pub mod ops;
#[cfg(feature = "service")]
pub mod service;
pub mod tables;
mod tx;
pub mod types;
pub use config::Config;
pub use free::open;
pub use free::{open, transaction_blob_hash};
pub use tx::TxEntry;
//re-exports
pub use cuprate_database;
// TODO: remove when used.
use tower as _;
#[cfg(test)]
mod test {
use cuprate_test_utils as _;

View file

@ -85,7 +85,7 @@ mod key_images;
mod tx_read;
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};
/// 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_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.
pub fn get_transaction_verification_data(
@ -34,3 +37,17 @@ pub fn get_transaction_verification_data(
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 crate::{
free::transaction_blob_hash,
ops::{
key_images::{add_tx_key_images, remove_tx_key_images},
TxPoolWriteError,
@ -56,6 +57,12 @@ pub fn add_transaction(
let kis_table = tables.spent_key_images_mut();
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(())
}
@ -79,5 +86,9 @@ pub fn remove_transaction(
let kis_table = tables.spent_key_images_mut();
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(())
}

View file

@ -10,8 +10,6 @@
//!
//! The system is managed by this crate, and only requires [`init`] by the user.
//!
//! This module must be enabled with the `service` feature.
//!
//! ## Handles
//! The 2 handles to the database are:
//! - [`TxpoolReadHandle`]
@ -42,7 +40,7 @@
//! To interact with the database (whether reading or writing data),
//! 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.
//! This channel can be `.await`ed upon to (eventually) receive

View file

@ -1,44 +1,76 @@
//! Tx-pool [`service`](super) interface.
//!
//! 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 crate::{tx::TxEntry, types::TransactionHash};
use crate::{
tx::TxEntry,
types::{KeyImage, TransactionBlobHash, TransactionHash},
};
//---------------------------------------------------------------------------------------------------- TxpoolReadRequest
/// The transaction pool [`tower::Service`] read request type.
#[derive(Clone)]
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),
/// A request for the [`TransactionVerificationData`] of a transaction in the tx pool.
/// Get the [`TransactionVerificationData`] of a transaction in the tx pool.
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.
Backlog,
/// 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
/// The transaction pool [`tower::Service`] read response type.
#[expect(clippy::large_enum_variant)]
pub enum TxpoolReadResponse {
/// Response to [`TxpoolReadRequest::TxBlob`].
///
/// The inner value is the raw bytes of a transaction.
// TODO: use bytes::Bytes.
TxBlob(Vec<u8>),
/// The response for [`TxpoolReadRequest::TxBlob`].
TxBlob { tx_blob: Vec<u8>, state_stem: bool },
/// Response to [`TxpoolReadRequest::TxVerificationData`].
/// The response for [`TxpoolReadRequest::TxVerificationData`].
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`].
///
/// The inner `Vec` contains information on all
/// The inner [`Vec`] contains information on all
/// the transactions currently in the pool.
Backlog(Vec<TxEntry>),
@ -66,9 +98,17 @@ pub enum TxpoolWriteRequest {
},
/// Remove a transaction with the given hash from the pool.
///
/// Returns [`TxpoolWriteResponse::Ok`].
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
@ -77,6 +117,8 @@ pub enum TxpoolWriteRequest {
pub enum TxpoolWriteResponse {
/// Response to:
/// - [`TxpoolWriteRequest::RemoveTransaction`]
/// - [`TxpoolWriteRequest::Promote`]
/// - [`TxpoolWriteRequest::NewBlock`]
Ok,
/// Response to [`TxpoolWriteRequest::AddTransaction`].

View file

@ -4,22 +4,24 @@
clippy::unnecessary_wraps,
reason = "TODO: finish implementing the signatures from <https://github.com/Cuprate/cuprate/pull/297>"
)]
use std::sync::Arc;
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
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 crate::{
ops::get_transaction_verification_data,
ops::{get_transaction_verification_data, in_stem_pool},
service::{
interface::{TxpoolReadRequest, TxpoolReadResponse},
types::{ReadResponseResult, TxpoolReadHandle},
},
tables::{OpenTables, TransactionBlobs},
types::TransactionHash,
tables::{KnownBlobHashes, OpenTables, TransactionBlobs, TransactionInfos},
types::{TransactionBlobHash, TransactionHash},
};
// 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
/// 2. Handler function is called
/// 3. [`TxpoolReadResponse`] is returned
#[expect(clippy::needless_pass_by_value)]
fn map_request(
env: &ConcreteEnv, // Access to the database
request: TxpoolReadRequest, // The request we must fulfill
@ -65,8 +66,14 @@ fn map_request(
match request {
TxpoolReadRequest::TxBlob(tx_hash) => tx_blob(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::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_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
.get(tx_hash)
.map(|blob| TxpoolReadResponse::TxBlob(blob.0))
let tx_blob = tx_blobs_table.get(tx_hash)?.0;
Ok(TxpoolReadResponse::TxBlob {
tx_blob,
state_stem: in_stem_pool(tx_hash, &tx_infos_table)?,
})
}
/// [`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)
}
/// [`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`].
#[inline]
fn backlog(env: &ConcreteEnv) -> ReadResponseResult {
@ -119,6 +203,6 @@ fn backlog(env: &ConcreteEnv) -> ReadResponseResult {
/// [`TxpoolReadRequest::Size`].
#[inline]
fn size(env: &ConcreteEnv) -> ReadResponseResult {
fn size(env: &ConcreteEnv, include_sensitive_txs: bool) -> ReadResponseResult {
Ok(TxpoolReadResponse::Size(todo!()))
}

View file

@ -1,6 +1,6 @@
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_types::TransactionVerificationData;
@ -10,8 +10,8 @@ use crate::{
interface::{TxpoolWriteRequest, TxpoolWriteResponse},
types::TxpoolWriteHandle,
},
tables::OpenTables,
types::TransactionHash,
tables::{OpenTables, Tables, TransactionInfos},
types::{KeyImage, TransactionHash, TxStateFlags},
};
//---------------------------------------------------------------------------------------------------- init_write_service
@ -31,6 +31,8 @@ fn handle_txpool_request(
add_transaction(env, tx, *state_stem)
}
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)?;
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.
use cuprate_database::{define_tables, StorableVec};
use crate::types::{KeyImage, RawCachedVerificationState, TransactionHash, TransactionInfo};
use crate::types::{
KeyImage, RawCachedVerificationState, TransactionBlobHash, TransactionHash, TransactionInfo,
};
define_tables! {
/// Serialized transaction blobs.
@ -41,5 +43,9 @@ define_tables! {
///
/// This table contains the spent key images from all transactions in the pool.
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).
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct TxEntry {
/// The transaction's ID (hash).
pub id: [u8; 32],
/// The transaction's weight.
pub weight: u64,
/// The transaction's fee.

View file

@ -6,7 +6,6 @@
//!
//! <!-- FIXME: Add schema here or a link to it when complete -->
use bytemuck::{Pod, Zeroable};
use monero_serai::transaction::Timelock;
use cuprate_types::{CachedVerificationState, HardFork};
@ -17,6 +16,9 @@ pub type KeyImage = [u8; 32];
/// A transaction hash.
pub type TransactionHash = [u8; 32];
/// A transaction blob hash.
pub type TransactionBlobHash = [u8; 32];
bitflags::bitflags! {
/// Flags representing the state of the transaction in the pool.
#[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"]
blockchain = []
epee = ["dep:cuprate-epee-encoding"]
serde = ["dep:serde"]
serde = ["dep:serde", "hex"]
proptest = ["dep:proptest", "dep:proptest-derive"]
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]
cuprate-epee-encoding = { workspace = true, optional = true, features = ["std"] }
cuprate-helper = { workspace = true, optional = true, features = ["cast"] }
cuprate-fixed-bytes = { workspace = true }
cuprate-fixed-bytes = { workspace = true, features = ["std", "serde"] }
bytes = { workspace = true }
curve25519-dalek = { workspace = true }
monero-serai = { workspace = 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"] }
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))]
pub struct PrunedTxBlobEntry {
/// The transaction.
pub tx: Bytes,
pub blob: Bytes,
/// The prunable transaction hash.
pub prunable_hash: ByteArray<32>,
}
@ -144,7 +144,7 @@ pub struct PrunedTxBlobEntry {
#[cfg(feature = "epee")]
epee_object!(
PrunedTxBlobEntry,
tx: Bytes,
blob: Bytes,
prunable_hash: ByteArray<32>,
);

View file

@ -12,7 +12,8 @@ use monero_serai::block::Block;
use crate::{
types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation},
AltBlockInformation, ChainId, CoinbaseTxSum, OutputHistogramEntry, OutputHistogramInput,
AltBlockInformation, ChainId, ChainInfo, CoinbaseTxSum, OutputHistogramEntry,
OutputHistogramInput,
};
//---------------------------------------------------------------------------------------------------- ReadRequest
@ -128,6 +129,12 @@ pub enum BlockchainReadRequest {
///
/// TODO: document fields after impl.
CoinbaseTxSum { height: usize, count: u64 },
/// Get information on all alternative chains.
AltChains,
/// Get the amount of alternative chains that exist.
AltChainCount,
}
//---------------------------------------------------------------------------------------------------- WriteRequest
@ -276,6 +283,12 @@ pub enum BlockchainResponse {
/// Response to [`BlockchainReadRequest::CoinbaseTxSum`].
CoinbaseTxSum(CoinbaseTxSum),
/// Response to [`BlockchainReadRequest::AltChains`].
AltChains(Vec<ChainInfo>),
/// Response to [`BlockchainReadRequest::AltChainCount`].
AltChainCount(usize),
//------------------------------------------------------ Writes
/// 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(feature = "serde")]
impl<'de, const N: usize> Deserialize<'de> for HexBytes<N>
where
[u8; N]: hex::FromHex,

View file

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

View file

@ -20,7 +20,7 @@ pub struct Output {
/// [`Output::target`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[serde(untagged)]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum Target {
Key { key: HexBytes<32> },
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)
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[serde(untagged)]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum Transaction {
V1 {
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[serde(flatten)]
#[cfg_attr(feature = "serde", serde(flatten))]
prefix: TransactionPrefix,
signatures: Vec<HexBytes<64>>,
},
V2 {
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[serde(flatten)]
#[cfg_attr(feature = "serde", serde(flatten))]
prefix: TransactionPrefix,
rct_signatures: RctSignatures,
/// 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.
mod address_type;
mod block_complete_entry;
mod connection_state;
mod hard_fork;
mod transaction_verification_data;
mod types;
pub use address_type::AddressType;
pub use block_complete_entry::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs};
pub use connection_state::ConnectionState;
pub use hard_fork::{HardFork, HardForkError};
pub use transaction_verification_data::{
CachedVerificationState, TransactionVerificationData, TxVersion,
};
pub use types::{
AltBlockInformation, Chain, ChainId, ChainInfo, CoinbaseTxSum, ExtendedBlockHeader,
FeeEstimate, HardForkInfo, MinerData, MinerDataTxBacklogEntry, OutputHistogramEntry,
OutputHistogramInput, OutputOnChain, VerifiedBlockInformation, VerifiedTransactionInformation,
AddAuxPow, AltBlockInformation, AuxPow, Chain, ChainId, ChainInfo, CoinbaseTxSum,
ExtendedBlockHeader, FeeEstimate, HardForkInfo, MinerData, MinerDataTxBacklogEntry,
OutputHistogramEntry, OutputHistogramInput, OutputOnChain, VerifiedBlockInformation,
VerifiedTransactionInformation,
};
//---------------------------------------------------------------------------------------------------- Feature-gated

View file

@ -177,8 +177,6 @@ pub struct OutputHistogramEntry {
pub struct CoinbaseTxSum {
pub emission_amount: u128,
pub fee_amount: u128,
pub wide_emission_amount: u128,
pub wide_fee_amount: u128,
}
/// Data to create a custom block template.
@ -242,7 +240,23 @@ pub struct ChainInfo {
pub height: u64,
pub length: u64,
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