From bec8cc0aa49517a13ff9132d3bf389cb9b20bcfa Mon Sep 17 00:00:00 2001
From: hinto-janai <hinto.janai@protonmail.com>
Date: Mon, 2 Sep 2024 13:09:52 -0400
Subject: [PATCH 1/5] helper: add and use `cast` module (#264)

* helper: add `cast` module

* fix crates

* spacing
---
 Cargo.lock                                |  3 +
 consensus/rules/Cargo.toml                |  2 +-
 consensus/rules/src/transactions/tests.rs |  6 +-
 consensus/src/block/alt_block.rs          |  4 +-
 consensus/src/context/task.rs             |  7 +-
 helper/Cargo.toml                         |  5 +-
 helper/src/cast.rs                        | 84 +++++++++++++++++++++++
 helper/src/lib.rs                         |  3 +
 helper/src/map.rs                         |  6 +-
 net/epee-encoding/Cargo.toml              |  1 +
 net/epee-encoding/src/lib.rs              | 12 ++--
 net/epee-encoding/src/value.rs            | 39 +++++++----
 net/levin/Cargo.toml                      |  2 +
 net/levin/src/codec.rs                    | 20 ++----
 net/levin/src/lib.rs                      |  4 +-
 net/levin/src/message.rs                  | 12 ++--
 net/levin/tests/fragmented_message.rs     |  4 +-
 net/wire/Cargo.toml                       |  1 +
 net/wire/src/p2p.rs                       |  2 +-
 storage/blockchain/Cargo.toml             |  2 +-
 storage/blockchain/src/ops/block.rs       |  2 +-
 21 files changed, 163 insertions(+), 58 deletions(-)
 create mode 100644 helper/src/cast.rs

diff --git a/Cargo.lock b/Cargo.lock
index 77531897..d004f954 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -646,6 +646,7 @@ version = "0.5.0"
 dependencies = [
  "bytes",
  "cuprate-fixed-bytes",
+ "cuprate-helper",
  "hex",
  "paste",
  "ref-cast",
@@ -713,6 +714,7 @@ version = "0.1.0"
 dependencies = [
  "bitflags 2.5.0",
  "bytes",
+ "cuprate-helper",
  "futures",
  "proptest",
  "rand",
@@ -893,6 +895,7 @@ dependencies = [
  "bytes",
  "cuprate-epee-encoding",
  "cuprate-fixed-bytes",
+ "cuprate-helper",
  "cuprate-levin",
  "cuprate-types",
  "hex",
diff --git a/consensus/rules/Cargo.toml b/consensus/rules/Cargo.toml
index 2cf03e39..8ba321d6 100644
--- a/consensus/rules/Cargo.toml
+++ b/consensus/rules/Cargo.toml
@@ -11,7 +11,7 @@ proptest = ["dep:proptest", "dep:proptest-derive", "cuprate-types/proptest"]
 rayon = ["dep:rayon"]
 
 [dependencies]
-cuprate-helper = { path = "../../helper", default-features = false, features = ["std"] }
+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/transactions/tests.rs b/consensus/rules/src/transactions/tests.rs
index cd0e8c3b..4da8fd53 100644
--- a/consensus/rules/src/transactions/tests.rs
+++ b/consensus/rules/src/transactions/tests.rs
@@ -9,6 +9,8 @@ use proptest::{collection::vec, prelude::*};
 
 use monero_serai::transaction::Output;
 
+use cuprate_helper::cast::u64_to_usize;
+
 use super::*;
 use crate::decomposed_amount::DECOMPOSED_AMOUNTS;
 
@@ -164,7 +166,7 @@ prop_compose! {
         if timebased || lock_height > 500_000_000 {
             Timelock::Time(time_for_time_lock)
         } else {
-            Timelock::Block(usize::try_from(lock_height).unwrap())
+            Timelock::Block(u64_to_usize(lock_height))
         }
     }
 }
@@ -179,7 +181,7 @@ prop_compose! {
         match ty {
             0 => Timelock::None,
             1 => Timelock::Time(time_for_time_lock),
-            _ =>  Timelock::Block(usize::try_from(lock_height).unwrap())
+            _ =>  Timelock::Block(u64_to_usize(lock_height))
         }
     }
 }
diff --git a/consensus/src/block/alt_block.rs b/consensus/src/block/alt_block.rs
index 513697e9..9b94a27d 100644
--- a/consensus/src/block/alt_block.rs
+++ b/consensus/src/block/alt_block.rs
@@ -14,7 +14,7 @@ use cuprate_consensus_rules::{
     miner_tx::MinerTxError,
     ConsensusError,
 };
-use cuprate_helper::asynch::rayon_spawn_async;
+use cuprate_helper::{asynch::rayon_spawn_async, cast::u64_to_usize};
 use cuprate_types::{
     AltBlockInformation, Chain, ChainId, TransactionVerificationData,
     VerifiedTransactionInformation,
@@ -101,7 +101,7 @@ where
 
     // Check the alt block timestamp is in the correct range.
     if let Some(median_timestamp) =
-        difficulty_cache.median_timestamp(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW.try_into().unwrap())
+        difficulty_cache.median_timestamp(u64_to_usize(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW))
     {
         check_timestamp(&prepped_block.block, median_timestamp).map_err(ConsensusError::Block)?
     };
diff --git a/consensus/src/context/task.rs b/consensus/src/context/task.rs
index 8939446a..2376c350 100644
--- a/consensus/src/context/task.rs
+++ b/consensus/src/context/task.rs
@@ -9,6 +9,7 @@ use tower::ServiceExt;
 use tracing::Instrument;
 
 use cuprate_consensus_rules::blocks::ContextToVerifyBlock;
+use cuprate_helper::cast::u64_to_usize;
 use cuprate_types::{
     blockchain::{BlockchainReadRequest, BlockchainResponse},
     Chain,
@@ -168,9 +169,9 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
                                 .weight_cache
                                 .effective_median_block_weight(&current_hf),
                             top_hash: self.top_block_hash,
-                            median_block_timestamp: self.difficulty_cache.median_timestamp(
-                                usize::try_from(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW).unwrap(),
-                            ),
+                            median_block_timestamp: self
+                                .difficulty_cache
+                                .median_timestamp(u64_to_usize(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)),
                             chain_height: self.chain_height,
                             current_hf,
                             next_difficulty: self.difficulty_cache.next_difficulty(&current_hf),
diff --git a/helper/Cargo.toml b/helper/Cargo.toml
index 59e4e71d..9af25c60 100644
--- a/helper/Cargo.toml
+++ b/helper/Cargo.toml
@@ -10,14 +10,15 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/consensus"
 
 [features]
 # All features on by default.
-default   = ["std", "atomic", "asynch", "fs", "num", "map", "time", "thread", "constants"]
+default   = ["std", "atomic", "asynch", "cast", "fs", "num", "map", "time", "thread", "constants"]
 std       = []
 atomic    = ["dep:crossbeam"]
 asynch    = ["dep:futures", "dep:rayon"]
+cast      = []
 constants = []
 fs        = ["dep:dirs"]
 num       = []
-map       = ["dep:monero-serai"]
+map       = ["cast", "dep:monero-serai"]
 time      = ["dep:chrono", "std"]
 thread    = ["std", "dep:target_os_lib"]
 
diff --git a/helper/src/cast.rs b/helper/src/cast.rs
new file mode 100644
index 00000000..81d0836a
--- /dev/null
+++ b/helper/src/cast.rs
@@ -0,0 +1,84 @@
+//! Casting.
+//!
+//! This modules provides utilities for casting between types.
+//!
+//! `#[no_std]` compatible.
+
+#[rustfmt::skip]
+//============================ SAFETY: DO NOT REMOVE ===========================//
+//                                                                              //
+//                                                                              //
+//                      Only allow building 64-bit targets.                     //
+//             This allows us to assume 64-bit invariants in this file.         //
+                       #[cfg(not(target_pointer_width = "64"))]
+            compile_error!("Cuprate is only compatible with 64-bit CPUs");
+//                                                                              //
+//                                                                              //
+//============================ SAFETY: DO NOT REMOVE ===========================//
+
+//---------------------------------------------------------------------------------------------------- Free functions
+/// Cast [`u64`] to [`usize`].
+#[inline(always)]
+pub const fn u64_to_usize(u: u64) -> usize {
+    u as usize
+}
+
+/// Cast [`u32`] to [`usize`].
+#[inline(always)]
+pub const fn u32_to_usize(u: u32) -> usize {
+    u as usize
+}
+
+/// Cast [`usize`] to [`u64`].
+#[inline(always)]
+pub const fn usize_to_u64(u: usize) -> u64 {
+    u as u64
+}
+
+/// Cast [`i64`] to [`isize`].
+#[inline(always)]
+pub const fn i64_to_isize(i: i64) -> isize {
+    i as isize
+}
+
+/// Cast [`i32`] to [`isize`].
+#[inline(always)]
+pub const fn i32_to_isize(i: i32) -> isize {
+    i as isize
+}
+
+/// Cast [`isize`] to [`i64`].
+#[inline(always)]
+pub const fn isize_to_i64(i: isize) -> i64 {
+    i as i64
+}
+
+//---------------------------------------------------------------------------------------------------- Tests
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn max_unsigned() {
+        assert_eq!(u32_to_usize(u32::MAX), u32::MAX as usize);
+        assert_eq!(usize_to_u64(u32_to_usize(u32::MAX)), u32::MAX as u64);
+
+        assert_eq!(u64_to_usize(u64::MAX), usize::MAX);
+        assert_eq!(usize_to_u64(u64_to_usize(u64::MAX)), u64::MAX);
+
+        assert_eq!(usize_to_u64(usize::MAX), u64::MAX);
+        assert_eq!(u64_to_usize(usize_to_u64(usize::MAX)), usize::MAX);
+    }
+
+    #[test]
+    fn max_signed() {
+        assert_eq!(i32_to_isize(i32::MAX), i32::MAX as isize);
+        assert_eq!(isize_to_i64(i32_to_isize(i32::MAX)), i32::MAX as i64);
+
+        assert_eq!(i64_to_isize(i64::MAX), isize::MAX);
+        assert_eq!(isize_to_i64(i64_to_isize(i64::MAX)), i64::MAX);
+
+        assert_eq!(isize_to_i64(isize::MAX), i64::MAX);
+        assert_eq!(i64_to_isize(isize_to_i64(isize::MAX)), isize::MAX);
+    }
+}
diff --git a/helper/src/lib.rs b/helper/src/lib.rs
index 90f420d6..4dd31055 100644
--- a/helper/src/lib.rs
+++ b/helper/src/lib.rs
@@ -40,6 +40,9 @@ pub mod asynch; // async collides
 #[cfg(feature = "atomic")]
 pub mod atomic;
 
+#[cfg(feature = "cast")]
+pub mod cast;
+
 #[cfg(feature = "constants")]
 pub mod constants;
 
diff --git a/helper/src/map.rs b/helper/src/map.rs
index 96d9f615..82d54940 100644
--- a/helper/src/map.rs
+++ b/helper/src/map.rs
@@ -7,6 +7,8 @@
 //---------------------------------------------------------------------------------------------------- Use
 use monero_serai::transaction::Timelock;
 
+use crate::cast::{u64_to_usize, usize_to_u64};
+
 //---------------------------------------------------------------------------------------------------- `(u64, u64) <-> u128`
 /// Split a [`u128`] value into 2 64-bit values.
 ///
@@ -77,7 +79,7 @@ pub fn u64_to_timelock(u: u64) -> Timelock {
     if u == 0 {
         Timelock::None
     } else if u < 500_000_000 {
-        Timelock::Block(usize::try_from(u).unwrap())
+        Timelock::Block(u64_to_usize(u))
     } else {
         Timelock::Time(u)
     }
@@ -97,7 +99,7 @@ pub fn u64_to_timelock(u: u64) -> Timelock {
 pub fn timelock_to_u64(timelock: Timelock) -> u64 {
     match timelock {
         Timelock::None => 0,
-        Timelock::Block(u) => u64::try_from(u).unwrap(),
+        Timelock::Block(u) => usize_to_u64(u),
         Timelock::Time(u) => u,
     }
 }
diff --git a/net/epee-encoding/Cargo.toml b/net/epee-encoding/Cargo.toml
index 7feac004..85ee2c93 100644
--- a/net/epee-encoding/Cargo.toml
+++ b/net/epee-encoding/Cargo.toml
@@ -15,6 +15,7 @@ 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 }
 
 paste = "1.0.14"
diff --git a/net/epee-encoding/src/lib.rs b/net/epee-encoding/src/lib.rs
index 5b64315e..fa3449b4 100644
--- a/net/epee-encoding/src/lib.rs
+++ b/net/epee-encoding/src/lib.rs
@@ -65,6 +65,8 @@ use core::{ops::Deref, str::from_utf8 as str_from_utf8};
 
 use bytes::{Buf, BufMut, Bytes, BytesMut};
 
+use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
+
 pub mod container_as_blob;
 pub mod error;
 mod io;
@@ -242,7 +244,7 @@ pub fn write_bytes<T: AsRef<[u8]>, B: BufMut>(t: T, w: &mut B) -> Result<()> {
     let bytes = t.as_ref();
     let len = bytes.len();
 
-    write_varint(len.try_into()?, w)?;
+    write_varint(usize_to_u64(len), w)?;
 
     if w.remaining_mut() < len {
         return Err(Error::IO("Not enough capacity to write bytes"));
@@ -286,7 +288,7 @@ where
     I: Iterator<Item = T> + ExactSizeIterator,
     B: BufMut,
 {
-    write_varint(iterator.len().try_into()?, w)?;
+    write_varint(usize_to_u64(iterator.len()), w)?;
     for item in iterator.into_iter() {
         item.write(w)?;
     }
@@ -334,7 +336,7 @@ fn skip_epee_value<B: Buf>(r: &mut B, skipped_objects: &mut u8) -> Result<()> {
 
     if let Some(size) = marker.inner_marker.size() {
         let bytes_to_skip = size
-            .checked_mul(len.try_into()?)
+            .checked_mul(u64_to_usize(len))
             .ok_or(Error::Value("List is too big".to_string()))?;
         return advance(bytes_to_skip, r);
     };
@@ -352,8 +354,8 @@ fn skip_epee_value<B: Buf>(r: &mut B, skipped_objects: &mut u8) -> Result<()> {
             | InnerMarker::U8
             | InnerMarker::Bool => unreachable!("These types are constant size."),
             InnerMarker::String => {
-                let len = read_varint(r)?;
-                advance(len.try_into()?, r)?;
+                let len = u64_to_usize(read_varint(r)?);
+                advance(len, r)?;
             }
             InnerMarker::Object => {
                 *skipped_objects += 1;
diff --git a/net/epee-encoding/src/value.rs b/net/epee-encoding/src/value.rs
index 094f0ef1..000d89c7 100644
--- a/net/epee-encoding/src/value.rs
+++ b/net/epee-encoding/src/value.rs
@@ -7,6 +7,7 @@ use core::fmt::Debug;
 use bytes::{Buf, BufMut, Bytes, BytesMut};
 
 use cuprate_fixed_bytes::{ByteArray, ByteArrayVec};
+use cuprate_helper::cast::u64_to_usize;
 
 use crate::{
     io::{checked_read_primitive, checked_write_primitive},
@@ -66,11 +67,11 @@ impl<T: EpeeObject> EpeeValue for Vec<T> {
                 "Marker is not sequence when a sequence was expected",
             ));
         }
-        let len = read_varint(r)?;
+        let len = u64_to_usize(read_varint(r)?);
 
         let individual_marker = Marker::new(marker.inner_marker);
 
-        let mut res = Vec::with_capacity(len.try_into()?);
+        let mut res = Vec::with_capacity(len);
         for _ in 0..len {
             res.push(T::read(r, &individual_marker)?);
         }
@@ -167,11 +168,13 @@ impl EpeeValue for Vec<u8> {
             return Err(Error::Format("Byte array exceeded max length"));
         }
 
-        if r.remaining() < len.try_into()? {
+        let len = u64_to_usize(len);
+
+        if r.remaining() < len {
             return Err(Error::IO("Not enough bytes to fill object"));
         }
 
-        let mut res = vec![0; len.try_into()?];
+        let mut res = vec![0; len];
         r.copy_to_slice(&mut res);
 
         Ok(res)
@@ -203,11 +206,13 @@ impl EpeeValue for Bytes {
             return Err(Error::Format("Byte array exceeded max length"));
         }
 
-        if r.remaining() < len.try_into()? {
+        let len = u64_to_usize(len);
+
+        if r.remaining() < len {
             return Err(Error::IO("Not enough bytes to fill object"));
         }
 
-        Ok(r.copy_to_bytes(len.try_into()?))
+        Ok(r.copy_to_bytes(len))
     }
 
     fn epee_default_value() -> Option<Self> {
@@ -236,11 +241,13 @@ impl EpeeValue for BytesMut {
             return Err(Error::Format("Byte array exceeded max length"));
         }
 
-        if r.remaining() < len.try_into()? {
+        let len = u64_to_usize(len);
+
+        if r.remaining() < len {
             return Err(Error::IO("Not enough bytes to fill object"));
         }
 
-        let mut bytes = BytesMut::zeroed(len.try_into()?);
+        let mut bytes = BytesMut::zeroed(len);
         r.copy_to_slice(&mut bytes);
 
         Ok(bytes)
@@ -272,11 +279,13 @@ impl<const N: usize> EpeeValue for ByteArrayVec<N> {
             return Err(Error::Format("Byte array exceeded max length"));
         }
 
-        if r.remaining() < usize::try_from(len)? {
+        let len = u64_to_usize(len);
+
+        if r.remaining() < len {
             return Err(Error::IO("Not enough bytes to fill object"));
         }
 
-        ByteArrayVec::try_from(r.copy_to_bytes(usize::try_from(len)?))
+        ByteArrayVec::try_from(r.copy_to_bytes(len))
             .map_err(|_| Error::Format("Field has invalid length"))
     }
 
@@ -302,7 +311,7 @@ impl<const N: usize> EpeeValue for ByteArray<N> {
             return Err(Error::Format("Marker does not match expected Marker"));
         }
 
-        let len: usize = read_varint(r)?.try_into()?;
+        let len = u64_to_usize(read_varint(r)?);
         if len != N {
             return Err(Error::Format("Byte array has incorrect length"));
         }
@@ -370,11 +379,11 @@ impl<const N: usize> EpeeValue for Vec<[u8; N]> {
             ));
         }
 
-        let len = read_varint(r)?;
+        let len = u64_to_usize(read_varint(r)?);
 
         let individual_marker = Marker::new(marker.inner_marker);
 
-        let mut res = Vec::with_capacity(len.try_into()?);
+        let mut res = Vec::with_capacity(len);
         for _ in 0..len {
             res.push(<[u8; N]>::read(r, &individual_marker)?);
         }
@@ -406,11 +415,11 @@ macro_rules! epee_seq {
                     ));
                 }
 
-                let len = read_varint(r)?;
+                let len = u64_to_usize(read_varint(r)?);
 
                 let individual_marker = Marker::new(marker.inner_marker.clone());
 
-                let mut res = Vec::with_capacity(len.try_into()?);
+                let mut res = Vec::with_capacity(len);
                 for _ in 0..len {
                     res.push(<$val>::read(r, &individual_marker)?);
                 }
diff --git a/net/levin/Cargo.toml b/net/levin/Cargo.toml
index 13deabea..1c585b9c 100644
--- a/net/levin/Cargo.toml
+++ b/net/levin/Cargo.toml
@@ -12,6 +12,8 @@ default = []
 tracing = ["dep:tracing", "tokio-util/tracing"]
 
 [dependencies]
+cuprate-helper = { path = "../../helper", default-features = false, features = ["cast"] }
+
 thiserror = { workspace = true }
 bytes = { workspace = true, features = ["std"] }
 bitflags = { workspace = true }
diff --git a/net/levin/src/codec.rs b/net/levin/src/codec.rs
index 3718d8c3..1177733f 100644
--- a/net/levin/src/codec.rs
+++ b/net/levin/src/codec.rs
@@ -20,6 +20,8 @@ use std::{fmt::Debug, marker::PhantomData};
 use bytes::{Buf, BufMut, BytesMut};
 use tokio_util::codec::{Decoder, Encoder};
 
+use cuprate_helper::cast::u64_to_usize;
+
 use crate::{
     header::{Flags, HEADER_SIZE},
     message::{make_dummy_message, LevinMessage},
@@ -114,10 +116,7 @@ impl<C: LevinCommand + Debug> Decoder for LevinBucketCodec<C> {
                         std::mem::replace(&mut self.state, LevinBucketState::WaitingForBody(head));
                 }
                 LevinBucketState::WaitingForBody(head) => {
-                    let body_len = head
-                        .size
-                        .try_into()
-                        .map_err(|_| BucketError::BucketExceededMaxSize)?;
+                    let body_len = u64_to_usize(head.size);
                     if src.len() < body_len {
                         src.reserve(body_len - src.len());
                         return Ok(None);
@@ -255,13 +254,11 @@ impl<T: LevinBody> Decoder for LevinMessageCodec<T> {
                         continue;
                     };
 
-                    let max_size = if self.bucket_codec.handshake_message_seen {
+                    let max_size = u64_to_usize(if self.bucket_codec.handshake_message_seen {
                         self.bucket_codec.protocol.max_packet_size
                     } else {
                         self.bucket_codec.protocol.max_packet_size_before_handshake
-                    }
-                    .try_into()
-                    .expect("Levin max message size is too large, does not fit into a usize.");
+                    });
 
                     if bytes.len().saturating_add(bucket.body.len()) > max_size {
                         return Err(BucketError::InvalidFragmentedMessage(
@@ -300,12 +297,7 @@ impl<T: LevinBody> Decoder for LevinMessageCodec<T> {
                         }
 
                         // Check the fragmented message contains enough bytes to build the message.
-                        if bytes.len().saturating_sub(HEADER_SIZE)
-                            < header
-                                .size
-                                .try_into()
-                                .map_err(|_| BucketError::BucketExceededMaxSize)?
-                        {
+                        if bytes.len().saturating_sub(HEADER_SIZE) < u64_to_usize(header.size) {
                             return Err(BucketError::InvalidFragmentedMessage(
                                 "Fragmented message does not have enough bytes to fill bucket body",
                             ));
diff --git a/net/levin/src/lib.rs b/net/levin/src/lib.rs
index 0a247f72..ab03bfb4 100644
--- a/net/levin/src/lib.rs
+++ b/net/levin/src/lib.rs
@@ -38,6 +38,8 @@ use std::fmt::Debug;
 use bytes::{Buf, Bytes};
 use thiserror::Error;
 
+use cuprate_helper::cast::usize_to_u64;
+
 pub mod codec;
 pub mod header;
 pub mod message;
@@ -212,7 +214,7 @@ impl<C: LevinCommand> BucketBuilder<C> {
         Bucket {
             header: BucketHead {
                 signature: self.signature.unwrap(),
-                size: body.len().try_into().unwrap(),
+                size: usize_to_u64(body.len()),
                 have_to_return_data: ty.have_to_return_data(),
                 command: self.command.unwrap(),
                 return_code: self.return_code.unwrap(),
diff --git a/net/levin/src/message.rs b/net/levin/src/message.rs
index af8227d7..19aa1b50 100644
--- a/net/levin/src/message.rs
+++ b/net/levin/src/message.rs
@@ -5,6 +5,8 @@
 //! for more control over what is actually sent over the wire at certain times.
 use bytes::{Bytes, BytesMut};
 
+use cuprate_helper::cast::usize_to_u64;
+
 use crate::{
     header::{Flags, HEADER_SIZE},
     Bucket, BucketBuilder, BucketError, BucketHead, LevinBody, LevinCommand, Protocol,
@@ -106,9 +108,7 @@ pub fn make_fragmented_messages<T: LevinBody>(
             new_body.resize(fragment_size - HEADER_SIZE, 0);
 
             bucket.body = new_body.freeze();
-            bucket.header.size = (fragment_size - HEADER_SIZE)
-                .try_into()
-                .expect("Bucket size does not fit into u64");
+            bucket.header.size = usize_to_u64(fragment_size - HEADER_SIZE);
         }
 
         return Ok(vec![bucket]);
@@ -118,9 +118,7 @@ pub fn make_fragmented_messages<T: LevinBody>(
     // The first fragment will set the START flag, the last will set the END flag.
     let fragment_head = BucketHead {
         signature: protocol.signature,
-        size: (fragment_size - HEADER_SIZE)
-            .try_into()
-            .expect("Bucket size does not fit into u64"),
+        size: usize_to_u64(fragment_size - HEADER_SIZE),
         have_to_return_data: false,
         // Just use a default command.
         command: T::Command::from(0),
@@ -191,7 +189,7 @@ pub(crate) fn make_dummy_message<T: LevinCommand>(protocol: &Protocol, size: usi
     // A header to put on the dummy message.
     let header = BucketHead {
         signature: protocol.signature,
-        size: size.try_into().expect("Bucket size does not fit into u64"),
+        size: usize_to_u64(size),
         have_to_return_data: false,
         // Just use a default command.
         command: T::from(0),
diff --git a/net/levin/tests/fragmented_message.rs b/net/levin/tests/fragmented_message.rs
index 7799a719..512fd461 100644
--- a/net/levin/tests/fragmented_message.rs
+++ b/net/levin/tests/fragmented_message.rs
@@ -8,6 +8,8 @@ use tokio::{
 };
 use tokio_util::codec::{FramedRead, FramedWrite};
 
+use cuprate_helper::cast::u64_to_usize;
+
 use cuprate_levin::{
     message::make_fragmented_messages, BucketBuilder, BucketError, LevinBody, LevinCommand,
     LevinMessageCodec, MessageType, Protocol,
@@ -54,7 +56,7 @@ impl LevinBody for TestBody {
         _: MessageType,
         _: Self::Command,
     ) -> Result<Self, BucketError> {
-        let size = body.get_u64_le().try_into().unwrap();
+        let size = u64_to_usize(body.get_u64_le());
         // bucket
         Ok(TestBody::Bytes(size, body.copy_to_bytes(size)))
     }
diff --git a/net/wire/Cargo.toml b/net/wire/Cargo.toml
index 101daa39..cbeb5511 100644
--- a/net/wire/Cargo.toml
+++ b/net/wire/Cargo.toml
@@ -15,6 +15,7 @@ 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 = ["cast"] }
 
 bitflags = { workspace = true, features = ["std"] }
 bytes = { workspace = true, features = ["std"] }
diff --git a/net/wire/src/p2p.rs b/net/wire/src/p2p.rs
index 97431099..3829d172 100644
--- a/net/wire/src/p2p.rs
+++ b/net/wire/src/p2p.rs
@@ -99,7 +99,7 @@ impl LevinCommandTrait for LevinCommand {
             LevinCommand::FluffyMissingTxsRequest => 1024 * 1024,  // 1 MB
             LevinCommand::GetTxPoolCompliment => 1024 * 1024 * 4,  // 4 MB
 
-            LevinCommand::Unknown(_) => usize::MAX.try_into().unwrap_or(u64::MAX),
+            LevinCommand::Unknown(_) => u64::MAX,
         }
     }
 
diff --git a/storage/blockchain/Cargo.toml b/storage/blockchain/Cargo.toml
index 7e79305a..58da21e6 100644
--- a/storage/blockchain/Cargo.toml
+++ b/storage/blockchain/Cargo.toml
@@ -39,7 +39,7 @@ thread_local = { workspace = true, optional = true }
 rayon        = { workspace = true, optional = true }
 
 [dev-dependencies]
-cuprate-helper     = { path = "../../helper", features = ["thread"] }
+cuprate-helper     = { path = "../../helper", features = ["thread", "cast"] }
 cuprate-test-utils = { path = "../../test-utils" }
 
 tokio             = { workspace = true, features = ["full"] }
diff --git a/storage/blockchain/src/ops/block.rs b/storage/blockchain/src/ops/block.rs
index 4f77d736..d1b83a45 100644
--- a/storage/blockchain/src/ops/block.rs
+++ b/storage/blockchain/src/ops/block.rs
@@ -442,7 +442,7 @@ mod test {
 
         let mut block = BLOCK_V9_TX3.clone();
 
-        block.height = usize::try_from(u32::MAX).unwrap() + 1;
+        block.height = cuprate_helper::cast::u32_to_usize(u32::MAX) + 1;
         add_block(&block, &mut tables).unwrap();
     }
 

From b837d350a448ba400fdd0296464a58672db2cb7b Mon Sep 17 00:00:00 2001
From: hinto-janai <hinto.janai@protonmail.com>
Date: Mon, 2 Sep 2024 13:10:45 -0400
Subject: [PATCH 2/5] workspace: add naming convention lints (#261)

* add lint to {Cargo,clippy}.toml

* `RandomXVM` -> `RandomXVm`

* epee: `TT` -> `T2`
---
 Cargo.toml                               |  2 ++
 clippy.toml                              |  1 +
 consensus/src/block/alt_block.rs         |  4 +--
 consensus/src/block/batch_prepare.rs     |  4 +--
 consensus/src/context.rs                 |  8 ++---
 consensus/src/context/alt_chains.rs      |  4 +--
 consensus/src/context/rx_vms.rs          | 38 ++++++++++++------------
 consensus/src/context/task.rs            |  4 +--
 consensus/src/tests/context/rx_vms.rs    |  6 ++--
 net/epee-encoding/tests/duplicate_key.rs |  6 ++--
 10 files changed, 40 insertions(+), 37 deletions(-)
 create mode 100644 clippy.toml

diff --git a/Cargo.toml b/Cargo.toml
index 06b49a0a..1a0c6675 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -262,6 +262,7 @@ empty_structs_with_brackets = "deny"
 empty_enum_variants_with_brackets = "deny"
 empty_drop = "deny"
 clone_on_ref_ptr = "deny"
+upper_case_acronyms  = "deny"
 
 # Hot
 # inline_always = "deny"
@@ -309,6 +310,7 @@ let_underscore_drop = "deny"
 unreachable_pub = "deny"
 unused_qualifications = "deny"
 variant_size_differences = "deny"
+non_camel_case_types = "deny"
 
 # Hot
 # unused_results = "deny"
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 00000000..cc94ec53
--- /dev/null
+++ b/clippy.toml
@@ -0,0 +1 @@
+upper-case-acronyms-aggressive = true
diff --git a/consensus/src/block/alt_block.rs b/consensus/src/block/alt_block.rs
index 9b94a27d..b20b4f26 100644
--- a/consensus/src/block/alt_block.rs
+++ b/consensus/src/block/alt_block.rs
@@ -24,7 +24,7 @@ use crate::{
     block::{free::pull_ordered_transactions, PreparedBlock},
     context::{
         difficulty::DifficultyCache,
-        rx_vms::RandomXVM,
+        rx_vms::RandomXVm,
         weight::{self, BlockWeightsCache},
         AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
     },
@@ -195,7 +195,7 @@ async fn alt_rx_vm<C>(
     parent_chain: Chain,
     alt_chain_context: &mut AltChainContextCache,
     context_svc: C,
-) -> Result<Option<Arc<RandomXVM>>, ExtendedConsensusError>
+) -> Result<Option<Arc<RandomXVm>>, ExtendedConsensusError>
 where
     C: Service<
             BlockChainContextRequest,
diff --git a/consensus/src/block/batch_prepare.rs b/consensus/src/block/batch_prepare.rs
index 9974d6d1..d32cd765 100644
--- a/consensus/src/block/batch_prepare.rs
+++ b/consensus/src/block/batch_prepare.rs
@@ -15,7 +15,7 @@ use cuprate_helper::asynch::rayon_spawn_async;
 
 use crate::{
     block::{free::pull_ordered_transactions, PreparedBlock, PreparedBlockExPow},
-    context::rx_vms::RandomXVM,
+    context::rx_vms::RandomXVm,
     transactions::new_tx_verification_data,
     BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError,
     VerifyBlockResponse,
@@ -148,7 +148,7 @@ where
         tracing::debug!("New randomX seed in batch, initialising VM");
 
         let new_vm = rayon_spawn_async(move || {
-            Arc::new(RandomXVM::new(&new_vm_seed).expect("RandomX VM gave an error on set up!"))
+            Arc::new(RandomXVm::new(&new_vm_seed).expect("RandomX VM gave an error on set up!"))
         })
         .await;
 
diff --git a/consensus/src/context.rs b/consensus/src/context.rs
index 26be75c3..9e713046 100644
--- a/consensus/src/context.rs
+++ b/consensus/src/context.rs
@@ -33,7 +33,7 @@ mod tokens;
 
 use cuprate_types::Chain;
 use difficulty::DifficultyCache;
-use rx_vms::RandomXVM;
+use rx_vms::RandomXVm;
 use weight::BlockWeightsCache;
 
 pub(crate) use alt_chains::{sealed::AltChainRequestToken, AltChainContextCache};
@@ -236,7 +236,7 @@ pub enum BlockChainContextRequest {
     /// seed.
     ///
     /// This should include the seed used to init this VM and the VM.
-    NewRXVM(([u8; 32], Arc<RandomXVM>)),
+    NewRXVM(([u8; 32], Arc<RandomXVm>)),
     /// A request to add a new block to the cache.
     Update(NewBlockData),
     /// Pop blocks from the cache to the specified height.
@@ -313,7 +313,7 @@ pub enum BlockChainContextResponse {
     /// Blockchain context response.
     Context(BlockChainContext),
     /// A map of seed height to RandomX VMs.
-    RxVms(HashMap<usize, Arc<RandomXVM>>),
+    RxVms(HashMap<usize, Arc<RandomXVm>>),
     /// A list of difficulties.
     BatchDifficulties(Vec<u128>),
     /// An alt chain context cache.
@@ -321,7 +321,7 @@ pub enum BlockChainContextResponse {
     /// A difficulty cache for an alt chain.
     AltChainDifficultyCache(DifficultyCache),
     /// A randomX VM for an alt chain.
-    AltChainRxVM(Arc<RandomXVM>),
+    AltChainRxVM(Arc<RandomXVm>),
     /// A weight cache for an alt chain
     AltChainWeightCache(BlockWeightsCache),
     /// A generic Ok response.
diff --git a/consensus/src/context/alt_chains.rs b/consensus/src/context/alt_chains.rs
index 5586226b..937e847e 100644
--- a/consensus/src/context/alt_chains.rs
+++ b/consensus/src/context/alt_chains.rs
@@ -11,7 +11,7 @@ use cuprate_types::{
 use crate::{
     ExtendedConsensusError,
     __private::Database,
-    context::{difficulty::DifficultyCache, rx_vms::RandomXVM, weight::BlockWeightsCache},
+    context::{difficulty::DifficultyCache, rx_vms::RandomXVm, weight::BlockWeightsCache},
 };
 
 pub(crate) mod sealed {
@@ -32,7 +32,7 @@ pub struct AltChainContextCache {
     pub difficulty_cache: Option<DifficultyCache>,
 
     /// A cached RX VM.
-    pub cached_rx_vm: Option<(usize, Arc<RandomXVM>)>,
+    pub cached_rx_vm: Option<(usize, Arc<RandomXVm>)>,
 
     /// The chain height of the alt chain.
     pub chain_height: usize,
diff --git a/consensus/src/context/rx_vms.rs b/consensus/src/context/rx_vms.rs
index 01aa9738..b1ab102b 100644
--- a/consensus/src/context/rx_vms.rs
+++ b/consensus/src/context/rx_vms.rs
@@ -9,7 +9,7 @@ use std::{
 };
 
 use futures::{stream::FuturesOrdered, StreamExt};
-use randomx_rs::{RandomXCache, RandomXError, RandomXFlag, RandomXVM as VMInner};
+use randomx_rs::{RandomXCache, RandomXError, RandomXFlag, RandomXVM as VmInner};
 use rayon::prelude::*;
 use thread_local::ThreadLocal;
 use tower::ServiceExt;
@@ -33,16 +33,16 @@ const RX_SEEDS_CACHED: usize = 2;
 
 /// A multithreaded randomX VM.
 #[derive(Debug)]
-pub struct RandomXVM {
+pub struct RandomXVm {
     /// These RandomX VMs all share the same cache.
-    vms: ThreadLocal<VMInner>,
+    vms: ThreadLocal<VmInner>,
     /// The RandomX cache.
     cache: RandomXCache,
     /// The flags used to start the RandomX VMs.
     flags: RandomXFlag,
 }
 
-impl RandomXVM {
+impl RandomXVm {
     /// Create a new multithreaded randomX VM with the provided seed.
     pub fn new(seed: &[u8; 32]) -> Result<Self, RandomXError> {
         // TODO: allow passing in flags.
@@ -50,7 +50,7 @@ impl RandomXVM {
 
         let cache = RandomXCache::new(flags, seed.as_slice())?;
 
-        Ok(RandomXVM {
+        Ok(RandomXVm {
             vms: ThreadLocal::new(),
             cache,
             flags,
@@ -58,12 +58,12 @@ impl RandomXVM {
     }
 }
 
-impl RandomX for RandomXVM {
+impl RandomX for RandomXVm {
     type Error = RandomXError;
 
     fn calculate_hash(&self, buf: &[u8]) -> Result<[u8; 32], Self::Error> {
         self.vms
-            .get_or_try(|| VMInner::new(self.flags, Some(self.cache.clone()), None))?
+            .get_or_try(|| VmInner::new(self.flags, Some(self.cache.clone()), None))?
             .calculate_hash(buf)
             .map(|out| out.try_into().unwrap())
     }
@@ -72,17 +72,17 @@ impl RandomX for RandomXVM {
 /// The randomX VMs cache, keeps the VM needed to calculate the current block's PoW hash (if a VM is needed) and a
 /// couple more around this VM.
 #[derive(Clone, Debug)]
-pub struct RandomXVMCache {
+pub struct RandomXVmCache {
     /// The top [`RX_SEEDS_CACHED`] RX seeds.  
     pub(crate) 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(crate) 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(crate) cached_vm: Option<([u8; 32], Arc<RandomXVm>)>,
 }
 
-impl RandomXVMCache {
+impl RandomXVmCache {
     #[instrument(name = "init_rx_vm_cache", level = "info", skip(database))]
     pub async fn init_from_chain_height<D: Database + Clone>(
         chain_height: usize,
@@ -106,7 +106,7 @@ impl RandomXVMCache {
                     .map(|(height, seed)| {
                         (
                             *height,
-                            Arc::new(RandomXVM::new(seed).expect("Failed to create RandomX VM!")),
+                            Arc::new(RandomXVm::new(seed).expect("Failed to create RandomX VM!")),
                         )
                     })
                     .collect()
@@ -117,7 +117,7 @@ impl RandomXVMCache {
             HashMap::new()
         };
 
-        Ok(RandomXVMCache {
+        Ok(RandomXVmCache {
             seeds,
             vms,
             cached_vm: None,
@@ -125,7 +125,7 @@ impl RandomXVMCache {
     }
 
     /// Add a randomX VM to the cache, with the seed it was created with.
-    pub 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);
     }
 
@@ -136,7 +136,7 @@ impl RandomXVMCache {
         height: usize,
         chain: Chain,
         database: D,
-    ) -> Result<Arc<RandomXVM>, ExtendedConsensusError> {
+    ) -> Result<Arc<RandomXVm>, ExtendedConsensusError> {
         let seed_height = randomx_seed_height(height);
 
         let BlockchainResponse::BlockHash(seed_hash) = database
@@ -156,13 +156,13 @@ impl RandomXVMCache {
             }
         }
 
-        let alt_vm = rayon_spawn_async(move || Arc::new(RandomXVM::new(&seed_hash).unwrap())).await;
+        let alt_vm = rayon_spawn_async(move || Arc::new(RandomXVm::new(&seed_hash).unwrap())).await;
 
         Ok(alt_vm)
     }
 
     /// Get the main-chain RandomX VMs.
-    pub 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) => (),
@@ -184,7 +184,7 @@ impl RandomXVMCache {
                         }
                     };
 
-                    rayon_spawn_async(move || Arc::new(RandomXVM::new(&next_seed_hash).unwrap()))
+                    rayon_spawn_async(move || Arc::new(RandomXVm::new(&next_seed_hash).unwrap()))
                         .await
                 };
 
@@ -200,7 +200,7 @@ impl RandomXVMCache {
                     seeds_clone
                         .par_iter()
                         .map(|(height, seed)| {
-                            let vm = RandomXVM::new(seed).expect("Failed to create RandomX VM!");
+                            let vm = RandomXVm::new(seed).expect("Failed to create RandomX VM!");
                             let vm = Arc::new(vm);
                             (*height, vm)
                         })
diff --git a/consensus/src/context/task.rs b/consensus/src/context/task.rs
index 2376c350..bc54285a 100644
--- a/consensus/src/context/task.rs
+++ b/consensus/src/context/task.rs
@@ -46,7 +46,7 @@ pub struct ContextTask<D: Database> {
     /// The weight cache.
     weight_cache: weight::BlockWeightsCache,
     /// The RX VM cache.
-    rx_vm_cache: rx_vms::RandomXVMCache,
+    rx_vm_cache: rx_vms::RandomXVmCache,
     /// The hard-fork state cache.
     hardfork_state: hardforks::HardForkState,
 
@@ -128,7 +128,7 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
 
         let db = database.clone();
         let rx_seed_handle = tokio::spawn(async move {
-            rx_vms::RandomXVMCache::init_from_chain_height(chain_height, &current_hf, db).await
+            rx_vms::RandomXVmCache::init_from_chain_height(chain_height, &current_hf, db).await
         });
 
         let context_svc = ContextTask {
diff --git a/consensus/src/tests/context/rx_vms.rs b/consensus/src/tests/context/rx_vms.rs
index f18a9b59..5c198cf6 100644
--- a/consensus/src/tests/context/rx_vms.rs
+++ b/consensus/src/tests/context/rx_vms.rs
@@ -9,7 +9,7 @@ use cuprate_consensus_rules::{
 };
 
 use crate::{
-    context::rx_vms::{get_last_rx_seed_heights, RandomXVMCache},
+    context::rx_vms::{get_last_rx_seed_heights, RandomXVmCache},
     tests::mock_db::*,
 };
 
@@ -42,7 +42,7 @@ fn rx_heights_consistent() {
 async fn rx_vm_created_on_hf_12() {
     let db = DummyDatabaseBuilder::default().finish(Some(10));
 
-    let mut cache = RandomXVMCache::init_from_chain_height(10, &HardFork::V11, db)
+    let mut cache = RandomXVmCache::init_from_chain_height(10, &HardFork::V11, db)
         .await
         .unwrap();
 
@@ -67,7 +67,7 @@ proptest! {
         let rt = Builder::new_multi_thread().enable_all().build().unwrap();
 
         rt.block_on(async move {
-            let cache = RandomXVMCache::init_from_chain_height(10, &hf, db).await.unwrap();
+            let cache = RandomXVmCache::init_from_chain_height(10, &hf, db).await.unwrap();
             assert!(cache.seeds.len() == cache.vms.len() || hf < HardFork::V12);
         });
     }
diff --git a/net/epee-encoding/tests/duplicate_key.rs b/net/epee-encoding/tests/duplicate_key.rs
index c1b3148f..0ed87aff 100644
--- a/net/epee-encoding/tests/duplicate_key.rs
+++ b/net/epee-encoding/tests/duplicate_key.rs
@@ -9,12 +9,12 @@ epee_object!(
     a: u8,
 );
 
-struct TT {
+struct T2 {
     a: u8,
 }
 
 epee_object!(
-    TT,
+    T2,
     a: u8 = 0,
 );
 
@@ -35,5 +35,5 @@ fn duplicate_key_with_default() {
         b'a', 0x0B, 0x00,
     ];
 
-    assert!(from_bytes::<TT, _>(&mut &data[..]).is_err());
+    assert!(from_bytes::<T2, _>(&mut &data[..]).is_err());
 }

From eead49beb09b4a0a8c4a2b0f38683311f53eb891 Mon Sep 17 00:00:00 2001
From: hinto-janai <hinto.janai@protonmail.com>
Date: Mon, 2 Sep 2024 13:12:54 -0400
Subject: [PATCH 3/5] lints: opt in manual lint crates (#263)

* cargo.toml: transfer existing lints

* rpc/interface: lints

* rpc/json-rpc: lints

* rpc/types: lints

* storage/blockchain: lints

* rpc/types: fix lints

* cargo.toml: fix lint group priority

* storage/blockchain: fix lints

* fix misc lints

* storage/database: fixes

* storage/txpool: opt in lints + fixes

* types: opt in + fixes

* helper: opt in + fixes

* types: remove borsh

* rpc/interface: fix test

* test fixes

* database: fix lints

* fix lint

* tabs -> spaces

* blockchain: `config/` -> `config.rs`
---
 Cargo.lock                                    |  44 +++++---
 Cargo.toml                                    |   6 +-
 helper/Cargo.toml                             |   3 +
 helper/src/asynch.rs                          |   8 +-
 helper/src/atomic.rs                          |   2 +
 helper/src/fs.rs                              |  89 +++++----------
 helper/src/lib.rs                             |  32 ------
 helper/src/map.rs                             |   3 +-
 helper/src/network.rs                         |   8 +-
 helper/src/num.rs                             |   3 +-
 helper/src/thread.rs                          |  10 +-
 helper/src/time.rs                            |   2 +
 rpc/interface/Cargo.toml                      |   8 +-
 rpc/interface/src/lib.rs                      | 103 ++----------------
 rpc/json-rpc/Cargo.toml                       |   5 +-
 rpc/json-rpc/src/lib.rs                       |  90 ---------------
 rpc/json-rpc/src/response.rs                  |   4 +-
 rpc/json-rpc/src/tests.rs                     |   1 +
 rpc/types/Cargo.toml                          |   9 +-
 rpc/types/src/lib.rs                          |  97 ++---------------
 rpc/types/src/misc/mod.rs                     |   1 +
 storage/blockchain/Cargo.toml                 |   3 +
 storage/blockchain/src/{config => }/config.rs |  42 ++++++-
 storage/blockchain/src/config/mod.rs          |  44 --------
 storage/blockchain/src/lib.rs                 | 100 +----------------
 storage/blockchain/src/service/free.rs        |   4 +-
 storage/blockchain/src/service/tests.rs       |   3 +-
 storage/database/Cargo.toml                   |   5 +-
 storage/database/src/backend/heed/env.rs      |   2 +-
 storage/database/src/backend/heed/storable.rs |   4 +-
 storage/database/src/backend/redb/database.rs |  10 +-
 storage/database/src/config/mod.rs            |   1 +
 storage/database/src/env.rs                   |   2 +-
 storage/database/src/key.rs                   |   4 +-
 storage/database/src/lib.rs                   | 102 +++--------------
 storage/txpool/Cargo.toml                     |   3 +
 storage/txpool/src/config.rs                  |   2 +-
 storage/txpool/src/lib.rs                     |  14 +++
 storage/txpool/src/ops/key_images.rs          |   4 +-
 storage/txpool/src/service/free.rs            |   4 +-
 storage/txpool/src/service/read.rs            |   8 +-
 storage/txpool/src/service/write.rs           |   2 +-
 storage/txpool/src/types.rs                   |  10 +-
 types/Cargo.toml                              |   6 +-
 types/src/lib.rs                              |  75 +------------
 45 files changed, 240 insertions(+), 742 deletions(-)
 rename storage/blockchain/src/{config => }/config.rs (82%)
 delete mode 100644 storage/blockchain/src/config/mod.rs

diff --git a/Cargo.lock b/Cargo.lock
index d004f954..950044c8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,12 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
+[[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
 [[package]]
 name = "ahash"
 version = "0.8.11"
@@ -160,7 +166,7 @@ dependencies = [
  "cc",
  "cfg-if",
  "libc",
- "miniz_oxide",
+ "miniz_oxide 0.7.3",
  "object",
  "rustc-demangle",
 ]
@@ -798,6 +804,7 @@ dependencies = [
  "cuprate-helper",
  "cuprate-json-rpc",
  "cuprate-rpc-types",
+ "cuprate-test-utils",
  "futures",
  "paste",
  "serde",
@@ -813,12 +820,9 @@ version = "0.0.0"
 dependencies = [
  "cuprate-epee-encoding",
  "cuprate-fixed-bytes",
- "cuprate-json-rpc",
  "cuprate-test-utils",
  "cuprate-types",
- "monero-serai",
  "paste",
- "pretty_assertions",
  "serde",
  "serde_json",
 ]
@@ -875,7 +879,6 @@ dependencies = [
 name = "cuprate-types"
 version = "0.0.0"
 dependencies = [
- "borsh",
  "bytes",
  "cuprate-epee-encoding",
  "cuprate-fixed-bytes",
@@ -1082,12 +1085,12 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
 
 [[package]]
 name = "flate2"
-version = "1.0.30"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
+checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
 dependencies = [
  "crc32fast",
- "miniz_oxide",
+ "miniz_oxide 0.8.0",
 ]
 
 [[package]]
@@ -1241,9 +1244,9 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab"
+checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
 dependencies = [
  "atomic-waker",
  "bytes",
@@ -1737,6 +1740,15 @@ dependencies = [
  "adler",
 ]
 
+[[package]]
+name = "miniz_oxide"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
+dependencies = [
+ "adler2",
+]
+
 [[package]]
 name = "mio"
 version = "0.8.11"
@@ -2399,9 +2411,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-pki-types"
-version = "1.7.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
+checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
 
 [[package]]
 name = "rustls-webpki"
@@ -2960,9 +2972,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
 
 [[package]]
 name = "ureq"
-version = "2.10.0"
+version = "2.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea"
+checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a"
 dependencies = [
  "base64",
  "flate2",
@@ -3085,9 +3097,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
 
 [[package]]
 name = "webpki-roots"
-version = "0.26.3"
+version = "0.26.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
+checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a"
 dependencies = [
  "rustls-pki-types",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 1a0c6675..0a98eab6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -279,13 +279,15 @@ upper_case_acronyms  = "deny"
 # allow_attributes_without_reason = "deny"
 # missing_assert_message = "deny"
 # missing_docs_in_private_items = "deny"
-# undocumented_unsafe_blocks = "deny"
+undocumented_unsafe_blocks = "deny"
 # multiple_unsafe_ops_per_block = "deny"
 # single_char_lifetime_names = "deny"
 # wildcard_enum_match_arm = "deny"
 
 [workspace.lints.rust]
 # Cold
+future_incompatible = { level = "deny", priority = -1 }
+nonstandard_style = { level = "deny", priority = -1 }
 absolute_paths_not_starting_with_crate = "deny"
 explicit_outlives_requirements = "deny"
 keyword_idents_2018 = "deny"
@@ -306,7 +308,7 @@ ambiguous_glob_imports = "deny"
 unused_unsafe = "deny"
 
 # Warm
-let_underscore_drop = "deny"
+let_underscore = { level = "deny", priority = -1 }
 unreachable_pub = "deny"
 unused_qualifications = "deny"
 variant_size_differences = "deny"
diff --git a/helper/Cargo.toml b/helper/Cargo.toml
index 9af25c60..c74e40fd 100644
--- a/helper/Cargo.toml
+++ b/helper/Cargo.toml
@@ -40,3 +40,6 @@ target_os_lib = { package = "libc", version = "0.2.151", optional = true }
 
 [dev-dependencies]
 tokio = { workspace = true, features = ["full"] }
+
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/helper/src/asynch.rs b/helper/src/asynch.rs
index ea89dd79..9868191b 100644
--- a/helper/src/asynch.rs
+++ b/helper/src/asynch.rs
@@ -19,7 +19,7 @@ pub struct InfallibleOneshotReceiver<T>(oneshot::Receiver<T>);
 
 impl<T> From<oneshot::Receiver<T>> for InfallibleOneshotReceiver<T> {
     fn from(value: oneshot::Receiver<T>) -> Self {
-        InfallibleOneshotReceiver(value)
+        Self(value)
     }
 }
 
@@ -43,7 +43,7 @@ where
 {
     let (tx, rx) = oneshot::channel();
     rayon::spawn(move || {
-        let _ = tx.send(f());
+        drop(tx.send(f()));
     });
     rx.await.expect("The sender must not be dropped")
 }
@@ -62,7 +62,7 @@ mod test {
     #[tokio::test]
     // Assert that basic channel operations work.
     async fn infallible_oneshot_receiver() {
-        let (tx, rx) = futures::channel::oneshot::channel::<String>();
+        let (tx, rx) = oneshot::channel::<String>();
         let msg = "hello world!".to_string();
 
         tx.send(msg.clone()).unwrap();
@@ -84,7 +84,7 @@ mod test {
         let barrier = Arc::new(Barrier::new(2));
         let task = |barrier: &Barrier| barrier.wait();
 
-        let b_2 = barrier.clone();
+        let b_2 = Arc::clone(&barrier);
 
         let (tx, rx) = std::sync::mpsc::channel();
 
diff --git a/helper/src/atomic.rs b/helper/src/atomic.rs
index f253737a..47958964 100644
--- a/helper/src/atomic.rs
+++ b/helper/src/atomic.rs
@@ -49,6 +49,8 @@ pub type AtomicF64 = AtomicCell<f64>;
 //---------------------------------------------------------------------------------------------------- TESTS
 #[cfg(test)]
 mod tests {
+    #![allow(clippy::float_cmp)]
+
     use super::*;
 
     #[test]
diff --git a/helper/src/fs.rs b/helper/src/fs.rs
index 7290361f..5d62a644 100644
--- a/helper/src/fs.rs
+++ b/helper/src/fs.rs
@@ -190,72 +190,41 @@ mod test {
     // - It must `ends_with()` the expected end PATH for the OS
     #[test]
     fn path_sanity_check() {
-        assert!(CUPRATE_CACHE_DIR.is_absolute());
-        assert!(CUPRATE_CONFIG_DIR.is_absolute());
-        assert!(CUPRATE_DATA_DIR.is_absolute());
-        assert!(CUPRATE_BLOCKCHAIN_DIR.is_absolute());
+        // Array of (PATH, expected_path_as_string).
+        //
+        // The different OS's will set the expected path below.
+        let mut array = [
+            (&*CUPRATE_CACHE_DIR, ""),
+            (&*CUPRATE_CONFIG_DIR, ""),
+            (&*CUPRATE_DATA_DIR, ""),
+            (&*CUPRATE_BLOCKCHAIN_DIR, ""),
+            (&*CUPRATE_TXPOOL_DIR, ""),
+        ];
 
         if cfg!(target_os = "windows") {
-            let dir = &*CUPRATE_CACHE_DIR;
-            println!("cuprate_cache_dir: {dir:?}");
-            assert!(dir.ends_with(r"AppData\Local\Cuprate"));
-
-            let dir = &*CUPRATE_CONFIG_DIR;
-            println!("cuprate_config_dir: {dir:?}");
-            assert!(dir.ends_with(r"AppData\Roaming\Cuprate"));
-
-            let dir = &*CUPRATE_DATA_DIR;
-            println!("cuprate_data_dir: {dir:?}");
-            assert!(dir.ends_with(r"AppData\Roaming\Cuprate"));
-
-            let dir = &*CUPRATE_BLOCKCHAIN_DIR;
-            println!("cuprate_blockchain_dir: {dir:?}");
-            assert!(dir.ends_with(r"AppData\Roaming\Cuprate\blockchain"));
-
-            let dir = &*CUPRATE_TXPOOL_DIR;
-            println!("cuprate_txpool_dir: {dir:?}");
-            assert!(dir.ends_with(r"AppData\Roaming\Cuprate\txpool"));
+            array[0].1 = r"AppData\Local\Cuprate";
+            array[1].1 = r"AppData\Roaming\Cuprate";
+            array[2].1 = r"AppData\Roaming\Cuprate";
+            array[3].1 = r"AppData\Roaming\Cuprate\blockchain";
+            array[4].1 = r"AppData\Roaming\Cuprate\txpool";
         } else if cfg!(target_os = "macos") {
-            let dir = &*CUPRATE_CACHE_DIR;
-            println!("cuprate_cache_dir: {dir:?}");
-            assert!(dir.ends_with("Library/Caches/Cuprate"));
-
-            let dir = &*CUPRATE_CONFIG_DIR;
-            println!("cuprate_config_dir: {dir:?}");
-            assert!(dir.ends_with("Library/Application Support/Cuprate"));
-
-            let dir = &*CUPRATE_DATA_DIR;
-            println!("cuprate_data_dir: {dir:?}");
-            assert!(dir.ends_with("Library/Application Support/Cuprate"));
-
-            let dir = &*CUPRATE_BLOCKCHAIN_DIR;
-            println!("cuprate_blockchain_dir: {dir:?}");
-            assert!(dir.ends_with("Library/Application Support/Cuprate/blockchain"));
-
-            let dir = &*CUPRATE_TXPOOL_DIR;
-            println!("cuprate_txpool_dir: {dir:?}");
-            assert!(dir.ends_with("Library/Application Support/Cuprate/txpool"));
+            array[0].1 = "Library/Caches/Cuprate";
+            array[1].1 = "Library/Application Support/Cuprate";
+            array[2].1 = "Library/Application Support/Cuprate";
+            array[3].1 = "Library/Application Support/Cuprate/blockchain";
+            array[4].1 = "Library/Application Support/Cuprate/txpool";
         } else {
             // Assumes Linux.
-            let dir = &*CUPRATE_CACHE_DIR;
-            println!("cuprate_cache_dir: {dir:?}");
-            assert!(dir.ends_with(".cache/cuprate"));
+            array[0].1 = ".cache/cuprate";
+            array[1].1 = ".config/cuprate";
+            array[2].1 = ".local/share/cuprate";
+            array[3].1 = ".local/share/cuprate/blockchain";
+            array[4].1 = ".local/share/cuprate/txpool";
+        };
 
-            let dir = &*CUPRATE_CONFIG_DIR;
-            println!("cuprate_config_dir: {dir:?}");
-            assert!(dir.ends_with(".config/cuprate"));
-
-            let dir = &*CUPRATE_DATA_DIR;
-            println!("cuprate_data_dir: {dir:?}");
-            assert!(dir.ends_with(".local/share/cuprate"));
-
-            let dir = &*CUPRATE_BLOCKCHAIN_DIR;
-            println!("cuprate_blockchain_dir: {dir:?}");
-            assert!(dir.ends_with(".local/share/cuprate/blockchain"));
-
-            let dir = &*CUPRATE_TXPOOL_DIR;
-            println!("cuprate_txpool_dir: {dir:?}");
-            assert!(dir.ends_with(".local/share/cuprate/txpool"));
+        for (path, expected) in array {
+            assert!(path.is_absolute());
+            assert!(path.ends_with(expected));
         }
     }
 }
diff --git a/helper/src/lib.rs b/helper/src/lib.rs
index 4dd31055..de0d9555 100644
--- a/helper/src/lib.rs
+++ b/helper/src/lib.rs
@@ -1,36 +1,4 @@
 #![doc = include_str!("../README.md")]
-//---------------------------------------------------------------------------------------------------- Lints
-#![allow(clippy::len_zero, clippy::type_complexity, clippy::module_inception)]
-#![deny(nonstandard_style, deprecated, missing_docs, unused_mut)]
-#![forbid(
-    unused_unsafe,
-    future_incompatible,
-    break_with_label_and_loop,
-    coherence_leak_check,
-    duplicate_macro_attributes,
-    exported_private_dependencies,
-    for_loops_over_fallibles,
-    large_assignments,
-    overlapping_range_endpoints,
-    // private_in_public,
-    semicolon_in_expressions_from_macros,
-    redundant_semicolons,
-    unconditional_recursion,
-    unreachable_patterns,
-    unused_allocation,
-    unused_braces,
-    unused_comparisons,
-    unused_doc_comments,
-    unused_parens,
-    unused_labels,
-    while_true,
-    keyword_idents,
-    non_ascii_idents,
-    noop_method_call,
-	unreachable_pub,
-    single_use_lifetimes,
-	// variant_size_differences,
-)]
 #![cfg_attr(not(feature = "std"), no_std)]
 
 //---------------------------------------------------------------------------------------------------- Public API
diff --git a/helper/src/map.rs b/helper/src/map.rs
index 82d54940..ea6dfc49 100644
--- a/helper/src/map.rs
+++ b/helper/src/map.rs
@@ -29,6 +29,7 @@ use crate::cast::{u64_to_usize, usize_to_u64};
 /// ```
 #[inline]
 pub const fn split_u128_into_low_high_bits(value: u128) -> (u64, u64) {
+    #[allow(clippy::cast_possible_truncation)]
     (value as u64, (value >> 64) as u64)
 }
 
@@ -60,7 +61,7 @@ pub const fn combine_low_high_bits_to_u128(low_bits: u64, high_bits: u64) -> u12
 /// Map a [`u64`] to a [`Timelock`].
 ///
 /// Height/time is not differentiated via type, but rather:
-/// "height is any value less than 500_000_000 and timestamp is any value above"
+/// "height is any value less than `500_000_000` and timestamp is any value above"
 /// so the `u64/usize` is stored without any tag.
 ///
 /// See [`timelock_to_u64`] for the inverse function.
diff --git a/helper/src/network.rs b/helper/src/network.rs
index 684e71a4..f3224b33 100644
--- a/helper/src/network.rs
+++ b/helper/src/network.rs
@@ -30,11 +30,11 @@ pub enum Network {
 
 impl Network {
     /// Returns the network ID for the current network.
-    pub fn network_id(&self) -> [u8; 16] {
+    pub const fn network_id(&self) -> [u8; 16] {
         match self {
-            Network::Mainnet => MAINNET_NETWORK_ID,
-            Network::Testnet => TESTNET_NETWORK_ID,
-            Network::Stagenet => STAGENET_NETWORK_ID,
+            Self::Mainnet => MAINNET_NETWORK_ID,
+            Self::Testnet => TESTNET_NETWORK_ID,
+            Self::Stagenet => STAGENET_NETWORK_ID,
         }
     }
 }
diff --git a/helper/src/num.rs b/helper/src/num.rs
index f90357e9..674ed354 100644
--- a/helper/src/num.rs
+++ b/helper/src/num.rs
@@ -89,8 +89,9 @@ where
 /// assert_eq!(median(vec), 5);
 /// ```
 ///
-/// # Safety
+/// # Invariant
 /// If not sorted the output will be invalid.
+#[allow(clippy::debug_assert_with_mut_call)]
 pub fn median<T>(array: impl AsRef<[T]>) -> T
 where
     T: Add<Output = T>
diff --git a/helper/src/thread.rs b/helper/src/thread.rs
index 96958ff6..04a26069 100644
--- a/helper/src/thread.rs
+++ b/helper/src/thread.rs
@@ -28,10 +28,10 @@ macro_rules! impl_thread_percent {
 		$(
 			$(#[$doc])*
 			pub fn $fn_name() -> NonZeroUsize {
-		        // SAFETY:
 		        // unwrap here is okay because:
 		        // - THREADS().get() is always non-zero
 		        // - max() guards against 0
+                #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_precision_loss)]
 		        NonZeroUsize::new(max(1, (threads().get() as f64 * $percent).floor() as usize)).unwrap()
 		    }
 		)*
@@ -58,10 +58,10 @@ impl_thread_percent! {
 /// Originally from <https://docs.rs/lpt>.
 ///
 /// # Windows
-/// Uses SetThreadPriority() with THREAD_PRIORITY_IDLE (-15).
+/// Uses `SetThreadPriority()` with `THREAD_PRIORITY_IDLE` (-15).
 ///
 /// # Unix
-/// Uses libc::nice() with the max nice level.
+/// Uses `libc::nice()` with the max nice level.
 ///
 /// On macOS and *BSD: +20
 /// On Linux: +19
@@ -74,7 +74,7 @@ pub fn low_priority_thread() {
         // SAFETY: calling C.
         // We are _lowering_ our priority, not increasing, so this function should never fail.
         unsafe {
-            let _ = SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
+            drop(SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE));
         }
     }
 
@@ -87,7 +87,7 @@ pub fn low_priority_thread() {
         // SAFETY: calling C.
         // We are _lowering_ our priority, not increasing, so this function should never fail.
         unsafe {
-            let _ = libc::nice(NICE_MAX);
+            libc::nice(NICE_MAX);
         }
     }
 }
diff --git a/helper/src/time.rs b/helper/src/time.rs
index 28aff7f5..ce39c2dc 100644
--- a/helper/src/time.rs
+++ b/helper/src/time.rs
@@ -129,6 +129,7 @@ pub const fn secs_to_clock(seconds: u32) -> (u8, u8, u8) {
     debug_assert!(m < 60);
     debug_assert!(s < 60);
 
+    #[allow(clippy::cast_possible_truncation)] // checked above
     (h as u8, m, s)
 }
 
@@ -153,6 +154,7 @@ pub fn time() -> u32 {
 ///
 /// This is guaranteed to return a value between `0..=86399`
 pub fn time_utc() -> u32 {
+    #[allow(clippy::cast_sign_loss)] // checked in function calls
     unix_clock(chrono::offset::Local::now().timestamp() as u64)
 }
 
diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml
index a83c0f07..5f173177 100644
--- a/rpc/interface/Cargo.toml
+++ b/rpc/interface/Cargo.toml
@@ -20,13 +20,17 @@ cuprate-helper        = { path = "../../helper", features = ["asynch"], default-
 
 axum       = { version = "0.7.5", features = ["json"], default-features = false }
 serde      = { workspace = true, optional = true }
-serde_json = { workspace = true, features = ["std"] }
 tower      = { workspace = true }
 paste      = { workspace = true }
 futures    = { workspace = true }
 
 [dev-dependencies]
+cuprate-test-utils = { path = "../../test-utils" }
+
 axum       = { version = "0.7.5", features = ["json", "tokio", "http2"] }
 serde_json = { workspace = true, features = ["std"] }
 tokio      = { workspace = true, features = ["full"] }
-ureq       = { version = "2.10.0", features = ["json"] }
\ No newline at end of file
+ureq       = { version = "2.10.0", features = ["json"] }
+
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs
index 43bd9e1c..d4376979 100644
--- a/rpc/interface/src/lib.rs
+++ b/rpc/interface/src/lib.rs
@@ -1,99 +1,6 @@
 #![doc = include_str!("../README.md")]
 #![cfg_attr(docsrs, feature(doc_cfg))]
-//---------------------------------------------------------------------------------------------------- Lints
-// Forbid lints.
-// Our code, and code generated (e.g macros) cannot overrule these.
-#![forbid(
-    // `unsafe` is allowed but it _must_ be
-    // commented with `SAFETY: reason`.
-    clippy::undocumented_unsafe_blocks,
 
-    // Never.
-    unused_unsafe,
-    redundant_semicolons,
-    unused_allocation,
-    coherence_leak_check,
-    while_true,
-
-    // Maybe can be put into `#[deny]`.
-    unconditional_recursion,
-    for_loops_over_fallibles,
-    unused_braces,
-    unused_labels,
-    keyword_idents,
-    non_ascii_idents,
-    variant_size_differences,
-    single_use_lifetimes,
-
-    // Probably can be put into `#[deny]`.
-    future_incompatible,
-    let_underscore,
-    break_with_label_and_loop,
-    duplicate_macro_attributes,
-    exported_private_dependencies,
-    large_assignments,
-    overlapping_range_endpoints,
-    semicolon_in_expressions_from_macros,
-    noop_method_call,
-)]
-// Deny lints.
-// Some of these are `#[allow]`'ed on a per-case basis.
-#![deny(
-    clippy::all,
-    clippy::correctness,
-    clippy::suspicious,
-    clippy::style,
-    clippy::complexity,
-    clippy::perf,
-    clippy::pedantic,
-    clippy::nursery,
-    clippy::cargo,
-    unused_doc_comments,
-    unused_mut,
-    missing_docs,
-    deprecated,
-    unused_comparisons,
-    nonstandard_style,
-    unreachable_pub
-)]
-#![allow(
-    // FIXME: this lint affects crates outside of
-    // `database/` for some reason, allow for now.
-    clippy::cargo_common_metadata,
-
-    // FIXME: adding `#[must_use]` onto everything
-    // might just be more annoying than useful...
-    // although it is sometimes nice.
-    clippy::must_use_candidate,
-
-    // FIXME: good lint but too many false positives
-    // with our `Env` + `RwLock` setup.
-    clippy::significant_drop_tightening,
-
-    // FIXME: good lint but is less clear in most cases.
-    clippy::items_after_statements,
-
-    // TODO
-    rustdoc::bare_urls,
-
-    clippy::multiple_crate_versions,
-    clippy::module_name_repetitions,
-    clippy::module_inception,
-    clippy::redundant_pub_crate,
-    clippy::option_if_let_else,
-)]
-// Allow some lints in tests.
-#![cfg_attr(
-    test,
-    allow(
-        clippy::cognitive_complexity,
-        clippy::needless_pass_by_value,
-        clippy::cast_possible_truncation,
-        clippy::too_many_lines
-    )
-)]
-
-//---------------------------------------------------------------------------------------------------- Mod
 mod route;
 mod router_builder;
 mod rpc_error;
@@ -110,3 +17,13 @@ pub use rpc_handler::RpcHandler;
 pub use rpc_handler_dummy::RpcHandlerDummy;
 pub use rpc_request::RpcRequest;
 pub use rpc_response::RpcResponse;
+
+// false-positive: used in `README.md`'s doc-test.
+#[cfg(test)]
+mod test {
+    extern crate axum;
+    extern crate cuprate_test_utils;
+    extern crate serde_json;
+    extern crate tokio;
+    extern crate ureq;
+}
diff --git a/rpc/json-rpc/Cargo.toml b/rpc/json-rpc/Cargo.toml
index 777f3264..5d2544e4 100644
--- a/rpc/json-rpc/Cargo.toml
+++ b/rpc/json-rpc/Cargo.toml
@@ -17,4 +17,7 @@ serde_json = { workspace = true, features = ["std"] }
 thiserror  = { workspace = true }
 
 [dev-dependencies]
-pretty_assertions = { workspace = true }
\ No newline at end of file
+pretty_assertions = { workspace = true }
+
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/rpc/json-rpc/src/lib.rs b/rpc/json-rpc/src/lib.rs
index ce7467a1..dfc4b181 100644
--- a/rpc/json-rpc/src/lib.rs
+++ b/rpc/json-rpc/src/lib.rs
@@ -1,94 +1,5 @@
 #![doc = include_str!("../README.md")]
-//---------------------------------------------------------------------------------------------------- Lints
-// Forbid lints.
-// Our code, and code generated (e.g macros) cannot overrule these.
-#![forbid(
-    // `unsafe` is allowed but it _must_ be
-    // commented with `SAFETY: reason`.
-    clippy::undocumented_unsafe_blocks,
 
-    // Never.
-    unused_unsafe,
-    redundant_semicolons,
-    unused_allocation,
-    coherence_leak_check,
-    while_true,
-
-    // Maybe can be put into `#[deny]`.
-    unconditional_recursion,
-    for_loops_over_fallibles,
-    unused_braces,
-    unused_labels,
-    keyword_idents,
-    non_ascii_idents,
-    variant_size_differences,
-    single_use_lifetimes,
-
-    // Probably can be put into `#[deny]`.
-    future_incompatible,
-    let_underscore,
-    break_with_label_and_loop,
-    duplicate_macro_attributes,
-    exported_private_dependencies,
-    large_assignments,
-    overlapping_range_endpoints,
-    semicolon_in_expressions_from_macros,
-    noop_method_call,
-    unreachable_pub,
-)]
-// Deny lints.
-// Some of these are `#[allow]`'ed on a per-case basis.
-#![deny(
-    clippy::all,
-    clippy::correctness,
-    clippy::suspicious,
-    clippy::style,
-    clippy::complexity,
-    clippy::perf,
-    clippy::pedantic,
-    clippy::nursery,
-    clippy::cargo,
-    clippy::missing_docs_in_private_items,
-    unused_mut,
-    missing_docs,
-    deprecated,
-    unused_comparisons,
-    nonstandard_style
-)]
-#![allow(
-    // FIXME: this lint affects crates outside of
-    // `database/` for some reason, allow for now.
-    clippy::cargo_common_metadata,
-
-    // FIXME: adding `#[must_use]` onto everything
-    // might just be more annoying than useful...
-    // although it is sometimes nice.
-    clippy::must_use_candidate,
-
-    // FIXME: good lint but too many false positives
-    // with our `Env` + `RwLock` setup.
-    clippy::significant_drop_tightening,
-
-    // FIXME: good lint but is less clear in most cases.
-    clippy::items_after_statements,
-
-    clippy::module_name_repetitions,
-    clippy::module_inception,
-    clippy::redundant_pub_crate,
-    clippy::option_if_let_else,
-)]
-// Allow some lints in tests.
-#![cfg_attr(
-    test,
-    allow(
-        clippy::cognitive_complexity,
-        clippy::needless_pass_by_value,
-        clippy::cast_possible_truncation,
-        clippy::too_many_lines
-    )
-)]
-
-//---------------------------------------------------------------------------------------------------- Mod/Use
 pub mod error;
 
 mod id;
@@ -103,6 +14,5 @@ pub use request::Request;
 mod response;
 pub use response::Response;
 
-//---------------------------------------------------------------------------------------------------- TESTS
 #[cfg(test)]
 mod tests;
diff --git a/rpc/json-rpc/src/response.rs b/rpc/json-rpc/src/response.rs
index efd768b5..2b846069 100644
--- a/rpc/json-rpc/src/response.rs
+++ b/rpc/json-rpc/src/response.rs
@@ -304,14 +304,14 @@ where
                             if payload.is_none() {
                                 payload = Some(Ok(map.next_value::<T>()?));
                             } else {
-                                return Err(serde::de::Error::duplicate_field("result/error"));
+                                return Err(Error::duplicate_field("result/error"));
                             }
                         }
                         Key::Error => {
                             if payload.is_none() {
                                 payload = Some(Err(map.next_value::<ErrorObject>()?));
                             } else {
-                                return Err(serde::de::Error::duplicate_field("result/error"));
+                                return Err(Error::duplicate_field("result/error"));
                             }
                         }
                         Key::Unknown => {
diff --git a/rpc/json-rpc/src/tests.rs b/rpc/json-rpc/src/tests.rs
index ff8f0496..3ee60881 100644
--- a/rpc/json-rpc/src/tests.rs
+++ b/rpc/json-rpc/src/tests.rs
@@ -52,6 +52,7 @@ where
 }
 
 /// Tests an input JSON string matches an expected type `T`.
+#[allow(clippy::needless_pass_by_value)] // serde signature
 fn assert_de<T>(json: &'static str, expected: T)
 where
     T: DeserializeOwned + std::fmt::Debug + Clone + PartialEq,
diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml
index 9c996818..08b13b18 100644
--- a/rpc/types/Cargo.toml
+++ b/rpc/types/Cargo.toml
@@ -18,13 +18,14 @@ cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true }
 cuprate-fixed-bytes   = { path = "../../net/fixed-bytes" }
 cuprate-types         = { path = "../../types" }
 
-monero-serai = { workspace = true }
 paste        = { workspace = true }
 serde        = { workspace = true, optional = true }
 
 [dev-dependencies]
 cuprate-test-utils = { path = "../../test-utils" }
-cuprate-json-rpc   = { path = "../json-rpc" }
 
-serde_json        = { workspace = true }
-pretty_assertions = { workspace = true }
\ No newline at end of file
+serde      = { workspace = true }
+serde_json = { workspace = true }
+
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs
index c5f890f6..51ea3cc2 100644
--- a/rpc/types/src/lib.rs
+++ b/rpc/types/src/lib.rs
@@ -1,96 +1,6 @@
 #![doc = include_str!("../README.md")]
 #![cfg_attr(docsrs, feature(doc_cfg))]
-//---------------------------------------------------------------------------------------------------- Lints
-// Forbid lints.
-// Our code, and code generated (e.g macros) cannot overrule these.
-#![forbid(
-    // `unsafe` is allowed but it _must_ be
-    // commented with `SAFETY: reason`.
-    clippy::undocumented_unsafe_blocks,
 
-    // Never.
-    unused_unsafe,
-    redundant_semicolons,
-    unused_allocation,
-    coherence_leak_check,
-    while_true,
-
-    // Maybe can be put into `#[deny]`.
-    unconditional_recursion,
-    for_loops_over_fallibles,
-    unused_braces,
-    unused_labels,
-    keyword_idents,
-    non_ascii_idents,
-    variant_size_differences,
-    single_use_lifetimes,
-
-    // Probably can be put into `#[deny]`.
-    future_incompatible,
-    let_underscore,
-    break_with_label_and_loop,
-    duplicate_macro_attributes,
-    exported_private_dependencies,
-    large_assignments,
-    overlapping_range_endpoints,
-    semicolon_in_expressions_from_macros,
-    noop_method_call,
-)]
-// Deny lints.
-// Some of these are `#[allow]`'ed on a per-case basis.
-#![deny(
-    clippy::all,
-    clippy::correctness,
-    clippy::suspicious,
-    clippy::style,
-    clippy::complexity,
-    clippy::perf,
-    clippy::pedantic,
-    clippy::nursery,
-    clippy::cargo,
-    unused_doc_comments,
-    unused_mut,
-    missing_docs,
-    deprecated,
-    unused_comparisons,
-    nonstandard_style,
-    unreachable_pub
-)]
-#![allow(
-    // FIXME: this lint affects crates outside of
-    // `database/` for some reason, allow for now.
-    clippy::cargo_common_metadata,
-
-    // FIXME: adding `#[must_use]` onto everything
-    // might just be more annoying than useful...
-    // although it is sometimes nice.
-    clippy::must_use_candidate,
-
-    // FIXME: good lint but too many false positives
-    // with our `Env` + `RwLock` setup.
-    clippy::significant_drop_tightening,
-
-    // FIXME: good lint but is less clear in most cases.
-    clippy::items_after_statements,
-
-    clippy::multiple_crate_versions,
-    clippy::module_name_repetitions,
-    clippy::module_inception,
-    clippy::redundant_pub_crate,
-    clippy::option_if_let_else,
-)]
-// Allow some lints in tests.
-#![cfg_attr(
-    test,
-    allow(
-        clippy::cognitive_complexity,
-        clippy::needless_pass_by_value,
-        clippy::cast_possible_truncation,
-        clippy::too_many_lines
-    )
-)]
-
-//---------------------------------------------------------------------------------------------------- Mod
 mod constants;
 mod defaults;
 mod free;
@@ -112,3 +22,10 @@ pub use constants::{
     CORE_RPC_VERSION_MINOR,
 };
 pub use rpc_call::{RpcCall, RpcCallValue};
+
+// false-positive: used in tests
+#[cfg(test)]
+mod test {
+    extern crate cuprate_test_utils;
+    extern crate serde_json;
+}
diff --git a/rpc/types/src/misc/mod.rs b/rpc/types/src/misc/mod.rs
index bd6454dd..c5c1840e 100644
--- a/rpc/types/src/misc/mod.rs
+++ b/rpc/types/src/misc/mod.rs
@@ -15,6 +15,7 @@
 mod binary_string;
 mod distribution;
 mod key_image_spent_status;
+#[allow(clippy::module_inception)]
 mod misc;
 mod pool_info_extent;
 mod status;
diff --git a/storage/blockchain/Cargo.toml b/storage/blockchain/Cargo.toml
index 58da21e6..e0399033 100644
--- a/storage/blockchain/Cargo.toml
+++ b/storage/blockchain/Cargo.toml
@@ -48,3 +48,6 @@ pretty_assertions = { workspace = true }
 proptest          = { workspace = true }
 hex               = { workspace = true }
 hex-literal       = { workspace = true }
+
+[lints]
+workspace = true
diff --git a/storage/blockchain/src/config/config.rs b/storage/blockchain/src/config.rs
similarity index 82%
rename from storage/blockchain/src/config/config.rs
rename to storage/blockchain/src/config.rs
index 957c67c0..e4b76068 100644
--- a/storage/blockchain/src/config/config.rs
+++ b/storage/blockchain/src/config.rs
@@ -1,4 +1,44 @@
-//! The main [`Config`] struct, holding all configurable values.
+//! Database configuration.
+//!
+//! This module contains the main [`Config`]uration struct
+//! for the database [`Env`](cuprate_database::Env)ironment,
+//! and blockchain-specific configuration.
+//!
+//! It also contains types related to configuration settings.
+//!
+//! The main constructor is the [`ConfigBuilder`].
+//!
+//! These configurations are processed at runtime, meaning
+//! the `Env` can/will dynamically adjust its behavior based
+//! on these values.
+//!
+//! # Example
+//! ```rust
+//! use cuprate_blockchain::{
+//!     cuprate_database::{Env, config::SyncMode},
+//!     config::{ConfigBuilder, ReaderThreads},
+//! };
+//!
+//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
+//! let tmp_dir = tempfile::tempdir()?;
+//! let db_dir = tmp_dir.path().to_owned();
+//!
+//! let config = ConfigBuilder::new()
+//!      // Use a custom database directory.
+//!     .db_directory(db_dir.into())
+//!     // Use as many reader threads as possible (when using `service`).
+//!     .reader_threads(ReaderThreads::OnePerThread)
+//!     // Use the fastest sync mode.
+//!     .sync_mode(SyncMode::Fast)
+//!     // Build into `Config`
+//!     .build();
+//!
+//! // Start a database `service` using this configuration.
+//! let (_, _, env) = cuprate_blockchain::service::init(config.clone())?;
+//! // It's using the config we provided.
+//! assert_eq!(env.config(), &config.db_config);
+//! # Ok(()) }
+//! ```
 
 //---------------------------------------------------------------------------------------------------- Import
 use std::{borrow::Cow, path::Path};
diff --git a/storage/blockchain/src/config/mod.rs b/storage/blockchain/src/config/mod.rs
deleted file mode 100644
index 555a6e6e..00000000
--- a/storage/blockchain/src/config/mod.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-//! Database configuration.
-//!
-//! This module contains the main [`Config`]uration struct
-//! for the database [`Env`](cuprate_database::Env)ironment,
-//! and blockchain-specific configuration.
-//!
-//! It also contains types related to configuration settings.
-//!
-//! The main constructor is the [`ConfigBuilder`].
-//!
-//! These configurations are processed at runtime, meaning
-//! the `Env` can/will dynamically adjust its behavior based
-//! on these values.
-//!
-//! # Example
-//! ```rust
-//! use cuprate_blockchain::{
-//!     cuprate_database::{Env, config::SyncMode},
-//!     config::{ConfigBuilder, ReaderThreads},
-//! };
-//!
-//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
-//! let tmp_dir = tempfile::tempdir()?;
-//! let db_dir = tmp_dir.path().to_owned();
-//!
-//! let config = ConfigBuilder::new()
-//!      // Use a custom database directory.
-//!     .db_directory(db_dir.into())
-//!     // Use as many reader threads as possible (when using `service`).
-//!     .reader_threads(ReaderThreads::OnePerThread)
-//!     // Use the fastest sync mode.
-//!     .sync_mode(SyncMode::Fast)
-//!     // Build into `Config`
-//!     .build();
-//!
-//! // Start a database `service` using this configuration.
-//! let (_, _, env) = cuprate_blockchain::service::init(config.clone())?;
-//! // It's using the config we provided.
-//! assert_eq!(env.config(), &config.db_config);
-//! # Ok(()) }
-//! ```
-
-mod config;
-pub use config::{Config, ConfigBuilder, ReaderThreads};
diff --git a/storage/blockchain/src/lib.rs b/storage/blockchain/src/lib.rs
index e544a69e..f66cd99b 100644
--- a/storage/blockchain/src/lib.rs
+++ b/storage/blockchain/src/lib.rs
@@ -1,103 +1,9 @@
 #![doc = include_str!("../README.md")]
-//---------------------------------------------------------------------------------------------------- Lints
-// Forbid lints.
-// Our code, and code generated (e.g macros) cannot overrule these.
-#![forbid(
-    // `unsafe` is allowed but it _must_ be
-    // commented with `SAFETY: reason`.
-    clippy::undocumented_unsafe_blocks,
-
-    // Never.
-    unused_unsafe,
-    redundant_semicolons,
-    unused_allocation,
-    coherence_leak_check,
-    while_true,
-    clippy::missing_docs_in_private_items,
-
-    // Maybe can be put into `#[deny]`.
-    unconditional_recursion,
-    for_loops_over_fallibles,
-    unused_braces,
-    unused_labels,
-    keyword_idents,
-    non_ascii_idents,
-    variant_size_differences,
-    single_use_lifetimes,
-
-    // Probably can be put into `#[deny]`.
-    future_incompatible,
-    let_underscore,
-    break_with_label_and_loop,
-    duplicate_macro_attributes,
-    exported_private_dependencies,
-    large_assignments,
-    overlapping_range_endpoints,
-    semicolon_in_expressions_from_macros,
-    noop_method_call,
-    unreachable_pub,
-)]
-// Deny lints.
-// Some of these are `#[allow]`'ed on a per-case basis.
-#![deny(
-    clippy::all,
-    clippy::correctness,
-    clippy::suspicious,
-    clippy::style,
-    clippy::complexity,
-    clippy::perf,
-    clippy::pedantic,
-    clippy::nursery,
-    clippy::cargo,
-    unused_crate_dependencies,
-    unused_doc_comments,
-    unused_mut,
-    missing_docs,
-    deprecated,
-    unused_comparisons,
-    nonstandard_style
-)]
 #![allow(
-    // FIXME: this lint affects crates outside of
-    // `database/` for some reason, allow for now.
-    clippy::cargo_common_metadata,
-
-    // FIXME: adding `#[must_use]` onto everything
-    // might just be more annoying than useful...
-    // although it is sometimes nice.
-    clippy::must_use_candidate,
-
-    // FIXME: good lint but too many false positives
-    // with our `Env` + `RwLock` setup.
-    clippy::significant_drop_tightening,
-
-    // FIXME: good lint but is less clear in most cases.
-    clippy::items_after_statements,
-
-    clippy::module_name_repetitions,
-    clippy::module_inception,
-    clippy::redundant_pub_crate,
-    clippy::option_if_let_else,
-)]
-// Allow some lints when running in debug mode.
-#![cfg_attr(
-    debug_assertions,
-    allow(
-        clippy::todo,
-        clippy::multiple_crate_versions,
-        // unused_crate_dependencies,
-    )
-)]
-// Allow some lints in tests.
-#![cfg_attr(
-    test,
-    allow(
-        clippy::cognitive_complexity,
-        clippy::needless_pass_by_value,
-        clippy::cast_possible_truncation,
-        clippy::too_many_lines
-    )
+    // See `cuprate-database` for reasoning.
+    clippy::significant_drop_tightening
 )]
+
 // Only allow building 64-bit targets.
 //
 // This allows us to assume 64-bit
diff --git a/storage/blockchain/src/service/free.rs b/storage/blockchain/src/service/free.rs
index e748bbbe..2e7c9086 100644
--- a/storage/blockchain/src/service/free.rs
+++ b/storage/blockchain/src/service/free.rs
@@ -37,8 +37,8 @@ pub fn init(
     let db = Arc::new(crate::open(config)?);
 
     // Spawn the Reader thread pool and Writer.
-    let readers = init_read_service(db.clone(), reader_threads);
-    let writer = init_write_service(db.clone());
+    let readers = init_read_service(Arc::clone(&db), reader_threads);
+    let writer = init_write_service(Arc::clone(&db));
 
     Ok((readers, writer, db))
 }
diff --git a/storage/blockchain/src/service/tests.rs b/storage/blockchain/src/service/tests.rs
index ed13f7b3..b68b5444 100644
--- a/storage/blockchain/src/service/tests.rs
+++ b/storage/blockchain/src/service/tests.rs
@@ -304,8 +304,9 @@ async fn test_template(
     // Assert we get back the same map of
     // `Amount`'s and `AmountIndex`'s.
     let mut response_output_count = 0;
+    #[allow(clippy::iter_over_hash_type)] // order doesn't matter in this test
     for (amount, output_map) in response {
-        let amount_index_set = map.get(&amount).unwrap();
+        let amount_index_set = &map[&amount];
 
         for (amount_index, output) in output_map {
             response_output_count += 1;
diff --git a/storage/database/Cargo.toml b/storage/database/Cargo.toml
index a70457f5..0ef4a97d 100644
--- a/storage/database/Cargo.toml
+++ b/storage/database/Cargo.toml
@@ -32,4 +32,7 @@ serde = { workspace = true, optional = true }
 [dev-dependencies]
 bytemuck  = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
 page_size = { version = "0.6.0" }
-tempfile  = { version = "3.10.0" }
\ No newline at end of file
+tempfile  = { version = "3.10.0" }
+
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/storage/database/src/backend/heed/env.rs b/storage/database/src/backend/heed/env.rs
index 0c2847fb..8c71e617 100644
--- a/storage/database/src/backend/heed/env.rs
+++ b/storage/database/src/backend/heed/env.rs
@@ -70,7 +70,7 @@ impl Drop for ConcreteEnv {
         // We need to do `mdb_env_set_flags(&env, MDB_NOSYNC|MDB_ASYNCMAP, 0)`
         // to clear the no sync and async flags such that the below `self.sync()`
         // _actually_ synchronously syncs.
-        if let Err(_e) = crate::Env::sync(self) {
+        if let Err(_e) = Env::sync(self) {
             // TODO: log error?
         }
 
diff --git a/storage/database/src/backend/heed/storable.rs b/storage/database/src/backend/heed/storable.rs
index 3566e88f..da0e0cb5 100644
--- a/storage/database/src/backend/heed/storable.rs
+++ b/storage/database/src/backend/heed/storable.rs
@@ -78,8 +78,8 @@ mod test {
             println!("left: {left:?}, right: {right:?}, expected: {expected:?}");
             assert_eq!(
                 <StorableHeed::<T> as heed::Comparator>::compare(
-                    &<StorableHeed::<T> as heed::BytesEncode>::bytes_encode(&left).unwrap(),
-                    &<StorableHeed::<T> as heed::BytesEncode>::bytes_encode(&right).unwrap()
+                    &<StorableHeed::<T> as BytesEncode>::bytes_encode(&left).unwrap(),
+                    &<StorableHeed::<T> as BytesEncode>::bytes_encode(&right).unwrap()
                 ),
                 expected
             );
diff --git a/storage/database/src/backend/redb/database.rs b/storage/database/src/backend/redb/database.rs
index cd9a0be9..dafb2417 100644
--- a/storage/database/src/backend/redb/database.rs
+++ b/storage/database/src/backend/redb/database.rs
@@ -23,7 +23,7 @@ use crate::{
 /// Shared [`DatabaseRo::get()`].
 #[inline]
 fn get<T: Table + 'static>(
-    db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
+    db: &impl ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
     key: &T::Key,
 ) -> Result<T::Value, RuntimeError> {
     Ok(db.get(key)?.ok_or(RuntimeError::KeyNotFound)?.value())
@@ -32,7 +32,7 @@ fn get<T: Table + 'static>(
 /// Shared [`DatabaseRo::len()`].
 #[inline]
 fn len<T: Table>(
-    db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
+    db: &impl ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
 ) -> Result<u64, RuntimeError> {
     Ok(db.len()?)
 }
@@ -40,7 +40,7 @@ fn len<T: Table>(
 /// Shared [`DatabaseRo::first()`].
 #[inline]
 fn first<T: Table>(
-    db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
+    db: &impl ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
 ) -> Result<(T::Key, T::Value), RuntimeError> {
     let (key, value) = db.first()?.ok_or(RuntimeError::KeyNotFound)?;
     Ok((key.value(), value.value()))
@@ -49,7 +49,7 @@ fn first<T: Table>(
 /// Shared [`DatabaseRo::last()`].
 #[inline]
 fn last<T: Table>(
-    db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
+    db: &impl ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
 ) -> Result<(T::Key, T::Value), RuntimeError> {
     let (key, value) = db.last()?.ok_or(RuntimeError::KeyNotFound)?;
     Ok((key.value(), value.value()))
@@ -58,7 +58,7 @@ fn last<T: Table>(
 /// Shared [`DatabaseRo::is_empty()`].
 #[inline]
 fn is_empty<T: Table>(
-    db: &impl redb::ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
+    db: &impl ReadableTable<StorableRedb<T::Key>, StorableRedb<T::Value>>,
 ) -> Result<bool, RuntimeError> {
     Ok(db.is_empty()?)
 }
diff --git a/storage/database/src/config/mod.rs b/storage/database/src/config/mod.rs
index 19a324e1..c6ed0c01 100644
--- a/storage/database/src/config/mod.rs
+++ b/storage/database/src/config/mod.rs
@@ -33,6 +33,7 @@
 //! # Ok(()) }
 //! ```
 
+#[allow(clippy::module_inception)]
 mod config;
 pub use config::{Config, ConfigBuilder, READER_THREADS_DEFAULT};
 
diff --git a/storage/database/src/env.rs b/storage/database/src/env.rs
index cae49733..82944434 100644
--- a/storage/database/src/env.rs
+++ b/storage/database/src/env.rs
@@ -163,7 +163,7 @@ pub trait Env: Sized {
         // We have the direct PATH to the file,
         // no need to use backend-specific functions.
         //
-        // SAFETY: as we are only accessing the metadata of
+        // INVARIANT: as we are only accessing the metadata of
         // the file and not reading the bytes, it should be
         // fine even with a memory mapped file being actively
         // written to.
diff --git a/storage/database/src/key.rs b/storage/database/src/key.rs
index 3273d4ed..2f3855a4 100644
--- a/storage/database/src/key.rs
+++ b/storage/database/src/key.rs
@@ -163,11 +163,11 @@ impl KeyCompare {
     #[inline]
     pub const fn as_compare_fn<K: Key>(self) -> fn(&[u8], &[u8]) -> Ordering {
         match self {
-            Self::Default => std::cmp::Ord::cmp,
+            Self::Default => Ord::cmp,
             Self::Number => |left, right| {
                 let left = <K as Storable>::from_bytes(left);
                 let right = <K as Storable>::from_bytes(right);
-                std::cmp::Ord::cmp(&left, &right)
+                Ord::cmp(&left, &right)
             },
             Self::Custom(f) => f,
         }
diff --git a/storage/database/src/lib.rs b/storage/database/src/lib.rs
index 5946fe5e..45bfc53c 100644
--- a/storage/database/src/lib.rs
+++ b/storage/database/src/lib.rs
@@ -1,94 +1,18 @@
 #![doc = include_str!("../README.md")]
-//---------------------------------------------------------------------------------------------------- Lints
-// Forbid lints.
-// Our code, and code generated (e.g macros) cannot overrule these.
-#![forbid(
-    // `unsafe` is allowed but it _must_ be
-    // commented with `SAFETY: reason`.
-    clippy::undocumented_unsafe_blocks,
-
-    // Never.
-    unused_unsafe,
-    redundant_semicolons,
-    unused_allocation,
-    coherence_leak_check,
-    while_true,
-    clippy::missing_docs_in_private_items,
-
-    // Maybe can be put into `#[deny]`.
-    unconditional_recursion,
-    for_loops_over_fallibles,
-    unused_braces,
-    unused_labels,
-    keyword_idents,
-    non_ascii_idents,
-    variant_size_differences,
-    single_use_lifetimes,
-
-    // Probably can be put into `#[deny]`.
-    future_incompatible,
-    let_underscore,
-    break_with_label_and_loop,
-    duplicate_macro_attributes,
-    exported_private_dependencies,
-    large_assignments,
-    overlapping_range_endpoints,
-    semicolon_in_expressions_from_macros,
-    noop_method_call,
-    unreachable_pub,
-)]
-// Deny lints.
-// Some of these are `#[allow]`'ed on a per-case basis.
-#![deny(
-    clippy::all,
-    clippy::correctness,
-    clippy::suspicious,
-    clippy::style,
-    clippy::complexity,
-    clippy::perf,
-    clippy::pedantic,
-    clippy::nursery,
-    clippy::cargo,
-    unused_crate_dependencies,
-    unused_doc_comments,
-    unused_mut,
-    missing_docs,
-    deprecated,
-    unused_comparisons,
-    nonstandard_style
-)]
 #![allow(
-    // FIXME: this lint affects crates outside of
-    // `database/` for some reason, allow for now.
-    clippy::cargo_common_metadata,
-
-    // FIXME: adding `#[must_use]` onto everything
-    // might just be more annoying than useful...
-    // although it is sometimes nice.
-    clippy::must_use_candidate,
-
-    // FIXME: good lint but too many false positives
-    // with our `Env` + `RwLock` setup.
-    clippy::significant_drop_tightening,
-
-    // FIXME: good lint but is less clear in most cases.
-    clippy::items_after_statements,
-
-    clippy::module_name_repetitions,
-    clippy::module_inception,
-    clippy::redundant_pub_crate,
-    clippy::option_if_let_else,
-
-    // unused_crate_dependencies, // false-positive with `paste`
-)]
-// Allow some lints when running in debug mode.
-#![cfg_attr(
-    debug_assertions,
-    allow(
-        clippy::todo,
-        clippy::multiple_crate_versions,
-        // unused_crate_dependencies,
-    )
+    // This lint is allowed because the following
+    // code exists a lot in this crate:
+    //
+    // ```rust
+    // let env_inner = env.env_inner();
+    // let tx_rw = env_inner.tx_rw()?;
+    // OpenTables::create_tables(&env_inner, &tx_rw)?;
+    // ```
+    //
+    // Rust thinks `env_inner` can be dropped earlier
+    // but it cannot, we need it for the lifetime of
+    // the database transaction + tables.
+    clippy::significant_drop_tightening
 )]
 // Allow some lints in tests.
 #![cfg_attr(
diff --git a/storage/txpool/Cargo.toml b/storage/txpool/Cargo.toml
index d5ea77d0..70211d9e 100644
--- a/storage/txpool/Cargo.toml
+++ b/storage/txpool/Cargo.toml
@@ -41,3 +41,6 @@ cuprate-test-utils = { path = "../../test-utils" }
 tokio              = { workspace = true }
 tempfile           = { workspace = true }
 hex-literal        = { workspace = true }
+
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/storage/txpool/src/config.rs b/storage/txpool/src/config.rs
index 8d09b5e5..1ef0d734 100644
--- a/storage/txpool/src/config.rs
+++ b/storage/txpool/src/config.rs
@@ -211,7 +211,7 @@ impl Config {
     /// assert_eq!(config.reader_threads, ReaderThreads::default());
     /// ```
     pub fn new() -> Self {
-        Config {
+        Self {
             db_config: DbConfig::new(Cow::Borrowed(&*CUPRATE_TXPOOL_DIR)),
             reader_threads: ReaderThreads::default(),
             max_txpool_weight: 0,
diff --git a/storage/txpool/src/lib.rs b/storage/txpool/src/lib.rs
index f200c348..243dc4d9 100644
--- a/storage/txpool/src/lib.rs
+++ b/storage/txpool/src/lib.rs
@@ -1,4 +1,8 @@
 #![doc = include_str!("../README.md")]
+#![allow(
+    // See `cuprate-database` for reasoning.
+    clippy::significant_drop_tightening
+)]
 
 pub mod config;
 mod free;
@@ -13,3 +17,13 @@ pub use free::open;
 
 //re-exports
 pub use cuprate_database;
+
+// TODO: remove when used.
+use tower as _;
+#[cfg(test)]
+mod test {
+    use cuprate_test_utils as _;
+    use hex_literal as _;
+    use tempfile as _;
+    use tokio as _;
+}
diff --git a/storage/txpool/src/ops/key_images.rs b/storage/txpool/src/ops/key_images.rs
index c6e44152..04aa1b44 100644
--- a/storage/txpool/src/ops/key_images.rs
+++ b/storage/txpool/src/ops/key_images.rs
@@ -11,7 +11,7 @@ use crate::{ops::TxPoolWriteError, tables::SpentKeyImages, types::TransactionHas
 ///
 /// # Panics
 /// This function will panic if any of the [`Input`]s are not [`Input::ToKey`]
-pub fn add_tx_key_images(
+pub(super) fn add_tx_key_images(
     inputs: &[Input],
     tx_hash: &TransactionHash,
     kis_table: &mut impl DatabaseRw<SpentKeyImages>,
@@ -31,7 +31,7 @@ pub fn add_tx_key_images(
 ///
 /// # Panics
 /// This function will panic if any of the [`Input`]s are not [`Input::ToKey`]
-pub fn remove_tx_key_images(
+pub(super) fn remove_tx_key_images(
     inputs: &[Input],
     kis_table: &mut impl DatabaseRw<SpentKeyImages>,
 ) -> Result<(), RuntimeError> {
diff --git a/storage/txpool/src/service/free.rs b/storage/txpool/src/service/free.rs
index 614ab5c4..003da552 100644
--- a/storage/txpool/src/service/free.rs
+++ b/storage/txpool/src/service/free.rs
@@ -30,8 +30,8 @@ pub fn init(
     let db = Arc::new(crate::open(config)?);
 
     // Spawn the Reader thread pool and Writer.
-    let readers = init_read_service(db.clone(), reader_threads);
-    let writer = init_write_service(db.clone());
+    let readers = init_read_service(Arc::clone(&db), reader_threads);
+    let writer = init_write_service(Arc::clone(&db));
 
     Ok((readers, writer, db))
 }
diff --git a/storage/txpool/src/service/read.rs b/storage/txpool/src/service/read.rs
index c2fee66d..56541641 100644
--- a/storage/txpool/src/service/read.rs
+++ b/storage/txpool/src/service/read.rs
@@ -25,7 +25,7 @@ use crate::{
 /// Should be called _once_ per actual database.
 #[cold]
 #[inline(never)] // Only called once.
-pub fn init_read_service(env: Arc<ConcreteEnv>, threads: ReaderThreads) -> TxpoolReadHandle {
+pub(super) fn init_read_service(env: Arc<ConcreteEnv>, threads: ReaderThreads) -> TxpoolReadHandle {
     init_read_service_with_pool(env, init_thread_pool(threads))
 }
 
@@ -35,10 +35,7 @@ pub fn init_read_service(env: Arc<ConcreteEnv>, threads: ReaderThreads) -> Txpoo
 /// Should be called _once_ per actual database.
 #[cold]
 #[inline(never)] // Only called once.
-pub fn init_read_service_with_pool(
-    env: Arc<ConcreteEnv>,
-    pool: Arc<ThreadPool>,
-) -> TxpoolReadHandle {
+fn init_read_service_with_pool(env: Arc<ConcreteEnv>, pool: Arc<ThreadPool>) -> TxpoolReadHandle {
     DatabaseReadService::new(env, pool, map_request)
 }
 
@@ -53,6 +50,7 @@ pub fn init_read_service_with_pool(
 /// 1. `Request` is mapped to a handler function
 /// 2. Handler function is called
 /// 3. [`TxpoolReadResponse`] is returned
+#[allow(clippy::needless_pass_by_value)]
 fn map_request(
     env: &ConcreteEnv,          // Access to the database
     request: TxpoolReadRequest, // The request we must fulfill
diff --git a/storage/txpool/src/service/write.rs b/storage/txpool/src/service/write.rs
index f6bdb385..8a3b1bf7 100644
--- a/storage/txpool/src/service/write.rs
+++ b/storage/txpool/src/service/write.rs
@@ -16,7 +16,7 @@ use crate::{
 
 //---------------------------------------------------------------------------------------------------- init_write_service
 /// Initialize the txpool write service from a [`ConcreteEnv`].
-pub fn init_write_service(env: Arc<ConcreteEnv>) -> TxpoolWriteHandle {
+pub(super) fn init_write_service(env: Arc<ConcreteEnv>) -> TxpoolWriteHandle {
     DatabaseWriteHandle::init(env, handle_txpool_request)
 }
 
diff --git a/storage/txpool/src/types.rs b/storage/txpool/src/types.rs
index 5c89d3b9..09b0ce0d 100644
--- a/storage/txpool/src/types.rs
+++ b/storage/txpool/src/types.rs
@@ -35,10 +35,11 @@ bitflags::bitflags! {
 pub struct TransactionInfo {
     /// The transaction's fee.
     pub fee: u64,
-    /// The transaction`s weight.
+    /// The transaction's weight.
     pub weight: usize,
     /// [`TxStateFlags`] of this transaction.
     pub flags: TxStateFlags,
+    #[allow(clippy::pub_underscore_fields)]
     /// Explicit padding so that we have no implicit padding bytes in `repr(C)`.
     ///
     /// Allows potential future expansion of this type.
@@ -68,21 +69,21 @@ impl From<RawCachedVerificationState> for CachedVerificationState {
     fn from(value: RawCachedVerificationState) -> Self {
         // if the hash is all `0`s then there is no hash this is valid at.
         if value.raw_valid_at_hash == [0; 32] {
-            return CachedVerificationState::NotVerified;
+            return Self::NotVerified;
         }
 
         let raw_valid_past_timestamp = u64::from_le_bytes(value.raw_valid_past_timestamp);
 
         // if the timestamp is 0, there is no timestamp that needs to be passed.
         if raw_valid_past_timestamp == 0 {
-            return CachedVerificationState::ValidAtHashAndHF {
+            return Self::ValidAtHashAndHF {
                 block_hash: value.raw_valid_at_hash,
                 hf: HardFork::from_version(value.raw_hf)
                     .expect("hard-fork values stored in the DB should always be valid"),
             };
         }
 
-        CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock {
+        Self::ValidAtHashAndHFWithTimeBasedLock {
             block_hash: value.raw_valid_at_hash,
             hf: HardFork::from_version(value.raw_hf)
                 .expect("hard-fork values stored in the DB should always be valid"),
@@ -91,6 +92,7 @@ impl From<RawCachedVerificationState> for CachedVerificationState {
     }
 }
 
+#[allow(clippy::fallible_impl_from)] // only panics in invalid states
 impl From<CachedVerificationState> for RawCachedVerificationState {
     fn from(value: CachedVerificationState) -> Self {
         match value {
diff --git a/types/Cargo.toml b/types/Cargo.toml
index 4c31cfc0..4b9204b9 100644
--- a/types/Cargo.toml
+++ b/types/Cargo.toml
@@ -23,10 +23,12 @@ bytes            = { workspace = true }
 curve25519-dalek = { workspace = true }
 monero-serai     = { workspace = true }
 serde            = { workspace = true, features = ["derive"], optional = true }
-borsh            = { workspace = true, optional = true }
 thiserror        = { workspace = true }
 
 proptest = { workspace = true, optional = true }
 proptest-derive  = { workspace = true, optional = true }
 
-[dev-dependencies]
\ No newline at end of file
+[dev-dependencies]
+
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/types/src/lib.rs b/types/src/lib.rs
index d70f4c31..0b0dbe67 100644
--- a/types/src/lib.rs
+++ b/types/src/lib.rs
@@ -1,76 +1,6 @@
 #![doc = include_str!("../README.md")]
-//---------------------------------------------------------------------------------------------------- Lints
-// Forbid lints.
-// Our code, and code generated (e.g macros) cannot overrule these.
-#![forbid(
-	// `unsafe` is allowed but it _must_ be
-	// commented with `SAFETY: reason`.
-	clippy::undocumented_unsafe_blocks,
-
-	// Never.
-	unused_unsafe,
-	redundant_semicolons,
-	unused_allocation,
-	coherence_leak_check,
-	single_use_lifetimes,
-	while_true,
-	clippy::missing_docs_in_private_items,
-
-	// Maybe can be put into `#[deny]`.
-	unconditional_recursion,
-	for_loops_over_fallibles,
-	unused_braces,
-	unused_doc_comments,
-	unused_labels,
-	keyword_idents,
-	non_ascii_idents,
-	variant_size_differences,
-
-	// Probably can be put into `#[deny]`.
-	future_incompatible,
-	let_underscore,
-	break_with_label_and_loop,
-	duplicate_macro_attributes,
-	exported_private_dependencies,
-	large_assignments,
-	overlapping_range_endpoints,
-	semicolon_in_expressions_from_macros,
-	noop_method_call,
-	unreachable_pub,
-)]
-// Deny lints.
-// Some of these are `#[allow]`'ed on a per-case basis.
-#![deny(
-    clippy::all,
-    clippy::correctness,
-    clippy::suspicious,
-    clippy::style,
-    clippy::complexity,
-    clippy::perf,
-    clippy::pedantic,
-    clippy::nursery,
-    clippy::cargo,
-    unused_mut,
-    missing_docs,
-    deprecated,
-    unused_comparisons,
-    nonstandard_style
-)]
-#![allow(
-	// FIXME: this lint affects crates outside of
-	// `database/` for some reason, allow for now.
-	clippy::cargo_common_metadata,
-
-	// FIXME: adding `#[must_use]` onto everything
-	// might just be more annoying than useful...
-	// although it is sometimes nice.
-	clippy::must_use_candidate,
-
-	clippy::module_name_repetitions,
-	clippy::module_inception,
-	clippy::redundant_pub_crate,
-	clippy::option_if_let_else,
-)]
+// `proptest` needs this internally.
+#![cfg_attr(any(feature = "proptest"), allow(non_local_definitions))]
 // Allow some lints when running in debug mode.
 #![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))]
 
@@ -97,4 +27,5 @@ pub use types::{
 //---------------------------------------------------------------------------------------------------- Feature-gated
 #[cfg(feature = "blockchain")]
 pub mod blockchain;
+
 //---------------------------------------------------------------------------------------------------- Private

From 0941f68efcd7dfe66124ad0c1934277f47da9090 Mon Sep 17 00:00:00 2001
From: hinto-janai <hinto.janai@protonmail.com>
Date: Mon, 2 Sep 2024 17:46:11 -0400
Subject: [PATCH 4/5] helper: fix clippy (#265)

* helper: fix lints

* fix tests
---
 helper/src/cast.rs | 16 +++++++++-------
 helper/src/map.rs  |  4 ++--
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/helper/src/cast.rs b/helper/src/cast.rs
index 81d0836a..99b7f53e 100644
--- a/helper/src/cast.rs
+++ b/helper/src/cast.rs
@@ -4,14 +4,16 @@
 //!
 //! `#[no_std]` compatible.
 
+#![allow(clippy::cast_possible_truncation)]
+
 #[rustfmt::skip]
 //============================ SAFETY: DO NOT REMOVE ===========================//
 //                                                                              //
 //                                                                              //
-//                      Only allow building 64-bit targets.                     //
-//             This allows us to assume 64-bit invariants in this file.         //
+//                     Only allow building 64-bit targets.                      //
+//            This allows us to assume 64-bit invariants in this file.          //
                        #[cfg(not(target_pointer_width = "64"))]
-            compile_error!("Cuprate is only compatible with 64-bit CPUs");
+           compile_error!("Cuprate is only compatible with 64-bit CPUs");
 //                                                                              //
 //                                                                              //
 //============================ SAFETY: DO NOT REMOVE ===========================//
@@ -60,8 +62,8 @@ mod test {
 
     #[test]
     fn max_unsigned() {
-        assert_eq!(u32_to_usize(u32::MAX), u32::MAX as usize);
-        assert_eq!(usize_to_u64(u32_to_usize(u32::MAX)), u32::MAX as u64);
+        assert_eq!(u32_to_usize(u32::MAX), usize::try_from(u32::MAX).unwrap());
+        assert_eq!(usize_to_u64(u32_to_usize(u32::MAX)), u64::from(u32::MAX));
 
         assert_eq!(u64_to_usize(u64::MAX), usize::MAX);
         assert_eq!(usize_to_u64(u64_to_usize(u64::MAX)), u64::MAX);
@@ -72,8 +74,8 @@ mod test {
 
     #[test]
     fn max_signed() {
-        assert_eq!(i32_to_isize(i32::MAX), i32::MAX as isize);
-        assert_eq!(isize_to_i64(i32_to_isize(i32::MAX)), i32::MAX as i64);
+        assert_eq!(i32_to_isize(i32::MAX), isize::try_from(i32::MAX).unwrap());
+        assert_eq!(isize_to_i64(i32_to_isize(i32::MAX)), i64::from(i32::MAX));
 
         assert_eq!(i64_to_isize(i64::MAX), isize::MAX);
         assert_eq!(isize_to_i64(i64_to_isize(i64::MAX)), i64::MAX);
diff --git a/helper/src/map.rs b/helper/src/map.rs
index ea6dfc49..7805ea66 100644
--- a/helper/src/map.rs
+++ b/helper/src/map.rs
@@ -76,7 +76,7 @@ pub const fn combine_low_high_bits_to_u128(low_bits: u64, high_bits: u64) -> u12
 /// assert_eq!(u64_to_timelock(499_999_999), Timelock::Block(499_999_999));
 /// assert_eq!(u64_to_timelock(500_000_000), Timelock::Time(500_000_000));
 /// ```
-pub fn u64_to_timelock(u: u64) -> Timelock {
+pub const fn u64_to_timelock(u: u64) -> Timelock {
     if u == 0 {
         Timelock::None
     } else if u < 500_000_000 {
@@ -97,7 +97,7 @@ pub fn u64_to_timelock(u: u64) -> Timelock {
 /// assert_eq!(timelock_to_u64(Timelock::Block(499_999_999)), 499_999_999);
 /// assert_eq!(timelock_to_u64(Timelock::Time(500_000_000)), 500_000_000);
 /// ```
-pub fn timelock_to_u64(timelock: Timelock) -> u64 {
+pub const fn timelock_to_u64(timelock: Timelock) -> u64 {
     match timelock {
         Timelock::None => 0,
         Timelock::Block(u) => usize_to_u64(u),

From 4653ac58849c81b6ab993a1d23f061a97962524b Mon Sep 17 00:00:00 2001
From: hinto-janai <hinto.janai@protonmail.com>
Date: Thu, 5 Sep 2024 11:53:16 -0400
Subject: [PATCH 5/5] rpc/interface: separate `RpcHandler` into 3
 `tower::Service`s (#266)

* apply diff

* cleanup

* fix test
---
 rpc/interface/README.md                |  16 +-
 rpc/interface/src/lib.rs               |   6 +-
 rpc/interface/src/route/bin.rs         |   9 +-
 rpc/interface/src/route/json_rpc.rs    |  28 ++--
 rpc/interface/src/route/other.rs       |  10 +-
 rpc/interface/src/rpc_handler.rs       |  49 +++---
 rpc/interface/src/rpc_handler_dummy.rs | 210 +++++++++++++++----------
 rpc/interface/src/rpc_request.rs       |  33 ----
 rpc/interface/src/rpc_response.rs      |  33 ----
 rpc/interface/src/rpc_service.rs       |  52 ++++++
 10 files changed, 227 insertions(+), 219 deletions(-)
 delete mode 100644 rpc/interface/src/rpc_request.rs
 delete mode 100644 rpc/interface/src/rpc_response.rs
 create mode 100644 rpc/interface/src/rpc_service.rs

diff --git a/rpc/interface/README.md b/rpc/interface/README.md
index 3a63ac46..eb878643 100644
--- a/rpc/interface/README.md
+++ b/rpc/interface/README.md
@@ -17,7 +17,7 @@ CLIENT ─► ROUTE ─► REQUEST ─► HANDLER ─► RESPONSE ─► CLIENT
 
 Everything coming _in_ from a client is handled by this crate.
 
-This is where your [`RpcHandler`] turns this [`RpcRequest`] into a [`RpcResponse`].
+This is where your [`RpcHandler`] turns this `Request` into a `Response`.
 
 You hand this `Response` back to `cuprate-rpc-interface` and it will take care of sending it back to the client.
 
@@ -42,16 +42,18 @@ The proper usage of this crate is to:
 3. Do whatever with it
 
 # The [`RpcHandler`]
-This is your [`tower::Service`] that converts [`RpcRequest`]s into [`RpcResponse`]s,
+This is your [`tower::Service`] that converts `Request`s into `Response`s,
 i.e. the "inner handler".
 
-Said concretely, `RpcHandler` is a `tower::Service` where the associated types are from this crate:
-- [`RpcRequest`]
-- [`RpcResponse`]
+Said concretely, `RpcHandler` is 3 `tower::Service`s where the request/response types are
+the 3 endpoint enums from [`cuprate_rpc_types`] and the error type is from this crate:
+- [`JsonRpcRequest`](cuprate_rpc_types::json::JsonRpcRequest) & [`JsonRpcResponse`](cuprate_rpc_types::json::JsonRpcResponse)
+- [`BinRequest`](cuprate_rpc_types::bin::BinRequest) & [`BinResponse`](cuprate_rpc_types::bin::BinRequest)
+- [`OtherRequest`](cuprate_rpc_types::other::OtherRequest) & [`OtherResponse`](cuprate_rpc_types::other::OtherRequest)
 - [`RpcError`]
 
 `RpcHandler`'s [`Future`](std::future::Future) is generic, _although_,
-it must output `Result<RpcResponse, RpcError>`.
+it must output `Result<$RESPONSE, RpcError>`.
 
 The `RpcHandler` must also hold some state that is required
 for RPC server operation.
@@ -83,7 +85,7 @@ use cuprate_rpc_types::{
     json::{JsonRpcRequest, JsonRpcResponse, GetBlockCountResponse},
     other::{OtherRequest, OtherResponse},
 };
-use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy, RpcRequest};
+use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy};
 
 // Send a `/get_height` request. This endpoint has no inputs.
 async fn get_height(port: u16) -> OtherResponse {
diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs
index d4376979..ebea4939 100644
--- a/rpc/interface/src/lib.rs
+++ b/rpc/interface/src/lib.rs
@@ -7,16 +7,14 @@ mod rpc_error;
 mod rpc_handler;
 #[cfg(feature = "dummy")]
 mod rpc_handler_dummy;
-mod rpc_request;
-mod rpc_response;
+mod rpc_service;
 
 pub use router_builder::RouterBuilder;
 pub use rpc_error::RpcError;
 pub use rpc_handler::RpcHandler;
 #[cfg(feature = "dummy")]
 pub use rpc_handler_dummy::RpcHandlerDummy;
-pub use rpc_request::RpcRequest;
-pub use rpc_response::RpcResponse;
+pub use rpc_service::RpcService;
 
 // false-positive: used in `README.md`'s doc-test.
 #[cfg(test)]
diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs
index 942e0917..45447caf 100644
--- a/rpc/interface/src/route/bin.rs
+++ b/rpc/interface/src/route/bin.rs
@@ -7,7 +7,7 @@ use tower::ServiceExt;
 use cuprate_epee_encoding::from_bytes;
 use cuprate_rpc_types::bin::{BinRequest, BinResponse, GetTransactionPoolHashesRequest};
 
-use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
+use crate::rpc_handler::RpcHandler;
 
 //---------------------------------------------------------------------------------------------------- Routes
 /// This macro generates route functions that expect input.
@@ -67,13 +67,8 @@ macro_rules! generate_endpoints_inner {
         paste::paste! {
             {
                 // Send request.
-                let request = RpcRequest::Binary($request);
-                let channel = $handler.oneshot(request).await?;
+                let response = $handler.oneshot($request).await?;
 
-                // Assert the response from the inner handler is correct.
-                let RpcResponse::Binary(response) = channel else {
-                    panic!("RPC handler did not return a binary response");
-                };
                 let BinResponse::$variant(response) = response else {
                     panic!("RPC handler returned incorrect response");
                 };
diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route/json_rpc.rs
index bd35e437..bf3d937d 100644
--- a/rpc/interface/src/route/json_rpc.rs
+++ b/rpc/interface/src/route/json_rpc.rs
@@ -8,21 +8,21 @@ use tower::ServiceExt;
 
 use cuprate_json_rpc::{
     error::{ErrorCode, ErrorObject},
-    Id,
+    Id, Response,
 };
 use cuprate_rpc_types::{
     json::{JsonRpcRequest, JsonRpcResponse},
     RpcCallValue,
 };
 
-use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
+use crate::rpc_handler::RpcHandler;
 
 //---------------------------------------------------------------------------------------------------- Routes
 /// The `/json_rpc` route function used in [`crate::RouterBuilder`].
 pub(crate) async fn json_rpc<H: RpcHandler>(
     State(handler): State<H>,
     Json(request): Json<cuprate_json_rpc::Request<JsonRpcRequest>>,
-) -> Result<Json<cuprate_json_rpc::Response<JsonRpcResponse>>, StatusCode> {
+) -> Result<Json<Response<JsonRpcResponse>>, StatusCode> {
     // TODO: <https://www.jsonrpc.org/specification#notification>
     //
     // JSON-RPC notifications (requests without `id`)
@@ -30,6 +30,11 @@ pub(crate) async fn json_rpc<H: RpcHandler>(
     // must remain. How to do this considering this function will
     // always return and cause `axum` to respond?
 
+    // JSON-RPC 2.0 rule:
+    // If there was an error in detecting the `Request`'s ID,
+    // the `Response` must contain an `Id::Null`
+    let id = request.id.unwrap_or(Id::Null);
+
     // Return early if this RPC server is restricted and
     // the requested method is only for non-restricted RPC.
     if request.body.is_restricted() && handler.restricted() {
@@ -39,26 +44,15 @@ pub(crate) async fn json_rpc<H: RpcHandler>(
             data: None,
         };
 
-        // JSON-RPC 2.0 rule:
-        // If there was an error in detecting the `Request`'s ID,
-        // the `Response` must contain an `Id::Null`
-        let id = request.id.unwrap_or(Id::Null);
-
-        let response = cuprate_json_rpc::Response::err(id, error_object);
+        let response = Response::err(id, error_object);
 
         return Ok(Json(response));
     }
 
     // Send request.
-    let request = RpcRequest::JsonRpc(request);
-    let channel = handler.oneshot(request).await?;
+    let response = handler.oneshot(request.body).await?;
 
-    // Assert the response from the inner handler is correct.
-    let RpcResponse::JsonRpc(response) = channel else {
-        panic!("RPC handler returned incorrect response");
-    };
-
-    Ok(Json(response))
+    Ok(Json(Response::ok(id, response)))
 }
 
 //---------------------------------------------------------------------------------------------------- Tests
diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs
index ce778db9..129ddd59 100644
--- a/rpc/interface/src/route/other.rs
+++ b/rpc/interface/src/route/other.rs
@@ -25,7 +25,7 @@ use cuprate_rpc_types::{
     RpcCall,
 };
 
-use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
+use crate::rpc_handler::RpcHandler;
 
 //---------------------------------------------------------------------------------------------------- Routes
 /// This macro generates route functions that expect input.
@@ -81,13 +81,9 @@ macro_rules! generate_endpoints_inner {
                 }
 
                 // Send request.
-                let request = RpcRequest::Other(OtherRequest::$variant($request));
-                let channel = $handler.oneshot(request).await?;
+                let request = OtherRequest::$variant($request);
+                let response = $handler.oneshot(request).await?;
 
-                // Assert the response from the inner handler is correct.
-                let RpcResponse::Other(response) = channel else {
-                    panic!("RPC handler did not return a binary response");
-                };
                 let OtherResponse::$variant(response) = response else {
                     panic!("RPC handler returned incorrect response")
                 };
diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs
index bcd08733..1299ec48 100644
--- a/rpc/interface/src/rpc_handler.rs
+++ b/rpc/interface/src/rpc_handler.rs
@@ -1,43 +1,42 @@
 //! RPC handler trait.
 
 //---------------------------------------------------------------------------------------------------- Use
-use std::future::Future;
+use cuprate_rpc_types::{
+    bin::{BinRequest, BinResponse},
+    json::{JsonRpcRequest, JsonRpcResponse},
+    other::{OtherRequest, OtherResponse},
+};
 
-use tower::Service;
-
-use crate::{rpc_error::RpcError, rpc_request::RpcRequest, rpc_response::RpcResponse};
+use crate::RpcService;
 
 //---------------------------------------------------------------------------------------------------- RpcHandler
 /// An RPC handler.
 ///
-/// This trait represents a type that can turn [`RpcRequest`]s into [`RpcResponse`]s.
+/// This trait represents a type that can turn `Request`s into `Response`s.
 ///
-/// Implementors of this trait must be [`tower::Service`]s that use:
-/// - [`RpcRequest`] as the generic `Request` type
-/// - [`RpcResponse`] as the associated `Response` type
-/// - [`RpcError`] as the associated `Error` type
-/// - A generic [`Future`] that outputs `Result<RpcResponse, RpcError>`
+/// Implementors of this trait must be:
+/// - A [`tower::Service`] that uses [`JsonRpcRequest`] & [`JsonRpcResponse`]
+/// - A [`tower::Service`] that uses [`BinRequest`] & [`BinResponse`]
+/// - A [`tower::Service`] that uses [`OtherRequest`] & [`OtherResponse`]
+///
+/// In other words, an [`RpcHandler`] is a type that implements [`tower::Service`] 3 times,
+/// one for each request/response enum type found in [`cuprate_rpc_types`].
+///
+/// The error type must always be [`RpcError`](crate::RpcError).
 ///
 /// See this crate's `RpcHandlerDummy` for an implementation example of this trait.
 ///
 /// # Panics
-/// Your [`RpcHandler`] must reply to [`RpcRequest`]s with the correct
-/// [`RpcResponse`] or else this crate will panic during routing functions.
+/// Your [`RpcHandler`] must reply to `Request`s with the correct
+/// `Response` or else this crate will panic during routing functions.
 ///
-/// For example, upon a [`RpcRequest::Binary`] must be replied with
-/// [`RpcRequest::Binary`]. If an [`RpcRequest::Other`] were returned instead,
-/// this crate would panic.
+/// For example, a [`JsonRpcRequest::GetBlockCount`] must be replied with
+/// [`JsonRpcResponse::GetBlockCount`]. If anything else is returned,
+/// this crate may panic.
 pub trait RpcHandler:
-    Clone
-    + Send
-    + Sync
-    + 'static
-    + Service<
-        RpcRequest,
-        Response = RpcResponse,
-        Error = RpcError,
-        Future: Future<Output = Result<RpcResponse, RpcError>> + Send + Sync + 'static,
-    >
+    RpcService<JsonRpcRequest, JsonRpcResponse>
+    + RpcService<BinRequest, BinResponse>
+    + RpcService<OtherRequest, OtherResponse>
 {
     /// Is this [`RpcHandler`] restricted?
     ///
diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs
index 73ffe9c8..06fa4608 100644
--- a/rpc/interface/src/rpc_handler_dummy.rs
+++ b/rpc/interface/src/rpc_handler_dummy.rs
@@ -3,18 +3,19 @@
 //---------------------------------------------------------------------------------------------------- Use
 use std::task::Poll;
 
+use cuprate_rpc_types::{
+    bin::{BinRequest, BinResponse},
+    json::{JsonRpcRequest, JsonRpcResponse},
+    other::{OtherRequest, OtherResponse},
+};
 use futures::channel::oneshot::channel;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
 use tower::Service;
 
 use cuprate_helper::asynch::InfallibleOneshotReceiver;
-use cuprate_json_rpc::Id;
 
-use crate::{
-    rpc_error::RpcError, rpc_handler::RpcHandler, rpc_request::RpcRequest,
-    rpc_response::RpcResponse,
-};
+use crate::{rpc_error::RpcError, rpc_handler::RpcHandler};
 
 //---------------------------------------------------------------------------------------------------- RpcHandlerDummy
 /// An [`RpcHandler`] that always returns [`Default::default`].
@@ -42,96 +43,133 @@ impl RpcHandler for RpcHandlerDummy {
     }
 }
 
-impl Service<RpcRequest> for RpcHandlerDummy {
-    type Response = RpcResponse;
+impl Service<JsonRpcRequest> for RpcHandlerDummy {
+    type Response = JsonRpcResponse;
     type Error = RpcError;
-    type Future = InfallibleOneshotReceiver<Result<RpcResponse, RpcError>>;
+    type Future = InfallibleOneshotReceiver<Result<JsonRpcResponse, RpcError>>;
 
     fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
         Poll::Ready(Ok(()))
     }
 
-    fn call(&mut self, req: RpcRequest) -> Self::Future {
-        use cuprate_rpc_types::bin::BinRequest as BReq;
-        use cuprate_rpc_types::bin::BinResponse as BResp;
-        use cuprate_rpc_types::json::JsonRpcRequest as JReq;
-        use cuprate_rpc_types::json::JsonRpcResponse as JResp;
-        use cuprate_rpc_types::other::OtherRequest as OReq;
-        use cuprate_rpc_types::other::OtherResponse as OResp;
+    fn call(&mut self, req: JsonRpcRequest) -> Self::Future {
+        use cuprate_rpc_types::json::JsonRpcRequest as Req;
+        use cuprate_rpc_types::json::JsonRpcResponse as Resp;
 
-        #[rustfmt::skip]
         #[allow(clippy::default_trait_access)]
         let resp = match req {
-            RpcRequest::JsonRpc(j) => RpcResponse::JsonRpc(cuprate_json_rpc::Response::ok(Id::Null, match j.body {
-                JReq::GetBlockCount(_) => JResp::GetBlockCount(Default::default()),
-                JReq::OnGetBlockHash(_) => JResp::OnGetBlockHash(Default::default()),
-                JReq::SubmitBlock(_) => JResp::SubmitBlock(Default::default()),
-                JReq::GenerateBlocks(_) => JResp::GenerateBlocks(Default::default()),
-                JReq::GetLastBlockHeader(_) => JResp::GetLastBlockHeader(Default::default()),
-                JReq::GetBlockHeaderByHash(_) => JResp::GetBlockHeaderByHash(Default::default()),
-                JReq::GetBlockHeaderByHeight(_) => JResp::GetBlockHeaderByHeight(Default::default()),
-                JReq::GetBlockHeadersRange(_) => JResp::GetBlockHeadersRange(Default::default()),
-                JReq::GetBlock(_) => JResp::GetBlock(Default::default()),
-                JReq::GetConnections(_) => JResp::GetConnections(Default::default()),
-                JReq::GetInfo(_) => JResp::GetInfo(Default::default()),
-                JReq::HardForkInfo(_) => JResp::HardForkInfo(Default::default()),
-                JReq::SetBans(_) => JResp::SetBans(Default::default()),
-                JReq::GetBans(_) => JResp::GetBans(Default::default()),
-                JReq::Banned(_) => JResp::Banned(Default::default()),
-                JReq::FlushTransactionPool(_) => JResp::FlushTransactionPool(Default::default()),
-                JReq::GetOutputHistogram(_) => JResp::GetOutputHistogram(Default::default()),
-                JReq::GetCoinbaseTxSum(_) => JResp::GetCoinbaseTxSum(Default::default()),
-                JReq::GetVersion(_) => JResp::GetVersion(Default::default()),
-                JReq::GetFeeEstimate(_) => JResp::GetFeeEstimate(Default::default()),
-                JReq::GetAlternateChains(_) => JResp::GetAlternateChains(Default::default()),
-                JReq::RelayTx(_) => JResp::RelayTx(Default::default()),
-                JReq::SyncInfo(_) => JResp::SyncInfo(Default::default()),
-                JReq::GetTransactionPoolBacklog(_) => JResp::GetTransactionPoolBacklog(Default::default()),
-                JReq::GetMinerData(_) => JResp::GetMinerData(Default::default()),
-                JReq::PruneBlockchain(_) => JResp::PruneBlockchain(Default::default()),
-                JReq::CalcPow(_) => JResp::CalcPow(Default::default()),
-                JReq::FlushCache(_) => JResp::FlushCache(Default::default()),
-                JReq::AddAuxPow(_) => JResp::AddAuxPow(Default::default()),
-                JReq::GetTxIdsLoose(_) => JResp::GetTxIdsLoose(Default::default()),
-            })),
-            RpcRequest::Binary(b) => RpcResponse::Binary(match b {
-                BReq::GetBlocks(_) => BResp::GetBlocks(Default::default()),
-                BReq::GetBlocksByHeight(_) => BResp::GetBlocksByHeight(Default::default()),
-                BReq::GetHashes(_) => BResp::GetHashes(Default::default()),
-                BReq::GetOutputIndexes(_) => BResp::GetOutputIndexes(Default::default()),
-                BReq::GetOuts(_) => BResp::GetOuts(Default::default()),
-                BReq::GetTransactionPoolHashes(_) => BResp::GetTransactionPoolHashes(Default::default()),
-                BReq::GetOutputDistribution(_) => BResp::GetOutputDistribution(Default::default()),
-            }),
-            RpcRequest::Other(o) => RpcResponse::Other(match o {
-                OReq::GetHeight(_) => OResp::GetHeight(Default::default()),
-                OReq::GetTransactions(_) => OResp::GetTransactions(Default::default()),
-                OReq::GetAltBlocksHashes(_) => OResp::GetAltBlocksHashes(Default::default()),
-                OReq::IsKeyImageSpent(_) => OResp::IsKeyImageSpent(Default::default()),
-                OReq::SendRawTransaction(_) => OResp::SendRawTransaction(Default::default()),
-                OReq::StartMining(_) => OResp::StartMining(Default::default()),
-                OReq::StopMining(_) => OResp::StopMining(Default::default()),
-                OReq::MiningStatus(_) => OResp::MiningStatus(Default::default()),
-                OReq::SaveBc(_) => OResp::SaveBc(Default::default()),
-                OReq::GetPeerList(_) => OResp::GetPeerList(Default::default()),
-                OReq::SetLogHashRate(_) => OResp::SetLogHashRate(Default::default()),
-                OReq::SetLogLevel(_) => OResp::SetLogLevel(Default::default()),
-                OReq::SetLogCategories(_) => OResp::SetLogCategories(Default::default()),
-                OReq::SetBootstrapDaemon(_) => OResp::SetBootstrapDaemon(Default::default()),
-                OReq::GetTransactionPool(_) => OResp::GetTransactionPool(Default::default()),
-                OReq::GetTransactionPoolStats(_) => OResp::GetTransactionPoolStats(Default::default()),
-                OReq::StopDaemon(_) => OResp::StopDaemon(Default::default()),
-                OReq::GetLimit(_) => OResp::GetLimit(Default::default()),
-                OReq::SetLimit(_) => OResp::SetLimit(Default::default()),
-                OReq::OutPeers(_) => OResp::OutPeers(Default::default()),
-                OReq::InPeers(_) => OResp::InPeers(Default::default()),
-                OReq::GetNetStats(_) => OResp::GetNetStats(Default::default()),
-                OReq::GetOuts(_) => OResp::GetOuts(Default::default()),
-                OReq::Update(_) => OResp::Update(Default::default()),
-                OReq::PopBlocks(_) => OResp::PopBlocks(Default::default()),
-                OReq::GetTransactionPoolHashes(_) => OResp::GetTransactionPoolHashes(Default::default()),
-                OReq::GetPublicNodes(_) => OResp::GetPublicNodes(Default::default()),
-            })
+            Req::GetBlockCount(_) => Resp::GetBlockCount(Default::default()),
+            Req::OnGetBlockHash(_) => Resp::OnGetBlockHash(Default::default()),
+            Req::SubmitBlock(_) => Resp::SubmitBlock(Default::default()),
+            Req::GenerateBlocks(_) => Resp::GenerateBlocks(Default::default()),
+            Req::GetLastBlockHeader(_) => Resp::GetLastBlockHeader(Default::default()),
+            Req::GetBlockHeaderByHash(_) => Resp::GetBlockHeaderByHash(Default::default()),
+            Req::GetBlockHeaderByHeight(_) => Resp::GetBlockHeaderByHeight(Default::default()),
+            Req::GetBlockHeadersRange(_) => Resp::GetBlockHeadersRange(Default::default()),
+            Req::GetBlock(_) => Resp::GetBlock(Default::default()),
+            Req::GetConnections(_) => Resp::GetConnections(Default::default()),
+            Req::GetInfo(_) => Resp::GetInfo(Default::default()),
+            Req::HardForkInfo(_) => Resp::HardForkInfo(Default::default()),
+            Req::SetBans(_) => Resp::SetBans(Default::default()),
+            Req::GetBans(_) => Resp::GetBans(Default::default()),
+            Req::Banned(_) => Resp::Banned(Default::default()),
+            Req::FlushTransactionPool(_) => Resp::FlushTransactionPool(Default::default()),
+            Req::GetOutputHistogram(_) => Resp::GetOutputHistogram(Default::default()),
+            Req::GetCoinbaseTxSum(_) => Resp::GetCoinbaseTxSum(Default::default()),
+            Req::GetVersion(_) => Resp::GetVersion(Default::default()),
+            Req::GetFeeEstimate(_) => Resp::GetFeeEstimate(Default::default()),
+            Req::GetAlternateChains(_) => Resp::GetAlternateChains(Default::default()),
+            Req::RelayTx(_) => Resp::RelayTx(Default::default()),
+            Req::SyncInfo(_) => Resp::SyncInfo(Default::default()),
+            Req::GetTransactionPoolBacklog(_) => {
+                Resp::GetTransactionPoolBacklog(Default::default())
+            }
+            Req::GetMinerData(_) => Resp::GetMinerData(Default::default()),
+            Req::PruneBlockchain(_) => Resp::PruneBlockchain(Default::default()),
+            Req::CalcPow(_) => Resp::CalcPow(Default::default()),
+            Req::FlushCache(_) => Resp::FlushCache(Default::default()),
+            Req::AddAuxPow(_) => Resp::AddAuxPow(Default::default()),
+            Req::GetTxIdsLoose(_) => Resp::GetTxIdsLoose(Default::default()),
+        };
+
+        let (tx, rx) = channel();
+        drop(tx.send(Ok(resp)));
+        InfallibleOneshotReceiver::from(rx)
+    }
+}
+
+impl Service<BinRequest> for RpcHandlerDummy {
+    type Response = BinResponse;
+    type Error = RpcError;
+    type Future = InfallibleOneshotReceiver<Result<BinResponse, RpcError>>;
+
+    fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
+        Poll::Ready(Ok(()))
+    }
+
+    fn call(&mut self, req: BinRequest) -> Self::Future {
+        use cuprate_rpc_types::bin::BinRequest as Req;
+        use cuprate_rpc_types::bin::BinResponse as Resp;
+
+        #[allow(clippy::default_trait_access)]
+        let resp = match req {
+            Req::GetBlocks(_) => Resp::GetBlocks(Default::default()),
+            Req::GetBlocksByHeight(_) => Resp::GetBlocksByHeight(Default::default()),
+            Req::GetHashes(_) => Resp::GetHashes(Default::default()),
+            Req::GetOutputIndexes(_) => Resp::GetOutputIndexes(Default::default()),
+            Req::GetOuts(_) => Resp::GetOuts(Default::default()),
+            Req::GetTransactionPoolHashes(_) => Resp::GetTransactionPoolHashes(Default::default()),
+            Req::GetOutputDistribution(_) => Resp::GetOutputDistribution(Default::default()),
+        };
+
+        let (tx, rx) = channel();
+        drop(tx.send(Ok(resp)));
+        InfallibleOneshotReceiver::from(rx)
+    }
+}
+
+impl Service<OtherRequest> for RpcHandlerDummy {
+    type Response = OtherResponse;
+    type Error = RpcError;
+    type Future = InfallibleOneshotReceiver<Result<OtherResponse, RpcError>>;
+
+    fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
+        Poll::Ready(Ok(()))
+    }
+
+    fn call(&mut self, req: OtherRequest) -> Self::Future {
+        use cuprate_rpc_types::other::OtherRequest as Req;
+        use cuprate_rpc_types::other::OtherResponse as Resp;
+
+        #[allow(clippy::default_trait_access)]
+        let resp = match req {
+            Req::GetHeight(_) => Resp::GetHeight(Default::default()),
+            Req::GetTransactions(_) => Resp::GetTransactions(Default::default()),
+            Req::GetAltBlocksHashes(_) => Resp::GetAltBlocksHashes(Default::default()),
+            Req::IsKeyImageSpent(_) => Resp::IsKeyImageSpent(Default::default()),
+            Req::SendRawTransaction(_) => Resp::SendRawTransaction(Default::default()),
+            Req::StartMining(_) => Resp::StartMining(Default::default()),
+            Req::StopMining(_) => Resp::StopMining(Default::default()),
+            Req::MiningStatus(_) => Resp::MiningStatus(Default::default()),
+            Req::SaveBc(_) => Resp::SaveBc(Default::default()),
+            Req::GetPeerList(_) => Resp::GetPeerList(Default::default()),
+            Req::SetLogHashRate(_) => Resp::SetLogHashRate(Default::default()),
+            Req::SetLogLevel(_) => Resp::SetLogLevel(Default::default()),
+            Req::SetLogCategories(_) => Resp::SetLogCategories(Default::default()),
+            Req::SetBootstrapDaemon(_) => Resp::SetBootstrapDaemon(Default::default()),
+            Req::GetTransactionPool(_) => Resp::GetTransactionPool(Default::default()),
+            Req::GetTransactionPoolStats(_) => Resp::GetTransactionPoolStats(Default::default()),
+            Req::StopDaemon(_) => Resp::StopDaemon(Default::default()),
+            Req::GetLimit(_) => Resp::GetLimit(Default::default()),
+            Req::SetLimit(_) => Resp::SetLimit(Default::default()),
+            Req::OutPeers(_) => Resp::OutPeers(Default::default()),
+            Req::InPeers(_) => Resp::InPeers(Default::default()),
+            Req::GetNetStats(_) => Resp::GetNetStats(Default::default()),
+            Req::GetOuts(_) => Resp::GetOuts(Default::default()),
+            Req::Update(_) => Resp::Update(Default::default()),
+            Req::PopBlocks(_) => Resp::PopBlocks(Default::default()),
+            Req::GetTransactionPoolHashes(_) => Resp::GetTransactionPoolHashes(Default::default()),
+            Req::GetPublicNodes(_) => Resp::GetPublicNodes(Default::default()),
         };
 
         let (tx, rx) = channel();
diff --git a/rpc/interface/src/rpc_request.rs b/rpc/interface/src/rpc_request.rs
deleted file mode 100644
index 3b66a780..00000000
--- a/rpc/interface/src/rpc_request.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-//! RPC requests.
-
-//---------------------------------------------------------------------------------------------------- Import
-#[cfg(feature = "serde")]
-use serde::{Deserialize, Serialize};
-
-use cuprate_rpc_types::{bin::BinRequest, json::JsonRpcRequest, other::OtherRequest};
-
-//---------------------------------------------------------------------------------------------------- RpcRequest
-/// All possible RPC requests.
-///
-/// This enum encapsulates all possible RPC requests:
-/// - JSON RPC 2.0 requests
-/// - Binary requests
-/// - Other JSON requests
-///
-/// It is the `Request` type required to be used in an [`RpcHandler`](crate::RpcHandler).
-#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
-pub enum RpcRequest {
-    /// JSON-RPC 2.0 requests.
-    JsonRpc(cuprate_json_rpc::Request<JsonRpcRequest>),
-    /// Binary requests.
-    Binary(BinRequest),
-    /// Other JSON requests.
-    Other(OtherRequest),
-}
-
-//---------------------------------------------------------------------------------------------------- Tests
-#[cfg(test)]
-mod test {
-    // use super::*;
-}
diff --git a/rpc/interface/src/rpc_response.rs b/rpc/interface/src/rpc_response.rs
deleted file mode 100644
index 7e8ecdbe..00000000
--- a/rpc/interface/src/rpc_response.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-//! RPC responses.
-
-//---------------------------------------------------------------------------------------------------- Import
-#[cfg(feature = "serde")]
-use serde::{Deserialize, Serialize};
-
-use cuprate_rpc_types::{bin::BinResponse, json::JsonRpcResponse, other::OtherResponse};
-
-//---------------------------------------------------------------------------------------------------- RpcResponse
-/// All possible RPC responses.
-///
-/// This enum encapsulates all possible RPC responses:
-/// - JSON RPC 2.0 responses
-/// - Binary responses
-/// - Other JSON responses
-///
-/// It is the `Response` type required to be used in an [`RpcHandler`](crate::RpcHandler).
-#[derive(Clone, PartialEq, Eq)]
-#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
-pub enum RpcResponse {
-    /// JSON RPC 2.0 responses.
-    JsonRpc(cuprate_json_rpc::Response<JsonRpcResponse>),
-    /// Binary responses.
-    Binary(BinResponse),
-    /// Other JSON responses.
-    Other(OtherResponse),
-}
-
-//---------------------------------------------------------------------------------------------------- Tests
-#[cfg(test)]
-mod test {
-    // use super::*;
-}
diff --git a/rpc/interface/src/rpc_service.rs b/rpc/interface/src/rpc_service.rs
new file mode 100644
index 00000000..db848307
--- /dev/null
+++ b/rpc/interface/src/rpc_service.rs
@@ -0,0 +1,52 @@
+//! RPC [`tower::Service`] trait.
+
+//---------------------------------------------------------------------------------------------------- Use
+use std::future::Future;
+
+use tower::Service;
+
+use crate::rpc_error::RpcError;
+
+//---------------------------------------------------------------------------------------------------- RpcService
+/// An RPC [`tower::Service`].
+///
+/// This trait solely exists to encapsulate the traits needed
+/// to handle RPC requests and respond with responses - **it is
+/// not meant to be used directly.**
+///
+/// The `Request` and `Response` are generic and
+/// are used in the [`tower::Service`] bounds.
+///
+/// The error type is always [`RpcError`].
+///
+/// There is a blanket implementation that implements this
+/// trait on types that implement `tower::Service` correctly.
+///
+/// See [`RpcHandler`](crate::RpcHandler) for more information.
+pub trait RpcService<Request, Response>:
+    Clone
+    + Send
+    + Sync
+    + 'static
+    + Service<
+        Request,
+        Response = Response,
+        Error = RpcError,
+        Future: Future<Output = Result<Response, RpcError>> + Send + Sync + 'static,
+    >
+{
+}
+
+impl<Request, Response, T> RpcService<Request, Response> for T where
+    Self: Clone
+        + Send
+        + Sync
+        + 'static
+        + Service<
+            Request,
+            Response = Response,
+            Error = RpcError,
+            Future: Future<Output = Result<Response, RpcError>> + Send + Sync + 'static,
+        >
+{
+}