From 978d72b6c14f27bd61836d90f304d766cc7a93f2 Mon Sep 17 00:00:00 2001
From: SyntheticBird <118022351+SyntheticBird45@users.noreply.github.com>
Date: Wed, 16 Oct 2024 23:17:58 +0000
Subject: [PATCH 1/4] Move consensus context service into a subcrate. (#318)

Co-authored-by: Boog900 <boog900@tutanota.com>
---
 Cargo.lock                                    | 23 +++++-
 Cargo.toml                                    |  3 +-
 binaries/cuprated/Cargo.toml                  |  1 +
 binaries/cuprated/src/blockchain/manager.rs   |  7 +-
 .../src/blockchain/manager/handler.rs         |  8 +--
 .../src/rpc/request/blockchain_context.rs     |  2 +-
 books/architecture/src/appendix/crates.md     |  3 +-
 consensus/Cargo.toml                          |  6 +-
 consensus/context/Cargo.toml                  | 24 +++++++
 .../context => context/src}/alt_chains.rs     | 13 ++--
 .../context => context/src}/difficulty.rs     | 23 +++---
 .../{src/context => context/src}/hardforks.rs | 28 ++++----
 .../{src/context.rs => context/src/lib.rs}    | 71 ++++++++++++++++---
 .../{src/context => context/src}/rx_vms.rs    | 32 ++++-----
 .../{src/context => context/src}/task.rs      | 13 ++--
 .../{src/context => context/src}/tokens.rs    |  0
 .../{src/context => context/src}/weight.rs    | 16 ++---
 consensus/fast-sync/Cargo.toml                | 13 ++--
 consensus/fast-sync/src/fast_sync.rs          |  6 +-
 consensus/src/block.rs                        |  4 +-
 consensus/src/block/alt_block.rs              | 12 ++--
 consensus/src/block/batch_prepare.rs          |  2 +-
 consensus/src/lib.rs                          |  3 +-
 consensus/src/tests/context.rs                | 12 ++--
 consensus/src/tests/context/difficulty.rs     |  2 +-
 consensus/src/tests/context/hardforks.rs      | 10 ++-
 consensus/src/tests/context/rx_vms.rs         |  6 +-
 consensus/src/tests/context/weight.rs         |  8 +--
 28 files changed, 218 insertions(+), 133 deletions(-)
 create mode 100644 consensus/context/Cargo.toml
 rename consensus/{src/context => context/src}/alt_chains.rs (94%)
 rename consensus/{src/context => context/src}/difficulty.rs (95%)
 rename consensus/{src/context => context/src}/hardforks.rs (90%)
 rename consensus/{src/context.rs => context/src/lib.rs} (88%)
 rename consensus/{src/context => context/src}/rx_vms.rs (90%)
 rename consensus/{src/context => context/src}/task.rs (96%)
 rename consensus/{src/context => context/src}/tokens.rs (100%)
 rename consensus/{src/context => context/src}/weight.rs (96%)

diff --git a/Cargo.lock b/Cargo.lock
index 1b05efad..ca0174ba 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -575,6 +575,7 @@ name = "cuprate-consensus"
 version = "0.1.0"
 dependencies = [
  "cfg-if",
+ "cuprate-consensus-context",
  "cuprate-consensus-rules",
  "cuprate-helper",
  "cuprate-test-utils",
@@ -587,12 +588,30 @@ dependencies = [
  "proptest",
  "proptest-derive",
  "rand",
- "randomx-rs",
  "rayon",
  "thiserror",
  "thread_local",
  "tokio",
  "tokio-test",
+ "tower 0.5.1",
+ "tracing",
+]
+
+[[package]]
+name = "cuprate-consensus-context"
+version = "0.1.0"
+dependencies = [
+ "cuprate-consensus-rules",
+ "cuprate-helper",
+ "cuprate-types",
+ "futures",
+ "hex",
+ "monero-serai",
+ "randomx-rs",
+ "rayon",
+ "thiserror",
+ "thread_local",
+ "tokio",
  "tokio-util",
  "tower 0.5.1",
  "tracing",
@@ -704,6 +723,7 @@ dependencies = [
  "clap",
  "cuprate-blockchain",
  "cuprate-consensus",
+ "cuprate-consensus-context",
  "cuprate-consensus-rules",
  "cuprate-helper",
  "cuprate-types",
@@ -972,6 +992,7 @@ dependencies = [
  "cuprate-async-buffer",
  "cuprate-blockchain",
  "cuprate-consensus",
+ "cuprate-consensus-context",
  "cuprate-consensus-rules",
  "cuprate-cryptonight",
  "cuprate-dandelion-tower",
diff --git a/Cargo.toml b/Cargo.toml
index 6c322fbd..2ef99d62 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ members = [
 	"binaries/cuprated",
 	"constants",
 	"consensus",
+	"consensus/context",
 	"consensus/fast-sync",
 	"consensus/rules",
 	"cryptonight",
@@ -322,4 +323,4 @@ non_camel_case_types = "deny"
 # unused_results = "deny"
 # non_exhaustive_omitted_patterns = "deny"
 # missing_docs = "deny"
-# missing_copy_implementations = "deny"
\ No newline at end of file
+# missing_copy_implementations = "deny"
diff --git a/binaries/cuprated/Cargo.toml b/binaries/cuprated/Cargo.toml
index 325406bf..59fa9784 100644
--- a/binaries/cuprated/Cargo.toml
+++ b/binaries/cuprated/Cargo.toml
@@ -11,6 +11,7 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/binaries/cuprated"
 # TODO: after v1.0.0, remove unneeded dependencies.
 cuprate-consensus        = { path = "../../consensus" }
 cuprate-fast-sync        = { path = "../../consensus/fast-sync" }
+cuprate-consensus-context = { path = "../../consensus/context" }
 cuprate-consensus-rules  = { path = "../../consensus/rules" }
 cuprate-cryptonight      = { path = "../../cryptonight" }
 cuprate-helper           = { path = "../../helper" }
diff --git a/binaries/cuprated/src/blockchain/manager.rs b/binaries/cuprated/src/blockchain/manager.rs
index 118c8dd6..8e613bc9 100644
--- a/binaries/cuprated/src/blockchain/manager.rs
+++ b/binaries/cuprated/src/blockchain/manager.rs
@@ -8,10 +8,11 @@ use tracing::error;
 
 use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle};
 use cuprate_consensus::{
-    context::RawBlockChainContext, BlockChainContextRequest, BlockChainContextResponse,
-    BlockChainContextService, BlockVerifierService, ExtendedConsensusError, TxVerifierService,
-    VerifyBlockRequest, VerifyBlockResponse, VerifyTxRequest, VerifyTxResponse,
+    BlockChainContextRequest, BlockChainContextResponse, BlockChainContextService,
+    BlockVerifierService, ExtendedConsensusError, TxVerifierService, VerifyBlockRequest,
+    VerifyBlockResponse, VerifyTxRequest, VerifyTxResponse,
 };
+use cuprate_consensus_context::RawBlockChainContext;
 use cuprate_p2p::{
     block_downloader::{BlockBatch, BlockDownloaderConfig},
     BroadcastSvc, NetworkInterface,
diff --git a/binaries/cuprated/src/blockchain/manager/handler.rs b/binaries/cuprated/src/blockchain/manager/handler.rs
index 9603bad5..e9805cd7 100644
--- a/binaries/cuprated/src/blockchain/manager/handler.rs
+++ b/binaries/cuprated/src/blockchain/manager/handler.rs
@@ -10,11 +10,11 @@ use tracing::info;
 
 use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle};
 use cuprate_consensus::{
-    block::PreparedBlock, context::NewBlockData, transactions::new_tx_verification_data,
-    BlockChainContextRequest, BlockChainContextResponse, BlockVerifierService,
-    ExtendedConsensusError, VerifyBlockRequest, VerifyBlockResponse, VerifyTxRequest,
-    VerifyTxResponse,
+    block::PreparedBlock, transactions::new_tx_verification_data, BlockChainContextRequest,
+    BlockChainContextResponse, BlockVerifierService, ExtendedConsensusError, VerifyBlockRequest,
+    VerifyBlockResponse, VerifyTxRequest, VerifyTxResponse,
 };
+use cuprate_consensus_context::NewBlockData;
 use cuprate_helper::cast::usize_to_u64;
 use cuprate_p2p::{block_downloader::BlockBatch, constants::LONG_BAN, BroadcastRequest};
 use cuprate_types::{
diff --git a/binaries/cuprated/src/rpc/request/blockchain_context.rs b/binaries/cuprated/src/rpc/request/blockchain_context.rs
index b616593d..2b14d467 100644
--- a/binaries/cuprated/src/rpc/request/blockchain_context.rs
+++ b/binaries/cuprated/src/rpc/request/blockchain_context.rs
@@ -5,7 +5,7 @@ use std::convert::Infallible;
 use anyhow::Error;
 use tower::{Service, ServiceExt};
 
-use cuprate_consensus::context::{
+use cuprate_consensus_context::{
     BlockChainContext, BlockChainContextRequest, BlockChainContextResponse,
     BlockChainContextService,
 };
diff --git a/books/architecture/src/appendix/crates.md b/books/architecture/src/appendix/crates.md
index 1993c47e..fe8f1f05 100644
--- a/books/architecture/src/appendix/crates.md
+++ b/books/architecture/src/appendix/crates.md
@@ -16,7 +16,8 @@ cargo doc --open --package cuprate-blockchain
 | Crate | In-tree path | Purpose |
 |-------|--------------|---------|
 | [`cuprate-consensus`](https://doc.cuprate.org/cuprate_consensus) | [`consensus/`](https://github.com/Cuprate/cuprate/tree/main/consensus) | TODO
-| [`cuprate-consensus-rules`](https://doc.cuprate.org/cuprate_consensus_rules) | [`consensus/rules/`](https://github.com/Cuprate/cuprate/tree/main/consensus-rules) | TODO
+| [`cuprate-consensus-context`](https://doc.cuprate.org/cuprate_consensus_context) | [`consensus/context/`](https://github.com/Cuprate/cuprate/tree/main/consensus/context) | TODO
+| [`cuprate-consensus-rules`](https://doc.cuprate.org/cuprate_consensus_rules) | [`consensus/rules/`](https://github.com/Cuprate/cuprate/tree/main/consensus/rules) | TODO
 | [`cuprate-fast-sync`](https://doc.cuprate.org/cuprate_fast_sync) | [`consensus/fast-sync/`](https://github.com/Cuprate/cuprate/tree/main/consensus/fast-sync) | Fast block synchronization
 
 ## Networking
diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml
index 12d97eed..1fdee89a 100644
--- a/consensus/Cargo.toml
+++ b/consensus/Cargo.toml
@@ -11,6 +11,7 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/consensus"
 cuprate-helper = { path = "../helper", default-features = false, features = ["std", "asynch", "num"] }
 cuprate-consensus-rules = { path = "./rules", features = ["rayon"] }
 cuprate-types = { path = "../types" }
+cuprate-consensus-context = { path = "./context" }
 
 cfg-if = { workspace = true }
 thiserror = { workspace = true }
@@ -18,13 +19,10 @@ tower = { workspace = true, features = ["util"] }
 tracing = { workspace = true, features = ["std", "attributes"] }
 futures = { workspace = true, features = ["std", "async-await"] }
 
-randomx-rs = { workspace = true }
 monero-serai = { workspace = true, features = ["std"] }
 
 rayon = { workspace = true }
 thread_local = { workspace = true }
-tokio = { workspace = true, features = ["rt"] }
-tokio-util = { workspace = true }
 
 hex = { workspace = true }
 rand = { workspace = true }
@@ -42,4 +40,4 @@ proptest = { workspace = true }
 proptest-derive = { workspace = true }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/consensus/context/Cargo.toml b/consensus/context/Cargo.toml
new file mode 100644
index 00000000..00804204
--- /dev/null
+++ b/consensus/context/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cuprate-consensus-context"
+version = "0.1.0"
+edition = "2021"
+license = "MIT"
+authors = ["SyntheticBird","Boog900"]
+
+[dependencies]
+cuprate-consensus-rules =  { path = "../rules", features = ["proptest"]}
+cuprate-helper = { path = "../../helper", default-features = false, features = ["std", "cast"] }
+cuprate-types = { path = "../../types", default-features = false }
+
+futures = { workspace = true, features = ["std", "async-await"] }
+tokio = { workspace = true, features = ["rt-multi-thread", "macros"]}
+tokio-util = { workspace = true }
+tower = { workspace = true, features = ["util"] }
+tracing = { workspace = true, features = ["std", "attributes"] }
+thiserror = { workspace = true }
+
+monero-serai = { workspace = true, features = ["std"] }
+randomx-rs = { workspace = true }
+rayon = { workspace = true }
+thread_local = { workspace = true }
+hex = { workspace = true }
diff --git a/consensus/src/context/alt_chains.rs b/consensus/context/src/alt_chains.rs
similarity index 94%
rename from consensus/src/context/alt_chains.rs
rename to consensus/context/src/alt_chains.rs
index cd945c81..df82ef34 100644
--- a/consensus/src/context/alt_chains.rs
+++ b/consensus/context/src/alt_chains.rs
@@ -9,9 +9,8 @@ use cuprate_types::{
 };
 
 use crate::{
-    ExtendedConsensusError,
-    __private::Database,
-    context::{difficulty::DifficultyCache, rx_vms::RandomXVm, weight::BlockWeightsCache},
+    ContextCacheError, __private::Database, difficulty::DifficultyCache, rx_vms::RandomXVm,
+    weight::BlockWeightsCache,
 };
 
 pub(crate) mod sealed {
@@ -38,7 +37,7 @@ pub struct AltChainContextCache {
     pub chain_height: usize,
     /// The top hash of the alt chain.
     pub top_hash: [u8; 32],
-    /// The [`ChainID`] of the alt chain.
+    /// The [`ChainId`] of the alt chain.
     pub chain_id: Option<ChainId>,
     /// The parent [`Chain`] of this alt chain.
     pub parent_chain: Chain,
@@ -98,7 +97,7 @@ impl AltChainMap {
         &mut self,
         prev_id: [u8; 32],
         database: D,
-    ) -> Result<Box<AltChainContextCache>, ExtendedConsensusError> {
+    ) -> Result<Box<AltChainContextCache>, ContextCacheError> {
         if let Some(cache) = self.alt_cache_map.remove(&prev_id) {
             return Ok(cache);
         }
@@ -133,7 +132,7 @@ pub(crate) async fn get_alt_chain_difficulty_cache<D: Database + Clone>(
     prev_id: [u8; 32],
     main_chain_difficulty_cache: &DifficultyCache,
     mut database: D,
-) -> Result<DifficultyCache, ExtendedConsensusError> {
+) -> Result<DifficultyCache, ContextCacheError> {
     // find the block with hash == prev_id.
     let BlockchainResponse::FindBlock(res) = database
         .ready()
@@ -180,7 +179,7 @@ pub(crate) async fn get_alt_chain_weight_cache<D: Database + Clone>(
     prev_id: [u8; 32],
     main_chain_weight_cache: &BlockWeightsCache,
     mut database: D,
-) -> Result<BlockWeightsCache, ExtendedConsensusError> {
+) -> Result<BlockWeightsCache, ContextCacheError> {
     // find the block with hash == prev_id.
     let BlockchainResponse::FindBlock(res) = database
         .ready()
diff --git a/consensus/src/context/difficulty.rs b/consensus/context/src/difficulty.rs
similarity index 95%
rename from consensus/src/context/difficulty.rs
rename to consensus/context/src/difficulty.rs
index 9316dc5e..e3f558a0 100644
--- a/consensus/src/context/difficulty.rs
+++ b/consensus/context/src/difficulty.rs
@@ -17,7 +17,7 @@ use cuprate_types::{
     Chain,
 };
 
-use crate::{Database, ExtendedConsensusError, HardFork};
+use crate::{ContextCacheError, Database, HardFork};
 
 /// The amount of blocks we account for to calculate difficulty
 const DIFFICULTY_WINDOW: usize = 720;
@@ -33,9 +33,9 @@ const DIFFICULTY_LAG: usize = 15;
 ///
 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
 pub struct DifficultyCacheConfig {
-    pub(crate) window: usize,
-    pub(crate) cut: usize,
-    pub(crate) lag: usize,
+    pub window: usize,
+    pub cut: usize,
+    pub lag: usize,
 }
 
 impl DifficultyCacheConfig {
@@ -73,14 +73,13 @@ impl DifficultyCacheConfig {
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct DifficultyCache {
     /// The list of timestamps in the window.
-    /// len <= [`DIFFICULTY_BLOCKS_COUNT`]
-    pub(crate) timestamps: VecDeque<u64>,
+    pub timestamps: VecDeque<u64>,
     /// The current cumulative difficulty of the chain.
-    pub(crate) cumulative_difficulties: VecDeque<u128>,
+    pub cumulative_difficulties: VecDeque<u128>,
     /// The last height we accounted for.
-    pub(crate) last_accounted_height: usize,
+    pub last_accounted_height: usize,
     /// The config
-    pub(crate) config: DifficultyCacheConfig,
+    pub config: DifficultyCacheConfig,
 }
 
 impl DifficultyCache {
@@ -91,7 +90,7 @@ impl DifficultyCache {
         config: DifficultyCacheConfig,
         database: D,
         chain: Chain,
-    ) -> Result<Self, ExtendedConsensusError> {
+    ) -> Result<Self, ContextCacheError> {
         tracing::info!("Initializing difficulty cache this may take a while.");
 
         let mut block_start = chain_height.saturating_sub(config.total_block_count());
@@ -134,7 +133,7 @@ impl DifficultyCache {
         &mut self,
         numb_blocks: usize,
         database: D,
-    ) -> Result<(), ExtendedConsensusError> {
+    ) -> Result<(), ContextCacheError> {
         let Some(retained_blocks) = self.timestamps.len().checked_sub(numb_blocks) else {
             // More blocks to pop than we have in the cache, so just restart a new cache.
             *self = Self::init_from_chain_height(
@@ -361,7 +360,7 @@ async fn get_blocks_in_pow_info<D: Database + Clone>(
     database: D,
     block_heights: Range<usize>,
     chain: Chain,
-) -> Result<(VecDeque<u64>, VecDeque<u128>), ExtendedConsensusError> {
+) -> Result<(VecDeque<u64>, VecDeque<u128>), ContextCacheError> {
     tracing::info!("Getting blocks timestamps");
 
     let BlockchainResponse::BlockExtendedHeaderInRange(ext_header) = database
diff --git a/consensus/src/context/hardforks.rs b/consensus/context/src/hardforks.rs
similarity index 90%
rename from consensus/src/context/hardforks.rs
rename to consensus/context/src/hardforks.rs
index 16ae7638..e6af492b 100644
--- a/consensus/src/context/hardforks.rs
+++ b/consensus/context/src/hardforks.rs
@@ -9,7 +9,7 @@ use cuprate_types::{
     Chain,
 };
 
-use crate::{Database, ExtendedConsensusError};
+use crate::{ContextCacheError, Database};
 
 /// The default amount of hard-fork votes to track to decide on activation of a hard-fork.
 ///
@@ -21,9 +21,9 @@ const DEFAULT_WINDOW_SIZE: usize = 10080; // supermajority window check length -
 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
 pub struct HardForkConfig {
     /// The network we are on.
-    pub(crate) info: HFsInfo,
+    pub info: HFsInfo,
     /// The amount of votes we are taking into account to decide on a fork activation.
-    pub(crate) window: usize,
+    pub window: usize,
 }
 
 impl HardForkConfig {
@@ -54,17 +54,17 @@ impl HardForkConfig {
 
 /// A struct that keeps track of the current hard-fork and current votes.
 #[derive(Debug, Clone, Eq, PartialEq)]
-pub(crate) struct HardForkState {
+pub struct HardForkState {
     /// The current active hard-fork.
-    pub(crate) current_hardfork: HardFork,
+    pub current_hardfork: HardFork,
 
     /// The hard-fork config.
-    pub(crate) config: HardForkConfig,
+    pub config: HardForkConfig,
     /// The votes in the current window.
-    pub(crate) votes: HFVotes,
+    pub votes: HFVotes,
 
     /// The last block height accounted for.
-    pub(crate) last_height: usize,
+    pub last_height: usize,
 }
 
 impl HardForkState {
@@ -74,7 +74,7 @@ impl HardForkState {
         chain_height: usize,
         config: HardForkConfig,
         mut database: D,
-    ) -> Result<Self, ExtendedConsensusError> {
+    ) -> Result<Self, ContextCacheError> {
         tracing::info!("Initializing hard-fork state this may take a while.");
 
         let block_start = chain_height.saturating_sub(config.window);
@@ -122,11 +122,11 @@ impl HardForkState {
     /// # Invariant
     ///
     /// This _must_ only be used on a main-chain cache.
-    pub(crate) async fn pop_blocks_main_chain<D: Database + Clone>(
+    pub async fn pop_blocks_main_chain<D: Database + Clone>(
         &mut self,
         numb_blocks: usize,
         database: D,
-    ) -> Result<(), ExtendedConsensusError> {
+    ) -> Result<(), ContextCacheError> {
         let Some(retained_blocks) = self.votes.total_votes().checked_sub(self.config.window) else {
             *self = Self::init_from_chain_height(
                 self.last_height + 1 - numb_blocks,
@@ -159,7 +159,7 @@ impl HardForkState {
     }
 
     /// Add a new block to the cache.
-    pub(crate) fn new_block(&mut self, vote: HardFork, height: usize) {
+    pub fn new_block(&mut self, vote: HardFork, height: usize) {
         // We don't _need_ to take in `height` but it's for safety, so we don't silently loose track
         // of blocks.
         assert_eq!(self.last_height + 1, height);
@@ -194,7 +194,7 @@ impl HardForkState {
     }
 
     /// Returns the current hard-fork.
-    pub(crate) const fn current_hardfork(&self) -> HardFork {
+    pub const fn current_hardfork(&self) -> HardFork {
         self.current_hardfork
     }
 }
@@ -205,7 +205,7 @@ async fn get_votes_in_range<D: Database>(
     database: D,
     block_heights: Range<usize>,
     window_size: usize,
-) -> Result<HFVotes, ExtendedConsensusError> {
+) -> Result<HFVotes, ContextCacheError> {
     let mut votes = HFVotes::new(window_size);
 
     let BlockchainResponse::BlockExtendedHeaderInRange(vote_list) = database
diff --git a/consensus/src/context.rs b/consensus/context/src/lib.rs
similarity index 88%
rename from consensus/src/context.rs
rename to consensus/context/src/lib.rs
index 3c944a92..82e601d7 100644
--- a/consensus/src/context.rs
+++ b/consensus/context/src/lib.rs
@@ -1,6 +1,6 @@
 //! # Blockchain Context
 //!
-//! This module contains a service to get cached context from the blockchain: [`BlockChainContext`].
+//! This crate contains a service to get cached context from the blockchain: [`BlockChainContext`].
 //! This is used during contextual validation, this does not have all the data for contextual validation
 //! (outputs) for that you will need a [`Database`].
 //!
@@ -18,14 +18,14 @@ use tokio::sync::mpsc;
 use tokio_util::sync::PollSender;
 use tower::Service;
 
-use cuprate_consensus_rules::{blocks::ContextToVerifyBlock, current_unix_timestamp, HardFork};
+use cuprate_consensus_rules::{
+    blocks::ContextToVerifyBlock, current_unix_timestamp, ConsensusError, HardFork,
+};
 
-use crate::{Database, ExtendedConsensusError};
-
-pub(crate) mod difficulty;
-pub(crate) mod hardforks;
-pub(crate) mod rx_vms;
-pub(crate) mod weight;
+pub mod difficulty;
+pub mod hardforks;
+pub mod rx_vms;
+pub mod weight;
 
 mod alt_chains;
 mod task;
@@ -36,13 +36,13 @@ use difficulty::DifficultyCache;
 use rx_vms::RandomXVm;
 use weight::BlockWeightsCache;
 
-pub(crate) use alt_chains::{sealed::AltChainRequestToken, AltChainContextCache};
+pub use alt_chains::{sealed::AltChainRequestToken, AltChainContextCache};
 pub use difficulty::DifficultyCacheConfig;
 pub use hardforks::HardForkConfig;
 pub use tokens::*;
 pub use weight::BlockWeightsCacheConfig;
 
-pub(crate) const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60;
+pub const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60;
 
 /// Config for the context service.
 pub struct ContextConfig {
@@ -91,7 +91,7 @@ impl ContextConfig {
 pub async fn initialize_blockchain_context<D>(
     cfg: ContextConfig,
     database: D,
-) -> Result<BlockChainContextService, ExtendedConsensusError>
+) -> Result<BlockChainContextService, ContextCacheError>
 where
     D: Database + Clone + Send + Sync + 'static,
     D::Future: Send + 'static,
@@ -414,3 +414,52 @@ impl Service<BlockChainContextRequest> for BlockChainContextService {
         .boxed()
     }
 }
+
+#[derive(Debug, thiserror::Error)]
+pub enum ContextCacheError {
+    /// A consensus error.
+    #[error("{0}")]
+    ConErr(#[from] ConsensusError),
+    /// A database error.
+    #[error("Database error: {0}")]
+    DBErr(#[from] tower::BoxError),
+}
+
+use __private::Database;
+
+pub mod __private {
+    use std::future::Future;
+
+    use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
+
+    /// A type alias trait used to represent a database, so we don't have to write [`tower::Service`] bounds
+    /// everywhere.
+    ///
+    /// Automatically implemented for:
+    /// ```ignore
+    /// tower::Service<BCReadRequest, Response = BCResponse, Error = tower::BoxError>
+    /// ```
+    pub trait Database:
+        tower::Service<
+        BlockchainReadRequest,
+        Response = BlockchainResponse,
+        Error = tower::BoxError,
+        Future = Self::Future2,
+    >
+    {
+        type Future2: Future<Output = Result<Self::Response, Self::Error>> + Send + 'static;
+    }
+
+    impl<
+            T: tower::Service<
+                BlockchainReadRequest,
+                Response = BlockchainResponse,
+                Error = tower::BoxError,
+            >,
+        > Database for T
+    where
+        T::Future: Future<Output = Result<Self::Response, Self::Error>> + Send + 'static,
+    {
+        type Future2 = T::Future;
+    }
+}
diff --git a/consensus/src/context/rx_vms.rs b/consensus/context/src/rx_vms.rs
similarity index 90%
rename from consensus/src/context/rx_vms.rs
rename to consensus/context/src/rx_vms.rs
index c6375fc1..803bb324 100644
--- a/consensus/src/context/rx_vms.rs
+++ b/consensus/context/src/rx_vms.rs
@@ -26,10 +26,10 @@ use cuprate_types::{
     Chain,
 };
 
-use crate::{Database, ExtendedConsensusError};
+use crate::{ContextCacheError, Database};
 
 /// The amount of randomX VMs to keep in the cache.
-const RX_SEEDS_CACHED: usize = 2;
+pub const RX_SEEDS_CACHED: usize = 2;
 
 /// A multithreaded randomX VM.
 #[derive(Debug)]
@@ -72,14 +72,14 @@ impl RandomX for RandomXVm {
 /// The randomX VMs cache, keeps the VM needed to calculate the current block's proof-of-work hash (if a VM is needed) and a
 /// couple more around this VM.
 #[derive(Clone, Debug)]
-pub(crate) struct RandomXVmCache {
+pub struct RandomXVmCache {
     /// The top [`RX_SEEDS_CACHED`] RX seeds.  
-    pub(crate) seeds: VecDeque<(usize, [u8; 32])>,
+    pub seeds: VecDeque<(usize, [u8; 32])>,
     /// The VMs for `seeds` (if after hf 12, otherwise this will be empty).
-    pub(crate) vms: HashMap<usize, Arc<RandomXVm>>,
+    pub vms: HashMap<usize, Arc<RandomXVm>>,
 
     /// A single cached VM that was given to us from a part of Cuprate.
-    pub(crate) cached_vm: Option<([u8; 32], Arc<RandomXVm>)>,
+    pub cached_vm: Option<([u8; 32], Arc<RandomXVm>)>,
 }
 
 impl RandomXVmCache {
@@ -88,7 +88,7 @@ impl RandomXVmCache {
         chain_height: usize,
         hf: &HardFork,
         database: D,
-    ) -> Result<Self, ExtendedConsensusError> {
+    ) -> Result<Self, ContextCacheError> {
         let seed_heights = get_last_rx_seed_heights(chain_height - 1, RX_SEEDS_CACHED);
         let seed_hashes = get_block_hashes(seed_heights.clone(), database).await?;
 
@@ -125,18 +125,18 @@ impl RandomXVmCache {
     }
 
     /// Add a randomX VM to the cache, with the seed it was created with.
-    pub(crate) fn add_vm(&mut self, vm: ([u8; 32], Arc<RandomXVm>)) {
+    pub fn add_vm(&mut self, vm: ([u8; 32], Arc<RandomXVm>)) {
         self.cached_vm.replace(vm);
     }
 
     /// Creates a RX VM for an alt chain, looking at the main chain RX VMs to see if we can use one
     /// of them first.
-    pub(crate) async fn get_alt_vm<D: Database>(
+    pub async fn get_alt_vm<D: Database>(
         &self,
         height: usize,
         chain: Chain,
         database: D,
-    ) -> Result<Arc<RandomXVm>, ExtendedConsensusError> {
+    ) -> Result<Arc<RandomXVm>, ContextCacheError> {
         let seed_height = randomx_seed_height(height);
 
         let BlockchainResponse::BlockHash(seed_hash) = database
@@ -162,7 +162,7 @@ impl RandomXVmCache {
     }
 
     /// Get the main-chain `RandomX` VMs.
-    pub(crate) async fn get_vms(&mut self) -> HashMap<usize, Arc<RandomXVm>> {
+    pub async fn get_vms(&mut self) -> HashMap<usize, Arc<RandomXVm>> {
         match self.seeds.len().checked_sub(self.vms.len()) {
             // No difference in the amount of seeds to VMs.
             Some(0) => (),
@@ -214,7 +214,7 @@ impl RandomXVmCache {
     }
 
     /// Removes all the `RandomX` VMs above the `new_height`.
-    pub(crate) fn pop_blocks_main_chain(&mut self, new_height: usize) {
+    pub fn pop_blocks_main_chain(&mut self, new_height: usize) {
         self.seeds.retain(|(height, _)| *height < new_height);
         self.vms.retain(|height, _| *height < new_height);
     }
@@ -222,7 +222,7 @@ impl RandomXVmCache {
     /// Add a new block to the VM cache.
     ///
     /// hash is the block hash not the blocks proof-of-work hash.
-    pub(crate) fn new_block(&mut self, height: usize, hash: &[u8; 32]) {
+    pub fn new_block(&mut self, height: usize, hash: &[u8; 32]) {
         if is_randomx_seed_height(height) {
             tracing::debug!("Block {height} is a randomX seed height, adding it to the cache.",);
 
@@ -243,7 +243,7 @@ impl RandomXVmCache {
 
 /// Get the last `amount` of RX seeds, the top height returned here will not necessarily be the RX VM for the top block
 /// in the chain as VMs include some lag before a seed activates.
-pub(crate) fn get_last_rx_seed_heights(mut last_height: usize, mut amount: usize) -> Vec<usize> {
+pub fn get_last_rx_seed_heights(mut last_height: usize, mut amount: usize) -> Vec<usize> {
     let mut seeds = Vec::with_capacity(amount);
     if is_randomx_seed_height(last_height) {
         seeds.push(last_height);
@@ -268,7 +268,7 @@ pub(crate) fn get_last_rx_seed_heights(mut last_height: usize, mut amount: usize
 async fn get_block_hashes<D: Database + Clone>(
     heights: Vec<usize>,
     database: D,
-) -> Result<Vec<[u8; 32]>, ExtendedConsensusError> {
+) -> Result<Vec<[u8; 32]>, ContextCacheError> {
     let mut fut = FuturesOrdered::new();
 
     for height in heights {
@@ -281,7 +281,7 @@ async fn get_block_hashes<D: Database + Clone>(
             else {
                 panic!("Database sent incorrect response!");
             };
-            Result::<_, ExtendedConsensusError>::Ok(hash)
+            Result::<_, ContextCacheError>::Ok(hash)
         });
     }
 
diff --git a/consensus/src/context/task.rs b/consensus/context/src/task.rs
similarity index 96%
rename from consensus/src/context/task.rs
rename to consensus/context/src/task.rs
index c51c795e..65cfea99 100644
--- a/consensus/src/context/task.rs
+++ b/consensus/context/src/task.rs
@@ -16,13 +16,10 @@ use cuprate_types::{
 };
 
 use crate::{
-    context::{
-        alt_chains::{get_alt_chain_difficulty_cache, get_alt_chain_weight_cache, AltChainMap},
-        difficulty, hardforks, rx_vms, weight, BlockChainContext, BlockChainContextRequest,
-        BlockChainContextResponse, ContextConfig, RawBlockChainContext, ValidityToken,
-        BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
-    },
-    Database, ExtendedConsensusError,
+    alt_chains::{get_alt_chain_difficulty_cache, get_alt_chain_weight_cache, AltChainMap},
+    difficulty, hardforks, rx_vms, weight, BlockChainContext, BlockChainContextRequest,
+    BlockChainContextResponse, ContextCacheError, ContextConfig, Database, RawBlockChainContext,
+    ValidityToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
 };
 
 /// A request from the context service to the context task.
@@ -68,7 +65,7 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
     pub(crate) async fn init_context(
         cfg: ContextConfig,
         mut database: D,
-    ) -> Result<Self, ExtendedConsensusError> {
+    ) -> Result<Self, ContextCacheError> {
         let ContextConfig {
             difficulty_cfg,
             weights_config,
diff --git a/consensus/src/context/tokens.rs b/consensus/context/src/tokens.rs
similarity index 100%
rename from consensus/src/context/tokens.rs
rename to consensus/context/src/tokens.rs
diff --git a/consensus/src/context/weight.rs b/consensus/context/src/weight.rs
similarity index 96%
rename from consensus/src/context/weight.rs
rename to consensus/context/src/weight.rs
index e95ae605..7f725998 100644
--- a/consensus/src/context/weight.rs
+++ b/consensus/context/src/weight.rs
@@ -21,12 +21,12 @@ use cuprate_types::{
     Chain,
 };
 
-use crate::{Database, ExtendedConsensusError, HardFork};
+use crate::{ContextCacheError, Database, HardFork};
 
 /// The short term block weight window.
-const SHORT_TERM_WINDOW: usize = 100;
+pub const SHORT_TERM_WINDOW: usize = 100;
 /// The long term block weight window.
-const LONG_TERM_WINDOW: usize = 100000;
+pub const LONG_TERM_WINDOW: usize = 100000;
 
 /// Configuration for the block weight cache.
 ///
@@ -80,7 +80,7 @@ impl BlockWeightsCache {
         config: BlockWeightsCacheConfig,
         database: D,
         chain: Chain,
-    ) -> Result<Self, ExtendedConsensusError> {
+    ) -> Result<Self, ContextCacheError> {
         tracing::info!("Initializing weight cache this may take a while.");
 
         let long_term_weights = get_long_term_weight_in_range(
@@ -121,7 +121,7 @@ impl BlockWeightsCache {
         &mut self,
         numb_blocks: usize,
         database: D,
-    ) -> Result<(), ExtendedConsensusError> {
+    ) -> Result<(), ContextCacheError> {
         if self.long_term_weights.window_len() <= numb_blocks {
             // More blocks to pop than we have in the cache, so just restart a new cache.
             *self = Self::init_from_chain_height(
@@ -258,7 +258,7 @@ fn calculate_effective_median_block_weight(
 }
 
 /// Calculates a blocks long term weight.
-pub(crate) fn calculate_block_long_term_weight(
+pub fn calculate_block_long_term_weight(
     hf: HardFork,
     block_weight: usize,
     long_term_median: usize,
@@ -287,7 +287,7 @@ async fn get_blocks_weight_in_range<D: Database + Clone>(
     range: Range<usize>,
     database: D,
     chain: Chain,
-) -> Result<Vec<usize>, ExtendedConsensusError> {
+) -> Result<Vec<usize>, ContextCacheError> {
     tracing::info!("getting block weights.");
 
     let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
@@ -311,7 +311,7 @@ async fn get_long_term_weight_in_range<D: Database + Clone>(
     range: Range<usize>,
     database: D,
     chain: Chain,
-) -> Result<Vec<usize>, ExtendedConsensusError> {
+) -> Result<Vec<usize>, ContextCacheError> {
     tracing::info!("getting block long term weights.");
 
     let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
diff --git a/consensus/fast-sync/Cargo.toml b/consensus/fast-sync/Cargo.toml
index 1d7d97b4..aa9c8d2f 100644
--- a/consensus/fast-sync/Cargo.toml
+++ b/consensus/fast-sync/Cargo.toml
@@ -9,11 +9,12 @@ name = "cuprate-fast-sync-create-hashes"
 path = "src/create.rs"
 
 [dependencies]
-cuprate-blockchain      = { path = "../../storage/blockchain" }
-cuprate-consensus       = { path = ".." }
-cuprate-consensus-rules = { path = "../rules" }
-cuprate-types           = { path = "../../types" }
-cuprate-helper          = { path = "../../helper", features = ["cast"] }
+cuprate-blockchain          = { path = "../../storage/blockchain" }
+cuprate-consensus           = { path = ".." }
+cuprate-consensus-rules     = { path = "../rules" }
+cuprate-consensus-context   = { path = "../context" }
+cuprate-types               = { path = "../../types" }
+cuprate-helper              = { path = "../../helper", features = ["cast"] }
 
 clap         = { workspace = true, features = ["derive", "std"] }
 hex          = { workspace = true }
@@ -27,4 +28,4 @@ tower        = { workspace = true }
 [dev-dependencies]
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/consensus/fast-sync/src/fast_sync.rs b/consensus/fast-sync/src/fast_sync.rs
index ec4ea297..3764e217 100644
--- a/consensus/fast-sync/src/fast_sync.rs
+++ b/consensus/fast-sync/src/fast_sync.rs
@@ -12,10 +12,8 @@ use monero_serai::{
 };
 use tower::{Service, ServiceExt};
 
-use cuprate_consensus::{
-    context::{BlockChainContextRequest, BlockChainContextResponse},
-    transactions::new_tx_verification_data,
-};
+use cuprate_consensus::transactions::new_tx_verification_data;
+use cuprate_consensus_context::{BlockChainContextRequest, BlockChainContextResponse};
 use cuprate_consensus_rules::{miner_tx::MinerTxError, ConsensusError};
 use cuprate_helper::cast::u64_to_usize;
 use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
diff --git a/consensus/src/block.rs b/consensus/src/block.rs
index ceb2cbab..3f5d749e 100644
--- a/consensus/src/block.rs
+++ b/consensus/src/block.rs
@@ -14,6 +14,9 @@ use monero_serai::{
 };
 use tower::{Service, ServiceExt};
 
+use cuprate_consensus_context::{
+    BlockChainContextRequest, BlockChainContextResponse, RawBlockChainContext,
+};
 use cuprate_helper::asynch::rayon_spawn_async;
 use cuprate_types::{
     AltBlockInformation, TransactionVerificationData, VerifiedBlockInformation,
@@ -30,7 +33,6 @@ use cuprate_consensus_rules::{
 };
 
 use crate::{
-    context::{BlockChainContextRequest, BlockChainContextResponse, RawBlockChainContext},
     transactions::{VerifyTxRequest, VerifyTxResponse},
     Database, ExtendedConsensusError,
 };
diff --git a/consensus/src/block/alt_block.rs b/consensus/src/block/alt_block.rs
index 3a5ea7cb..18c27345 100644
--- a/consensus/src/block/alt_block.rs
+++ b/consensus/src/block/alt_block.rs
@@ -7,6 +7,12 @@ use std::{collections::HashMap, sync::Arc};
 use monero_serai::{block::Block, transaction::Input};
 use tower::{Service, ServiceExt};
 
+use cuprate_consensus_context::{
+    difficulty::DifficultyCache,
+    rx_vms::RandomXVm,
+    weight::{self, BlockWeightsCache},
+    AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
+};
 use cuprate_consensus_rules::{
     blocks::{
         check_block_pow, check_block_weight, check_timestamp, randomx_seed_height, BlockError,
@@ -22,12 +28,6 @@ use cuprate_types::{
 
 use crate::{
     block::{free::pull_ordered_transactions, PreparedBlock},
-    context::{
-        difficulty::DifficultyCache,
-        rx_vms::RandomXVm,
-        weight::{self, BlockWeightsCache},
-        AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
-    },
     BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError,
     VerifyBlockResponse,
 };
diff --git a/consensus/src/block/batch_prepare.rs b/consensus/src/block/batch_prepare.rs
index 029a5ae6..ef384f5d 100644
--- a/consensus/src/block/batch_prepare.rs
+++ b/consensus/src/block/batch_prepare.rs
@@ -5,6 +5,7 @@ use rayon::prelude::*;
 use tower::{Service, ServiceExt};
 use tracing::instrument;
 
+use cuprate_consensus_context::rx_vms::RandomXVm;
 use cuprate_consensus_rules::{
     blocks::{check_block_pow, is_randomx_seed_height, randomx_seed_height, BlockError},
     hard_forks::HardForkError,
@@ -15,7 +16,6 @@ use cuprate_helper::asynch::rayon_spawn_async;
 
 use crate::{
     block::{free::pull_ordered_transactions, PreparedBlock, PreparedBlockExPow},
-    context::rx_vms::RandomXVm,
     transactions::new_tx_verification_data,
     BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError,
     VerifyBlockResponse,
diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs
index 7280f2ff..f21d00b2 100644
--- a/consensus/src/lib.rs
+++ b/consensus/src/lib.rs
@@ -24,13 +24,12 @@ use cuprate_consensus_rules::ConsensusError;
 
 mod batch_verifier;
 pub mod block;
-pub mod context;
 #[cfg(test)]
 mod tests;
 pub mod transactions;
 
 pub use block::{BlockVerifierService, VerifyBlockRequest, VerifyBlockResponse};
-pub use context::{
+pub use cuprate_consensus_context::{
     initialize_blockchain_context, BlockChainContext, BlockChainContextRequest,
     BlockChainContextResponse, BlockChainContextService, ContextConfig,
 };
diff --git a/consensus/src/tests/context.rs b/consensus/src/tests/context.rs
index fdef0ac8..b9c52177 100644
--- a/consensus/src/tests/context.rs
+++ b/consensus/src/tests/context.rs
@@ -2,15 +2,13 @@ use proptest::strategy::ValueTree;
 use proptest::{strategy::Strategy, test_runner::TestRunner};
 use tower::ServiceExt;
 
-use crate::{
-    context::{
-        initialize_blockchain_context, BlockChainContextRequest, BlockChainContextResponse,
-        ContextConfig, NewBlockData,
-    },
-    tests::mock_db::*,
-    HardFork,
+use cuprate_consensus_context::{
+    initialize_blockchain_context, BlockChainContextRequest, BlockChainContextResponse,
+    ContextConfig, NewBlockData,
 };
 
+use crate::{tests::mock_db::*, HardFork};
+
 pub(crate) mod data;
 mod difficulty;
 mod hardforks;
diff --git a/consensus/src/tests/context/difficulty.rs b/consensus/src/tests/context/difficulty.rs
index d5027f50..f1c0fd97 100644
--- a/consensus/src/tests/context/difficulty.rs
+++ b/consensus/src/tests/context/difficulty.rs
@@ -4,10 +4,10 @@ use proptest::collection::{size_range, vec};
 use proptest::{prelude::*, prop_assert_eq, prop_compose, proptest};
 
 use crate::{
-    context::difficulty::*,
     tests::{context::data::DIF_3000000_3002000, mock_db::*},
     HardFork,
 };
+use cuprate_consensus_context::difficulty::*;
 use cuprate_helper::num::median;
 use cuprate_types::Chain;
 
diff --git a/consensus/src/tests/context/hardforks.rs b/consensus/src/tests/context/hardforks.rs
index 17bd47f9..f0800232 100644
--- a/consensus/src/tests/context/hardforks.rs
+++ b/consensus/src/tests/context/hardforks.rs
@@ -1,13 +1,11 @@
 use proptest::{collection::vec, prelude::*};
 
+use cuprate_consensus_context::{hardforks::HardForkState, HardForkConfig};
 use cuprate_consensus_rules::hard_forks::{HFInfo, HFsInfo, HardFork, NUMB_OF_HARD_FORKS};
 
-use crate::{
-    context::{hardforks::HardForkState, HardForkConfig},
-    tests::{
-        context::data::{HFS_2678808_2688888, HFS_2688888_2689608},
-        mock_db::*,
-    },
+use crate::tests::{
+    context::data::{HFS_2678808_2688888, HFS_2688888_2689608},
+    mock_db::*,
 };
 
 const TEST_WINDOW_SIZE: usize = 25;
diff --git a/consensus/src/tests/context/rx_vms.rs b/consensus/src/tests/context/rx_vms.rs
index b1eba8e2..41c62796 100644
--- a/consensus/src/tests/context/rx_vms.rs
+++ b/consensus/src/tests/context/rx_vms.rs
@@ -3,15 +3,13 @@ use std::collections::VecDeque;
 use proptest::prelude::*;
 use tokio::runtime::Builder;
 
+use cuprate_consensus_context::rx_vms::{get_last_rx_seed_heights, RandomXVmCache};
 use cuprate_consensus_rules::{
     blocks::{is_randomx_seed_height, randomx_seed_height},
     HardFork,
 };
 
-use crate::{
-    context::rx_vms::{get_last_rx_seed_heights, RandomXVmCache},
-    tests::mock_db::*,
-};
+use crate::tests::mock_db::*;
 
 #[test]
 fn rx_heights_consistent() {
diff --git a/consensus/src/tests/context/weight.rs b/consensus/src/tests/context/weight.rs
index b23f8f80..dab3979e 100644
--- a/consensus/src/tests/context/weight.rs
+++ b/consensus/src/tests/context/weight.rs
@@ -1,11 +1,11 @@
 use crate::{
-    context::{
-        weight::{calculate_block_long_term_weight, BlockWeightsCache},
-        BlockWeightsCacheConfig,
-    },
     tests::{context::data::BW_2850000_3050000, mock_db::*},
     HardFork,
 };
+use cuprate_consensus_context::{
+    weight::{calculate_block_long_term_weight, BlockWeightsCache},
+    BlockWeightsCacheConfig,
+};
 use cuprate_types::Chain;
 
 pub(crate) const TEST_WEIGHT_CONFIG: BlockWeightsCacheConfig =

From 4b350e897d76abfc4fdccd152ff02b966d549031 Mon Sep 17 00:00:00 2001
From: hinto-janai <hinto.janai@protonmail.com>
Date: Tue, 22 Oct 2024 12:35:54 -0400
Subject: [PATCH 2/4] consensus-context: enable workspace lints (#321)

enable lints, fix 1.82 clippy
---
 consensus/context/Cargo.toml        | 7 +++++--
 consensus/context/src/difficulty.rs | 2 +-
 consensus/context/src/lib.rs        | 6 +++++-
 consensus/rules/Cargo.toml          | 2 +-
 consensus/rules/src/lib.rs          | 4 ++--
 consensus/rules/src/miner_tx.rs     | 2 +-
 6 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/consensus/context/Cargo.toml b/consensus/context/Cargo.toml
index 00804204..f7642e8a 100644
--- a/consensus/context/Cargo.toml
+++ b/consensus/context/Cargo.toml
@@ -7,8 +7,8 @@ authors = ["SyntheticBird","Boog900"]
 
 [dependencies]
 cuprate-consensus-rules =  { path = "../rules", features = ["proptest"]}
-cuprate-helper = { path = "../../helper", default-features = false, features = ["std", "cast"] }
-cuprate-types = { path = "../../types", default-features = false }
+cuprate-helper = { path = "../../helper", default-features = false, features = ["std", "cast", "num", "asynch"] }
+cuprate-types = { path = "../../types", default-features = false, features = ["blockchain"] }
 
 futures = { workspace = true, features = ["std", "async-await"] }
 tokio = { workspace = true, features = ["rt-multi-thread", "macros"]}
@@ -22,3 +22,6 @@ randomx-rs = { workspace = true }
 rayon = { workspace = true }
 thread_local = { workspace = true }
 hex = { workspace = true }
+
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/consensus/context/src/difficulty.rs b/consensus/context/src/difficulty.rs
index e3f558a0..1b61eb9e 100644
--- a/consensus/context/src/difficulty.rs
+++ b/consensus/context/src/difficulty.rs
@@ -329,7 +329,7 @@ fn next_difficulty(
     }
 
     // TODO: do checked operations here and unwrap so we don't silently overflow?
-    (windowed_work * hf.block_time().as_secs() as u128 + time_span - 1) / time_span
+    (windowed_work * u128::from(hf.block_time().as_secs()) + time_span - 1) / time_span
 }
 
 /// Get the start and end of the window to calculate difficulty.
diff --git a/consensus/context/src/lib.rs b/consensus/context/src/lib.rs
index 82e601d7..198d5a1d 100644
--- a/consensus/context/src/lib.rs
+++ b/consensus/context/src/lib.rs
@@ -3,7 +3,11 @@
 //! This crate contains a service to get cached context from the blockchain: [`BlockChainContext`].
 //! This is used during contextual validation, this does not have all the data for contextual validation
 //! (outputs) for that you will need a [`Database`].
-//!
+
+// Used in documentation references for [`BlockChainContextRequest`]
+// FIXME: should we pull in a dependency just to link docs?
+use monero_serai as _;
+
 use std::{
     cmp::min,
     collections::HashMap,
diff --git a/consensus/rules/Cargo.toml b/consensus/rules/Cargo.toml
index 50117acf..fac22bcc 100644
--- a/consensus/rules/Cargo.toml
+++ b/consensus/rules/Cargo.toml
@@ -11,7 +11,7 @@ proptest = ["cuprate-types/proptest"]
 rayon = ["dep:rayon"]
 
 [dependencies]
-cuprate-constants = { path = "../../constants", default-features = false }
+cuprate-constants = { path = "../../constants", default-features = false, features = ["block"] }
 cuprate-helper = { path = "../../helper", default-features = false, features = ["std", "cast"] }
 cuprate-types = { path = "../../types", default-features = false }
 cuprate-cryptonight = {path = "../../cryptonight"}
diff --git a/consensus/rules/src/lib.rs b/consensus/rules/src/lib.rs
index 876e2f7f..eef20c1e 100644
--- a/consensus/rules/src/lib.rs
+++ b/consensus/rules/src/lib.rs
@@ -63,9 +63,9 @@ where
 /// An internal function that returns an iterator or a parallel iterator if the
 /// `rayon` feature is enabled.
 #[cfg(not(feature = "rayon"))]
-fn try_par_iter<T>(t: T) -> impl std::iter::Iterator<Item = T::Item>
+fn try_par_iter<T>(t: T) -> impl Iterator<Item = T::Item>
 where
-    T: std::iter::IntoIterator,
+    T: IntoIterator,
 {
     t.into_iter()
 }
diff --git a/consensus/rules/src/miner_tx.rs b/consensus/rules/src/miner_tx.rs
index 5221ee55..bb3b004a 100644
--- a/consensus/rules/src/miner_tx.rs
+++ b/consensus/rules/src/miner_tx.rs
@@ -68,7 +68,7 @@ pub fn calculate_block_reward(
         .unwrap();
     let effective_median_bw: u128 = median_bw.try_into().unwrap();
 
-    (((base_reward as u128 * multiplicand) / effective_median_bw) / effective_median_bw)
+    (((u128::from(base_reward) * multiplicand) / effective_median_bw) / effective_median_bw)
         .try_into()
         .unwrap()
 }

From b8e2d00af492a5262185d2dda6a42f6bd3b6a7ea Mon Sep 17 00:00:00 2001
From: SyntheticBird <118022351+SyntheticBird45@users.noreply.github.com>
Date: Thu, 24 Oct 2024 23:10:33 +0200
Subject: [PATCH 3/4] storage: Add common amounts commitment lookup table
 (#323)

Add common ammounts commitment lookup table

- Implements `compute_zero_commitment` function in `cuprate-helper::crypto` module.
- Added test that compare the function output with the correct calculation.
- Use of a constant-time algorithm for the lookup table.
- Added according documentation
---
 constants/Cargo.toml                 |   2 +-
 helper/Cargo.toml                    |  14 +--
 helper/src/crypto.rs                 | 122 +++++++++++++++++++++++++++
 helper/src/lib.rs                    |   3 +
 storage/blockchain/Cargo.toml        |   2 +-
 storage/blockchain/src/ops/output.rs |   9 +-
 storage/blockchain/src/ops/tx.rs     |   9 +-
 7 files changed, 142 insertions(+), 19 deletions(-)
 create mode 100644 helper/src/crypto.rs

diff --git a/constants/Cargo.toml b/constants/Cargo.toml
index 6d3e031b..5ce37325 100644
--- a/constants/Cargo.toml
+++ b/constants/Cargo.toml
@@ -19,4 +19,4 @@ rpc     = []
 [dev-dependencies]
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/helper/Cargo.toml b/helper/Cargo.toml
index 111c6f02..c70efb0c 100644
--- a/helper/Cargo.toml
+++ b/helper/Cargo.toml
@@ -16,6 +16,7 @@ atomic    = ["dep:crossbeam"]
 asynch    = ["dep:futures", "dep:rayon"]
 cast      = []
 constants = []
+crypto    = ["dep:curve25519-dalek", "dep:monero-serai", "std"]
 fs        = ["dep:dirs"]
 num       = []
 map       = ["cast", "dep:monero-serai", "dep:cuprate-constants"]
@@ -26,12 +27,13 @@ tx        = ["dep:monero-serai"]
 [dependencies]
 cuprate-constants = { path = "../constants", optional = true, features = ["block"] }
 
-crossbeam    = { workspace = true, optional = true }
-chrono       = { workspace = true, optional = true, features = ["std", "clock"] }
-dirs         = { workspace = true, optional = true }
-futures      = { workspace = true, optional = true, features = ["std"] }
-monero-serai = { workspace = true, optional = true }
-rayon        = { workspace = true, optional = true }
+chrono           = { workspace = true, optional = true, features = ["std", "clock"] }
+crossbeam        = { workspace = true, optional = true }
+curve25519-dalek = { workspace = true, optional = true }
+dirs             = { workspace = true, optional = true }
+futures          = { workspace = true, optional = true, features = ["std"] }
+monero-serai     = { workspace = true, optional = true }
+rayon            = { workspace = true, optional = true }
 
 # This is kinda a stupid work around.
 # [thread] needs to activate one of these libs (windows|libc)
diff --git a/helper/src/crypto.rs b/helper/src/crypto.rs
new file mode 100644
index 00000000..1a27cd30
--- /dev/null
+++ b/helper/src/crypto.rs
@@ -0,0 +1,122 @@
+//! Crypto related functions and runtime initialized constants
+
+//---------------------------------------------------------------------------------------------------- Use
+use std::sync::LazyLock;
+
+use curve25519_dalek::{
+    constants::ED25519_BASEPOINT_POINT, edwards::VartimeEdwardsPrecomputation,
+    traits::VartimePrecomputedMultiscalarMul, EdwardsPoint, Scalar,
+};
+use monero_serai::generators::H;
+
+//---------------------------------------------------------------------------------------------------- Pre-computation
+
+/// This is the decomposed amount table containing the mandatory Pre-RCT amounts. It is used to pre-compute 
+/// zero commitments at runtime.
+/// 
+/// Defined at:
+/// - <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/ringct/rctOps.cpp#L44>
+#[rustfmt::skip]
+pub const ZERO_COMMITMENT_DECOMPOSED_AMOUNT: [u64; 172] = [
+    1,                   2,                   3,                   4,                   5,                   6,                   7,                   8,                   9,
+    10,                  20,                  30,                  40,                  50,                  60,                  70,                  80,                  90,
+    100,                 200,                 300,                 400,                 500,                 600,                 700,                 800,                 900,
+    1000,                2000,                3000,                4000,                5000,                6000,                7000,                8000,                9000,
+    10000,               20000,               30000,               40000,               50000,               60000,               70000,               80000,               90000,
+    100000,              200000,              300000,              400000,              500000,              600000,              700000,              800000,              900000,
+    1000000,             2000000,             3000000,             4000000,             5000000,             6000000,             7000000,             8000000,             9000000,
+    10000000,            20000000,            30000000,            40000000,            50000000,            60000000,            70000000,            80000000,            90000000,
+    100000000,           200000000,           300000000,           400000000,           500000000,           600000000,           700000000,           800000000,           900000000,
+    1000000000,          2000000000,          3000000000,          4000000000,          5000000000,          6000000000,          7000000000,          8000000000,          9000000000,
+    10000000000,         20000000000,         30000000000,         40000000000,         50000000000,         60000000000,         70000000000,         80000000000,         90000000000,
+    100000000000,        200000000000,        300000000000,        400000000000,        500000000000,        600000000000,        700000000000,        800000000000,        900000000000,
+    1000000000000,       2000000000000,       3000000000000,       4000000000000,       5000000000000,       6000000000000,       7000000000000,       8000000000000,       9000000000000,
+    10000000000000,      20000000000000,      30000000000000,      40000000000000,      50000000000000,      60000000000000,      70000000000000,      80000000000000,      90000000000000,
+    100000000000000,     200000000000000,     300000000000000,     400000000000000,     500000000000000,     600000000000000,     700000000000000,     800000000000000,     900000000000000,
+    1000000000000000,    2000000000000000,    3000000000000000,    4000000000000000,    5000000000000000,    6000000000000000,    7000000000000000,    8000000000000000,    9000000000000000,
+    10000000000000000,   20000000000000000,   30000000000000000,   40000000000000000,   50000000000000000,   60000000000000000,   70000000000000000,   80000000000000000,   90000000000000000,
+    100000000000000000,  200000000000000000,  300000000000000000,  400000000000000000,  500000000000000000,  600000000000000000,  700000000000000000,  800000000000000000,  900000000000000000,
+    1000000000000000000, 2000000000000000000, 3000000000000000000, 4000000000000000000, 5000000000000000000, 6000000000000000000, 7000000000000000000, 8000000000000000000, 9000000000000000000,
+    10000000000000000000
+];
+
+/// Runtime initialized [`H`] generator.
+static H_PRECOMP: LazyLock<VartimeEdwardsPrecomputation> =
+    LazyLock::new(|| VartimeEdwardsPrecomputation::new([*H, ED25519_BASEPOINT_POINT]));
+
+/// Runtime initialized zero commitment lookup table
+///
+/// # Invariant
+/// This function assumes that the [`ZERO_COMMITMENT_DECOMPOSED_AMOUNT`]
+/// table is sorted.
+pub static ZERO_COMMITMENT_LOOKUP_TABLE: LazyLock<[EdwardsPoint; 172]> = LazyLock::new(|| {
+    let mut lookup_table: [EdwardsPoint; 172] = [ED25519_BASEPOINT_POINT; 172];
+
+    for (i, amount) in ZERO_COMMITMENT_DECOMPOSED_AMOUNT.into_iter().enumerate() {
+        lookup_table[i] = ED25519_BASEPOINT_POINT + *H * Scalar::from(amount);
+    }
+
+    lookup_table
+});
+
+//---------------------------------------------------------------------------------------------------- Free functions
+
+/// This function computes the zero commitment given a specific amount.
+///
+/// It will first attempt to lookup into the table of known Pre-RCT value.
+/// Compute it otherwise.
+#[expect(clippy::cast_possible_truncation)]
+pub fn compute_zero_commitment(amount: u64) -> EdwardsPoint {
+    // OPTIMIZATION: Unlike monerod which execute a linear search across its lookup
+    // table (O(n)). Cuprate is making use of an arithmetic based constant time
+    // version (O(1)). It has been benchmarked in both hit and miss scenarios against
+    // a binary search lookup (O(log2(n))). To understand the following algorithm it
+    // is important to observe the pattern that follows the values of
+    // [`ZERO_COMMITMENT_DECOMPOSED_AMOUNT`].
+
+    // First obtain the logarithm base 10 of the amount. and extend it back to obtain
+    // the amount without its most significant digit.
+    let Some(log) = amount.checked_ilog10() else {
+        // amount = 0 so H component is 0.
+        return ED25519_BASEPOINT_POINT;
+    };
+    let div = 10_u64.pow(log);
+
+    // Extract the most significant digit.
+    let most_significant_digit = amount / div;
+
+    // If the *rounded* version is different than the exact amount. Then
+    // there aren't only trailing zeroes behind the most significant digit.
+    // The amount is not part of the table and can calculated apart.
+    if most_significant_digit * div != amount {
+        return H_PRECOMP.vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE]);
+    }
+
+    // Calculating the index back by progressing within the powers of 10.
+    // The index of the first value in the cached amount's row.
+    let row_start = u64::from(log) * 9;
+    // The index of the cached amount
+    let index = (most_significant_digit - 1 + row_start) as usize;
+
+    ZERO_COMMITMENT_LOOKUP_TABLE[index]
+}
+
+//---------------------------------------------------------------------------------------------------- Tests
+#[cfg(test)]
+mod test {
+    use curve25519_dalek::{traits::VartimePrecomputedMultiscalarMul, Scalar};
+
+    use crate::crypto::{compute_zero_commitment, H_PRECOMP, ZERO_COMMITMENT_DECOMPOSED_AMOUNT};
+
+    #[test]
+    /// Compare the output of `compute_zero_commitment` for all
+    /// preRCT decomposed amounts against their actual computation.
+    ///
+    /// Assert that the lookup table returns the correct commitments
+    fn compare_lookup_with_computation() {
+        for amount in ZERO_COMMITMENT_DECOMPOSED_AMOUNT {
+            let commitment = H_PRECOMP.vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE]);
+            assert!(commitment == compute_zero_commitment(amount));
+        }
+    }
+}
diff --git a/helper/src/lib.rs b/helper/src/lib.rs
index bfd2fd60..47d47a23 100644
--- a/helper/src/lib.rs
+++ b/helper/src/lib.rs
@@ -30,6 +30,9 @@ pub mod time;
 
 #[cfg(feature = "tx")]
 pub mod tx;
+
+#[cfg(feature = "crypto")]
+pub mod crypto;
 //---------------------------------------------------------------------------------------------------- Private Usage
 
 //----------------------------------------------------------------------------------------------------
diff --git a/storage/blockchain/Cargo.toml b/storage/blockchain/Cargo.toml
index 00579110..414b7843 100644
--- a/storage/blockchain/Cargo.toml
+++ b/storage/blockchain/Cargo.toml
@@ -20,7 +20,7 @@ service     = ["dep:thread_local", "dep:rayon", "cuprate-helper/thread"]
 [dependencies]
 cuprate-database         = { path = "../database" }
 cuprate-database-service = { path = "../service" }
-cuprate-helper           = { path = "../../helper", features = ["fs", "map"] }
+cuprate-helper           = { path = "../../helper", features = ["fs", "map", "crypto"] }
 cuprate-types            = { path = "../../types", features = ["blockchain"] }
 cuprate-pruning          = { path = "../../pruning" }
 
diff --git a/storage/blockchain/src/ops/output.rs b/storage/blockchain/src/ops/output.rs
index 1c7c1d78..14c209ab 100644
--- a/storage/blockchain/src/ops/output.rs
+++ b/storage/blockchain/src/ops/output.rs
@@ -1,12 +1,13 @@
 //! Output functions.
 
 //---------------------------------------------------------------------------------------------------- Import
-use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY, Scalar};
-use monero_serai::{generators::H, transaction::Timelock};
+use curve25519_dalek::edwards::CompressedEdwardsY;
+use monero_serai::transaction::Timelock;
 
 use cuprate_database::{
     RuntimeError, {DatabaseRo, DatabaseRw},
 };
+use cuprate_helper::crypto::compute_zero_commitment;
 use cuprate_helper::map::u64_to_timelock;
 use cuprate_types::OutputOnChain;
 
@@ -155,9 +156,7 @@ pub fn output_to_output_on_chain(
     amount: Amount,
     table_tx_unlock_time: &impl DatabaseRo<TxUnlockTime>,
 ) -> Result<OutputOnChain, RuntimeError> {
-    // FIXME: implement lookup table for common values:
-    // <https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/ringct/rctOps.cpp#L322>
-    let commitment = ED25519_BASEPOINT_POINT + *H * Scalar::from(amount);
+    let commitment = compute_zero_commitment(amount);
 
     let time_lock = if output
         .output_flags
diff --git a/storage/blockchain/src/ops/tx.rs b/storage/blockchain/src/ops/tx.rs
index c9799a2c..5a60ad53 100644
--- a/storage/blockchain/src/ops/tx.rs
+++ b/storage/blockchain/src/ops/tx.rs
@@ -2,10 +2,10 @@
 
 //---------------------------------------------------------------------------------------------------- Import
 use bytemuck::TransparentWrapper;
-use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, Scalar};
 use monero_serai::transaction::{Input, Timelock, Transaction};
 
 use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec};
+use cuprate_helper::crypto::compute_zero_commitment;
 
 use crate::{
     ops::{
@@ -136,12 +136,9 @@ pub fn add_tx(
             .enumerate()
             .map(|(i, output)| {
                 // Create commitment.
-                // <https://github.com/Cuprate/cuprate/pull/102#discussion_r1559489302>
-                // FIXME: implement lookup table for common values:
-                // <https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/ringct/rctOps.cpp#L322>
+
                 let commitment = if miner_tx {
-                    ED25519_BASEPOINT_POINT
-                        + *monero_serai::generators::H * Scalar::from(output.amount.unwrap_or(0))
+                    compute_zero_commitment(output.amount.unwrap_or(0))
                 } else {
                     proofs
                         .as_ref()

From 63216aecaead915550be93236faf6aeeebf0e04f Mon Sep 17 00:00:00 2001
From: SyntheticBird <118022351+SyntheticBird45@users.noreply.github.com>
Date: Fri, 25 Oct 2024 00:12:30 +0200
Subject: [PATCH 4/4] workspace: Defines cuprate members as workspace
 dependencies  (#326)

Defines cuprate members as workspace dependencies

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

Co-authored-by: Boog900 <boog900@tutanota.com>
---
 Cargo.toml                     | 29 ++++++++++++++++++++
 binaries/cuprated/Cargo.toml   | 50 +++++++++++++++++-----------------
 consensus/Cargo.toml           | 12 ++++----
 consensus/context/Cargo.toml   |  8 +++---
 consensus/fast-sync/Cargo.toml | 12 ++++----
 consensus/rules/Cargo.toml     |  8 +++---
 helper/Cargo.toml              |  2 +-
 net/epee-encoding/Cargo.toml   |  6 ++--
 net/levin/Cargo.toml           |  4 +--
 net/wire/Cargo.toml            | 10 +++----
 p2p/address-book/Cargo.toml    | 10 +++----
 p2p/p2p-core/Cargo.toml        | 10 +++----
 p2p/p2p/Cargo.toml             | 22 +++++++--------
 pruning/Cargo.toml             |  4 +--
 rpc/interface/Cargo.toml       | 10 +++----
 rpc/types/Cargo.toml           | 10 +++----
 storage/blockchain/Cargo.toml  | 16 +++++------
 storage/service/Cargo.toml     |  4 +--
 storage/txpool/Cargo.toml      | 12 ++++----
 test-utils/Cargo.toml          | 10 +++----
 types/Cargo.toml               |  8 +++---
 21 files changed, 143 insertions(+), 114 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 2ef99d62..38658632 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,6 +50,35 @@ opt-level     = 1
 opt-level = 3
 
 [workspace.dependencies]
+# Cuprate members
+cuprate-fast-sync           = { path = "consensus/fast-sync"    ,default-features = false}
+cuprate-consensus-rules     = { path = "consensus/rules"        ,default-features = false}
+cuprate-constants           = { path = "constants"              ,default-features = false}
+cuprate-consensus           = { path = "consensus"              ,default-features = false}
+cuprate-consensus-context   = { path = "consensus/context"      ,default-features = false}
+cuprate-cryptonight         = { path = "cryptonight"            ,default-features = false}
+cuprate-helper              = { path = "helper"                 ,default-features = false}
+cuprate-epee-encoding       = { path = "net/epee-encoding"      ,default-features = false}
+cuprate-fixed-bytes         = { path = "net/fixed-bytes"        ,default-features = false}
+cuprate-levin               = { path = "net/levin"              ,default-features = false}
+cuprate-wire                = { path = "net/wire"               ,default-features = false}
+cuprate-p2p                 = { path = "p2p/p2p"                ,default-features = false}
+cuprate-p2p-core            = { path = "p2p/p2p-core"           ,default-features = false}
+cuprate-dandelion-tower     = { path = "p2p/dandelion-tower"    ,default-features = false}
+cuprate-async-buffer        = { path = "p2p/async-buffer"       ,default-features = false}
+cuprate-address-book        = { path = "p2p/address-book"       ,default-features = false}
+cuprate-blockchain          = { path = "storage/blockchain"     ,default-features = false}
+cuprate-database            = { path = "storage/database"       ,default-features = false}
+cuprate-database-service    = { path = "storage/service"        ,default-features = false}
+cuprate-txpool              = { path = "storage/txpool"         ,default-features = false}
+cuprate-pruning             = { path = "pruning"                ,default-features = false}
+cuprate-test-utils          = { path = "test-utils"             ,default-features = false}
+cuprate-types               = { path = "types"                  ,default-features = false}
+cuprate-json-rpc            = { path = "rpc/json-rpc"           ,default-features = false}
+cuprate-rpc-types           = { path = "rpc/types"              ,default-features = false}
+cuprate-rpc-interface       = { path = "rpc/interface"          ,default-features = false}
+
+# 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 }
diff --git a/binaries/cuprated/Cargo.toml b/binaries/cuprated/Cargo.toml
index 59fa9784..2f22be0c 100644
--- a/binaries/cuprated/Cargo.toml
+++ b/binaries/cuprated/Cargo.toml
@@ -9,31 +9,31 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/binaries/cuprated"
 
 [dependencies]
 # TODO: after v1.0.0, remove unneeded dependencies.
-cuprate-consensus        = { path = "../../consensus" }
-cuprate-fast-sync        = { path = "../../consensus/fast-sync" }
-cuprate-consensus-context = { path = "../../consensus/context" }
-cuprate-consensus-rules  = { path = "../../consensus/rules" }
-cuprate-cryptonight      = { path = "../../cryptonight" }
-cuprate-helper           = { path = "../../helper" }
-cuprate-epee-encoding    = { path = "../../net/epee-encoding" }
-cuprate-fixed-bytes      = { path = "../../net/fixed-bytes" }
-cuprate-levin            = { path = "../../net/levin" }
-cuprate-wire             = { path = "../../net/wire" }
-cuprate-p2p              = { path = "../../p2p/p2p" }
-cuprate-p2p-core         = { path = "../../p2p/p2p-core" }
-cuprate-dandelion-tower  = { path = "../../p2p/dandelion-tower" }
-cuprate-async-buffer     = { path = "../../p2p/async-buffer" }
-cuprate-address-book     = { path = "../../p2p/address-book" }
-cuprate-blockchain       = { path = "../../storage/blockchain", features = ["service"] }
-cuprate-database-service = { path = "../../storage/service" }
-cuprate-txpool           = { path = "../../storage/txpool" }
-cuprate-database         = { path = "../../storage/database" }
-cuprate-pruning          = { path = "../../pruning" }
-cuprate-test-utils       = { path = "../../test-utils" }
-cuprate-types            = { path = "../../types" }
-cuprate-json-rpc         = { path = "../../rpc/json-rpc" }
-cuprate-rpc-interface    = { path = "../../rpc/interface" }
-cuprate-rpc-types        = { path = "../../rpc/types" }
+cuprate-consensus        = { workspace = true }
+cuprate-fast-sync        = { workspace = true }
+cuprate-consensus-context = { workspace = true }
+cuprate-consensus-rules  = { workspace = true }
+cuprate-cryptonight      = { workspace = true }
+cuprate-helper           = { workspace = true }
+cuprate-epee-encoding    = { workspace = true }
+cuprate-fixed-bytes      = { workspace = true }
+cuprate-levin            = { workspace = true }
+cuprate-wire             = { workspace = true }
+cuprate-p2p              = { workspace = true }
+cuprate-p2p-core         = { workspace = true }
+cuprate-dandelion-tower  = { workspace = true }
+cuprate-async-buffer     = { workspace = true }
+cuprate-address-book     = { workspace = true }
+cuprate-blockchain       = { workspace = true, features = ["service"] }
+cuprate-database-service = { workspace = true }
+cuprate-txpool           = { workspace = true }
+cuprate-database         = { workspace = true }
+cuprate-pruning          = { workspace = true }
+cuprate-test-utils       = { workspace = true }
+cuprate-types            = { workspace = true }
+cuprate-json-rpc         = { workspace = true }
+cuprate-rpc-interface    = { workspace = true }
+cuprate-rpc-types        = { workspace = true }
 
 # TODO: after v1.0.0, remove unneeded dependencies.
 anyhow                = { workspace = true }
diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml
index 1fdee89a..8b732a07 100644
--- a/consensus/Cargo.toml
+++ b/consensus/Cargo.toml
@@ -8,10 +8,10 @@ authors = ["Boog900"]
 repository = "https://github.com/Cuprate/cuprate/tree/main/consensus"
 
 [dependencies]
-cuprate-helper = { path = "../helper", default-features = false, features = ["std", "asynch", "num"] }
-cuprate-consensus-rules = { path = "./rules", features = ["rayon"] }
-cuprate-types = { path = "../types" }
-cuprate-consensus-context = { path = "./context" }
+cuprate-helper              = { workspace = true, default-features = false, features = ["std", "asynch", "num"] }
+cuprate-consensus-rules     = { workspace = true, features = ["rayon"] }
+cuprate-types               = { workspace = true }
+cuprate-consensus-context   = { workspace = true }
 
 cfg-if = { workspace = true }
 thiserror = { workspace = true }
@@ -28,8 +28,8 @@ hex = { workspace = true }
 rand = { workspace = true }
 
 [dev-dependencies]
-cuprate-test-utils = { path = "../test-utils" }
-cuprate-consensus-rules =  {path = "./rules", features = ["proptest"]}
+cuprate-test-utils = { workspace = true }
+cuprate-consensus-rules =  { workspace = true, features = ["proptest"]}
 
 hex-literal = { workspace = true }
 curve25519-dalek = { workspace = true }
diff --git a/consensus/context/Cargo.toml b/consensus/context/Cargo.toml
index f7642e8a..76790464 100644
--- a/consensus/context/Cargo.toml
+++ b/consensus/context/Cargo.toml
@@ -6,9 +6,9 @@ license = "MIT"
 authors = ["SyntheticBird","Boog900"]
 
 [dependencies]
-cuprate-consensus-rules =  { path = "../rules", features = ["proptest"]}
-cuprate-helper = { path = "../../helper", default-features = false, features = ["std", "cast", "num", "asynch"] }
-cuprate-types = { path = "../../types", default-features = false, features = ["blockchain"] }
+cuprate-consensus-rules =  { workspace = true, features = ["proptest"]}
+cuprate-helper = { workspace = true, default-features = false, features = ["std", "cast", "num", "asynch"] }
+cuprate-types = { workspace = true, default-features = false, features = ["blockchain"] }
 
 futures = { workspace = true, features = ["std", "async-await"] }
 tokio = { workspace = true, features = ["rt-multi-thread", "macros"]}
@@ -24,4 +24,4 @@ thread_local = { workspace = true }
 hex = { workspace = true }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/consensus/fast-sync/Cargo.toml b/consensus/fast-sync/Cargo.toml
index aa9c8d2f..8e732a6f 100644
--- a/consensus/fast-sync/Cargo.toml
+++ b/consensus/fast-sync/Cargo.toml
@@ -9,12 +9,12 @@ name = "cuprate-fast-sync-create-hashes"
 path = "src/create.rs"
 
 [dependencies]
-cuprate-blockchain          = { path = "../../storage/blockchain" }
-cuprate-consensus           = { path = ".." }
-cuprate-consensus-rules     = { path = "../rules" }
-cuprate-consensus-context   = { path = "../context" }
-cuprate-types               = { path = "../../types" }
-cuprate-helper              = { path = "../../helper", features = ["cast"] }
+cuprate-blockchain          = { workspace = true }
+cuprate-consensus           = { workspace = true }
+cuprate-consensus-rules     = { workspace = true }
+cuprate-consensus-context   = { workspace = true }
+cuprate-types               = { workspace = true }
+cuprate-helper              = { workspace = true, features = ["cast"] }
 
 clap         = { workspace = true, features = ["derive", "std"] }
 hex          = { workspace = true }
diff --git a/consensus/rules/Cargo.toml b/consensus/rules/Cargo.toml
index fac22bcc..8999cbcf 100644
--- a/consensus/rules/Cargo.toml
+++ b/consensus/rules/Cargo.toml
@@ -11,10 +11,10 @@ proptest = ["cuprate-types/proptest"]
 rayon = ["dep:rayon"]
 
 [dependencies]
-cuprate-constants = { path = "../../constants", default-features = false, features = ["block"] }
-cuprate-helper = { path = "../../helper", default-features = false, features = ["std", "cast"] }
-cuprate-types = { path = "../../types", default-features = false }
-cuprate-cryptonight = {path = "../../cryptonight"}
+cuprate-constants   = { workspace = true, default-features = false, features = ["block"] }
+cuprate-helper      = { workspace = true, default-features = false, features = ["std", "cast"] }
+cuprate-types       = { workspace = true, default-features = false }
+cuprate-cryptonight = { workspace = true }
 
 monero-serai = { workspace = true, features = ["std"] }
 curve25519-dalek = { workspace = true, features = ["alloc", "zeroize", "precomputed-tables"] }
diff --git a/helper/Cargo.toml b/helper/Cargo.toml
index c70efb0c..ad78a448 100644
--- a/helper/Cargo.toml
+++ b/helper/Cargo.toml
@@ -25,7 +25,7 @@ thread    = ["std", "dep:target_os_lib"]
 tx        = ["dep:monero-serai"]
 
 [dependencies]
-cuprate-constants = { path = "../constants", optional = true, features = ["block"] }
+cuprate-constants = { workspace = true, optional = true, features = ["block"] }
 
 chrono           = { workspace = true, optional = true, features = ["std", "clock"] }
 crossbeam        = { workspace = true, optional = true }
diff --git a/net/epee-encoding/Cargo.toml b/net/epee-encoding/Cargo.toml
index c021e429..4724e2d0 100644
--- a/net/epee-encoding/Cargo.toml
+++ b/net/epee-encoding/Cargo.toml
@@ -15,8 +15,8 @@ default = ["std"]
 std = ["dep:thiserror", "bytes/std", "cuprate-fixed-bytes/std"]
 
 [dependencies]
-cuprate-helper = { path = "../../helper", default-features = false, features = ["cast"] }
-cuprate-fixed-bytes = { path = "../fixed-bytes", default-features = false }
+cuprate-helper      = { workspace = true, default-features = false, features = ["cast"] }
+cuprate-fixed-bytes = { workspace = true, default-features = false }
 
 paste = "1.0.15"
 ref-cast = "1.0.23"
@@ -27,4 +27,4 @@ thiserror = { workspace = true, optional = true}
 hex = { workspace = true, features = ["default"] }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/net/levin/Cargo.toml b/net/levin/Cargo.toml
index 68c32e54..a9f3c1f2 100644
--- a/net/levin/Cargo.toml
+++ b/net/levin/Cargo.toml
@@ -12,7 +12,7 @@ default = []
 tracing = ["dep:tracing", "tokio-util/tracing"]
 
 [dependencies]
-cuprate-helper = { path = "../../helper", default-features = false, features = ["cast"] }
+cuprate-helper = { workspace = true, default-features = false, features = ["cast"] }
 
 cfg-if = { workspace = true }
 thiserror = { workspace = true }
@@ -30,4 +30,4 @@ tokio = { workspace = true, features = ["full"] }
 futures = { workspace = true, features = ["std"] }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/net/wire/Cargo.toml b/net/wire/Cargo.toml
index 0b77cf1b..b500a288 100644
--- a/net/wire/Cargo.toml
+++ b/net/wire/Cargo.toml
@@ -11,11 +11,11 @@ default = []
 tracing = ["cuprate-levin/tracing"]
 
 [dependencies]
-cuprate-levin = { path = "../levin" }
-cuprate-epee-encoding = { path = "../epee-encoding" }
-cuprate-fixed-bytes = { path = "../fixed-bytes" }
-cuprate-types = { path = "../../types", default-features = false, features = ["epee"] }
-cuprate-helper = { path = "../../helper", default-features = false, features = ["map"] }
+cuprate-levin           = { workspace = true }
+cuprate-epee-encoding   = { workspace = true }
+cuprate-fixed-bytes     = { workspace = true }
+cuprate-types           = { workspace = true, default-features = false, features = ["epee"] }
+cuprate-helper          = { workspace = true, default-features = false, features = ["map"] }
 
 bitflags = { workspace = true, features = ["std"] }
 bytes = { workspace = true, features = ["std"] }
diff --git a/p2p/address-book/Cargo.toml b/p2p/address-book/Cargo.toml
index 9afc2552..9cbba717 100644
--- a/p2p/address-book/Cargo.toml
+++ b/p2p/address-book/Cargo.toml
@@ -7,9 +7,9 @@ authors = ["Boog900"]
 
 
 [dependencies]
-cuprate-constants = { path = "../../constants" }
-cuprate-pruning = { path = "../../pruning" }
-cuprate-p2p-core = { path = "../p2p-core" }
+cuprate-constants   = { workspace = true }
+cuprate-pruning     = { workspace = true }
+cuprate-p2p-core    = { workspace = true }
 
 tower = { workspace = true, features = ["util"] }
 tokio = { workspace = true, features = ["time", "fs", "rt"]}
@@ -26,9 +26,9 @@ rand = { workspace = true, features = ["std", "std_rng"] }
 borsh = { workspace = true, features = ["derive", "std"]}
 
 [dev-dependencies]
-cuprate-test-utils = {path = "../../test-utils"}
+cuprate-test-utils = { workspace = true }
 
 tokio = { workspace = true, features = ["rt-multi-thread", "macros"]}
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/p2p/p2p-core/Cargo.toml b/p2p/p2p-core/Cargo.toml
index a30590fa..0a6aaf38 100644
--- a/p2p/p2p-core/Cargo.toml
+++ b/p2p/p2p-core/Cargo.toml
@@ -10,9 +10,9 @@ default = ["borsh"]
 borsh = ["dep:borsh", "cuprate-pruning/borsh"]
 
 [dependencies]
-cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false }
-cuprate-wire = { path = "../../net/wire", features = ["tracing"] }
-cuprate-pruning = { path = "../../pruning" }
+cuprate-helper = { workspace = true, features = ["asynch"], default-features = false }
+cuprate-wire = { workspace = true, features = ["tracing"] }
+cuprate-pruning = { workspace = true }
 
 tokio = { workspace = true, features = ["net", "sync", "macros", "time", "rt", "rt-multi-thread"]}
 tokio-util = { workspace = true, features = ["codec"] }
@@ -29,10 +29,10 @@ hex-literal = { workspace = true }
 borsh = { workspace = true, features = ["derive", "std"], optional = true }
 
 [dev-dependencies]
-cuprate-test-utils = { path = "../../test-utils" }
+cuprate-test-utils = { workspace = true }
 
 hex = { workspace = true, features = ["std"] }
 tokio-test = { workspace = true }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/p2p/p2p/Cargo.toml b/p2p/p2p/Cargo.toml
index 3444b5ef..866fb918 100644
--- a/p2p/p2p/Cargo.toml
+++ b/p2p/p2p/Cargo.toml
@@ -6,15 +6,15 @@ license = "MIT"
 authors = ["Boog900"]
 
 [dependencies]
-cuprate-constants = { path = "../../constants" }
-cuprate-fixed-bytes = { path = "../../net/fixed-bytes" }
-cuprate-wire = { path = "../../net/wire" }
-cuprate-p2p-core = { path = "../p2p-core", features = ["borsh"] }
-cuprate-address-book = { path = "../address-book" }
-cuprate-pruning = { path = "../../pruning" }
-cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false }
-cuprate-async-buffer = { path = "../async-buffer" }
-cuprate-types = { path = "../../types", default-features = false }
+cuprate-constants = { workspace = true }
+cuprate-fixed-bytes = { workspace = true }
+cuprate-wire = { workspace = true }
+cuprate-p2p-core = { workspace = true, features = ["borsh"] }
+cuprate-address-book = { workspace = true }
+cuprate-pruning = { workspace = true }
+cuprate-helper = { workspace = true, features = ["asynch"], default-features = false }
+cuprate-async-buffer = { workspace = true }
+cuprate-types = { workspace = true, default-features = false }
 
 monero-serai = { workspace = true, features = ["std"] }
 
@@ -35,10 +35,10 @@ tracing = { workspace = true, features = ["std", "attributes"] }
 borsh = { workspace = true, features = ["derive", "std"] }
 
 [dev-dependencies]
-cuprate-test-utils = { path = "../../test-utils" }
+cuprate-test-utils = { workspace = true }
 indexmap = { workspace = true }
 proptest = { workspace = true }
 tokio-test = { workspace = true }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/pruning/Cargo.toml b/pruning/Cargo.toml
index e898fd5e..6fcc74e2 100644
--- a/pruning/Cargo.toml
+++ b/pruning/Cargo.toml
@@ -10,11 +10,11 @@ default = []
 borsh = ["dep:borsh"]
 
 [dependencies]
-cuprate-constants = { path = "../constants" }
+cuprate-constants = { workspace = true }
 
 thiserror = { workspace = true }
 
 borsh = { workspace = true, features = ["derive", "std"], optional = true }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml
index 00f7a228..ef62d349 100644
--- a/rpc/interface/Cargo.toml
+++ b/rpc/interface/Cargo.toml
@@ -13,10 +13,10 @@ default  = ["dummy", "serde"]
 dummy    = []
 
 [dependencies]
-cuprate-epee-encoding = { path = "../../net/epee-encoding", default-features = false }
-cuprate-json-rpc      = { path = "../json-rpc", default-features = false }
-cuprate-rpc-types     = { path = "../types", features = ["serde", "epee"], default-features = false }
-cuprate-helper        = { path = "../../helper", features = ["asynch"], default-features = false }
+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 }
 
 anyhow     = { workspace = true }
 axum       = { version = "0.7.5", features = ["json"], default-features = false }
@@ -26,7 +26,7 @@ paste      = { workspace = true }
 futures    = { workspace = true }
 
 [dev-dependencies]
-cuprate-test-utils = { path = "../../test-utils" }
+cuprate-test-utils = { workspace = true }
 
 axum       = { version = "0.7.5", features = ["json", "tokio", "http2"] }
 serde_json = { workspace = true, features = ["std"] }
diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml
index cfe7e47d..e9ca5296 100644
--- a/rpc/types/Cargo.toml
+++ b/rpc/types/Cargo.toml
@@ -14,18 +14,18 @@ serde   = ["dep:serde", "cuprate-fixed-bytes/serde"]
 epee    = ["dep:cuprate-epee-encoding"]
 
 [dependencies]
-cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true }
-cuprate-fixed-bytes   = { path = "../../net/fixed-bytes" }
-cuprate-types         = { path = "../../types", default-features = false, features = ["epee", "serde"] }
+cuprate-epee-encoding = { workspace = true, optional = true }
+cuprate-fixed-bytes   = { workspace = true }
+cuprate-types         = { workspace = true, default-features = false, features = ["epee", "serde"] }
 
 paste        = { workspace = true }
 serde        = { workspace = true, optional = true }
 
 [dev-dependencies]
-cuprate-test-utils = { path = "../../test-utils" }
+cuprate-test-utils = { workspace = true }
 
 serde      = { workspace = true }
 serde_json = { workspace = true }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/storage/blockchain/Cargo.toml b/storage/blockchain/Cargo.toml
index 414b7843..d0a43b3b 100644
--- a/storage/blockchain/Cargo.toml
+++ b/storage/blockchain/Cargo.toml
@@ -18,11 +18,11 @@ redb-memory = ["cuprate-database/redb-memory"]
 service     = ["dep:thread_local", "dep:rayon", "cuprate-helper/thread"]
 
 [dependencies]
-cuprate-database         = { path = "../database" }
-cuprate-database-service = { path = "../service" }
-cuprate-helper           = { path = "../../helper", features = ["fs", "map", "crypto"] }
-cuprate-types            = { path = "../../types", features = ["blockchain"] }
-cuprate-pruning          = { path = "../../pruning" }
+cuprate-database         = { workspace = true }
+cuprate-database-service = { workspace = true }
+cuprate-helper           = { workspace = true, features = ["fs", "map", "crypto"] }
+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"] }
@@ -37,9 +37,9 @@ thread_local = { workspace = true, optional = true }
 rayon        = { workspace = true, optional = true }
 
 [dev-dependencies]
-cuprate-constants  = { path = "../../constants" }
-cuprate-helper     = { path = "../../helper", features = ["thread", "cast"] }
-cuprate-test-utils = { path = "../../test-utils" }
+cuprate-constants  = { workspace = true }
+cuprate-helper     = { workspace = true, features = ["thread", "cast"] }
+cuprate-test-utils = { workspace = true }
 
 tokio             = { workspace = true, features = ["full"] }
 tempfile          = { workspace = true }
diff --git a/storage/service/Cargo.toml b/storage/service/Cargo.toml
index ed46b355..fa6971c5 100644
--- a/storage/service/Cargo.toml
+++ b/storage/service/Cargo.toml
@@ -9,8 +9,8 @@ repository  = "https://github.com/Cuprate/cuprate/tree/main/storage/service"
 keywords    = ["cuprate", "service", "database"]
 
 [dependencies]
-cuprate-database = { path = "../database" }
-cuprate-helper   = { path = "../../helper", features = ["fs", "thread", "map"] }
+cuprate-database = { workspace = true }
+cuprate-helper   = { workspace = true, features = ["fs", "thread", "map"] }
 
 serde     = { workspace = true, optional = true }
 rayon     = { workspace = true }
diff --git a/storage/txpool/Cargo.toml b/storage/txpool/Cargo.toml
index 70211d9e..b9d42181 100644
--- a/storage/txpool/Cargo.toml
+++ b/storage/txpool/Cargo.toml
@@ -19,10 +19,10 @@ service     = ["dep:tower", "dep:rayon", "dep:cuprate-database-service"]
 serde       = ["dep:serde", "cuprate-database/serde", "cuprate-database-service/serde"]
 
 [dependencies]
-cuprate-database         = { path = "../database", features = ["heed"] }
-cuprate-database-service = { path = "../service", optional = true }
-cuprate-types            = { path = "../../types" }
-cuprate-helper           = { path = "../../helper", default-features = false, features = ["constants"] }
+cuprate-database         = { workspace = true, features = ["heed"] }
+cuprate-database-service = { workspace = true, optional = true }
+cuprate-types            = { workspace = true }
+cuprate-helper           = { workspace = true, default-features = false, features = ["constants"] }
 
 monero-serai             = { workspace = true, features = ["std"] }
 bytemuck                 = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
@@ -36,11 +36,11 @@ rayon                    = { workspace = true, optional = true }
 serde                    = { workspace = true, optional = true }
 
 [dev-dependencies]
-cuprate-test-utils = { path = "../../test-utils" }
+cuprate-test-utils = { workspace = true }
 
 tokio              = { workspace = true }
 tempfile           = { workspace = true }
 hex-literal        = { workspace = true }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml
index abf7ee44..4eb56844 100644
--- a/test-utils/Cargo.toml
+++ b/test-utils/Cargo.toml
@@ -6,10 +6,10 @@ license = "MIT"
 authors = ["Boog900", "hinto-janai"]
 
 [dependencies]
-cuprate-types             = { path = "../types" }
-cuprate-helper            = { path = "../helper", features = ["map", "tx"] }
-cuprate-wire              = { path = "../net/wire" }
-cuprate-p2p-core          = { path = "../p2p/p2p-core", features = ["borsh"] }
+cuprate-types             = { workspace = true }
+cuprate-helper            = { workspace = true, features = ["map", "tx"] }
+cuprate-wire              = { workspace = true }
+cuprate-p2p-core          = { workspace = true, features = ["borsh"] }
 
 hex                       = { workspace = true }
 hex-literal               = { workspace = true }
@@ -31,4 +31,4 @@ hex               = { workspace = true }
 pretty_assertions = { workspace = true }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true
diff --git a/types/Cargo.toml b/types/Cargo.toml
index 8ac6b25f..29887bdb 100644
--- a/types/Cargo.toml
+++ b/types/Cargo.toml
@@ -18,9 +18,9 @@ json       = ["hex", "dep:cuprate-helper"]
 hex        = ["dep:hex"]
 
 [dependencies]
-cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true }
-cuprate-helper = { path = "../helper", optional = true, features = ["cast"] }
-cuprate-fixed-bytes   = { path = "../net/fixed-bytes" }
+cuprate-epee-encoding   = { workspace = true, optional = true, features = ["std"] }
+cuprate-helper          = { workspace = true, optional = true, features = ["cast"] }
+cuprate-fixed-bytes     = { workspace = true }
 
 bytes            = { workspace = true }
 curve25519-dalek = { workspace = true }
@@ -39,4 +39,4 @@ pretty_assertions = { workspace = true }
 serde_json        = { workspace = true, features = ["std"] }
 
 [lints]
-workspace = true
\ No newline at end of file
+workspace = true