diff --git a/consensus/src/bin/scan_chain.rs b/consensus/src/bin/scan_chain.rs
index 0502e7bb..6d1f051f 100644
--- a/consensus/src/bin/scan_chain.rs
+++ b/consensus/src/bin/scan_chain.rs
@@ -1,17 +1,11 @@
 #![cfg(feature = "binaries")]
 
-use futures::Sink;
-use std::collections::HashMap;
-use std::fmt::{Display, Formatter};
-use std::io::Read;
 use std::ops::Range;
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 use std::time::Duration;
 
-use rayon::prelude::*;
 use tower::{Service, ServiceExt};
-use tracing::instrument;
 use tracing::level_filters::LevelFilter;
 
 use cuprate_common::Network;
@@ -53,7 +47,8 @@ where
 {
     tracing::info!("Beginning chain scan");
 
-    let chain_height = 3_000_000;
+    // TODO: when we implement all rules use the RPCs chain height, for now we don't check v2 txs.
+    let chain_height = 1288616;
 
     tracing::info!("scanning to chain height: {}", chain_height);
 
@@ -79,10 +74,6 @@ where
     let mut current_height = start_height;
     let mut next_batch_start_height = start_height + batch_size;
 
-    let mut time_to_verify_last_batch: u128 = 0;
-
-    let mut batches_till_check_batch_size: u64 = 2;
-
     while next_batch_start_height < chain_height {
         let next_batch_size = rpc_config.read().unwrap().block_batch_size();
 
@@ -96,56 +87,10 @@ where
             )),
         );
 
-        let (DatabaseResponse::BlockBatchInRange(blocks), time_to_retrieve_batch) =
-            current_fut.await??
-        else {
+        let (DatabaseResponse::BlockBatchInRange(blocks), _) = current_fut.await?? else {
             panic!("Database sent incorrect response!");
         };
 
-        let time_to_verify_batch = std::time::Instant::now();
-
-        let time_to_retrieve_batch = time_to_retrieve_batch.as_millis();
-        /*
-               if time_to_retrieve_batch > time_to_verify_last_batch + 2000
-                   && batches_till_check_batch_size == 0
-               {
-                   batches_till_check_batch_size = 3;
-
-                   let mut conf = rpc_config.write().unwrap();
-                   tracing::info!(
-                       "Decreasing batch size time to verify last batch: {}, time_to_retrieve_batch: {}",
-                       time_to_verify_last_batch,
-                       time_to_retrieve_batch
-                   );
-                   conf.max_blocks_per_node = (conf.max_blocks_per_node
-                       * time_to_verify_last_batch as u64
-                       / (time_to_retrieve_batch as u64))
-                       .max(10_u64)
-                       .min(MAX_BLOCKS_IN_RANGE);
-                   tracing::info!("Decreasing batch size to: {}", conf.max_blocks_per_node);
-               } else if time_to_retrieve_batch + 2000 < time_to_verify_last_batch
-                   && batches_till_check_batch_size == 0
-               {
-                   batches_till_check_batch_size = 3;
-
-                   let mut conf = rpc_config.write().unwrap();
-                   tracing::info!(
-                       "Increasing batch size time to verify last batch: {}, time_to_retrieve_batch: {}",
-                       time_to_verify_last_batch,
-                       time_to_retrieve_batch
-                   );
-                   conf.max_blocks_per_node = (conf.max_blocks_per_node
-                       * (time_to_verify_last_batch as u64)
-                       / time_to_retrieve_batch.max(1) as u64)
-                       .max(30_u64)
-                       .min(MAX_BLOCKS_IN_RANGE);
-                   tracing::info!("Increasing batch size to: {}", conf.max_blocks_per_node);
-               } else {
-                   batches_till_check_batch_size = batches_till_check_batch_size.saturating_sub(1);
-               }
-
-        */
-
         tracing::info!(
             "Handling batch: {:?}, chain height: {}",
             current_height..(current_height + blocks.len() as u64),
@@ -190,8 +135,6 @@ where
                 cache.write().unwrap().save(&save_file)?;
             }
         }
-
-        time_to_verify_last_batch = time_to_verify_batch.elapsed().as_millis();
     }
 
     Ok(())
@@ -200,7 +143,7 @@ where
 #[tokio::main]
 async fn main() {
     tracing_subscriber::fmt()
-        .with_max_level(LevelFilter::DEBUG)
+        .with_max_level(LevelFilter::INFO)
         .init();
 
     let network = Network::Mainnet;
diff --git a/consensus/src/block.rs b/consensus/src/block.rs
index 98b929c4..ca15ed5d 100644
--- a/consensus/src/block.rs
+++ b/consensus/src/block.rs
@@ -132,6 +132,7 @@ where
             .oneshot(VerifyTxRequest::BatchSetupVerifyBlock {
                 txs,
                 current_chain_height: context.chain_height,
+                time_for_time_lock: context.current_adjusted_timestamp_for_time_lock(),
                 hf: context.current_hard_fork,
             })
             .await?
diff --git a/consensus/src/context.rs b/consensus/src/context.rs
index 43f33270..5017c2e4 100644
--- a/consensus/src/context.rs
+++ b/consensus/src/context.rs
@@ -6,6 +6,7 @@
 //!
 
 use std::{
+    cmp::min,
     future::Future,
     ops::{Deref, DerefMut},
     pin::Pin,
@@ -17,7 +18,7 @@ use futures::FutureExt;
 use tokio::sync::RwLock;
 use tower::{Service, ServiceExt};
 
-use crate::{ConsensusError, Database, DatabaseRequest, DatabaseResponse};
+use crate::{helper::current_time, ConsensusError, Database, DatabaseRequest, DatabaseResponse};
 
 pub mod difficulty;
 mod hardforks;
@@ -27,7 +28,7 @@ pub use difficulty::DifficultyCacheConfig;
 pub use hardforks::{HardFork, HardForkConfig};
 pub use weight::BlockWeightsCacheConfig;
 
-const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: usize = 60;
+const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60;
 
 pub struct ContextConfig {
     hard_fork_cfg: HardForkConfig,
@@ -143,11 +144,10 @@ pub struct BlockChainContext {
     pub median_weight_for_block_reward: usize,
     /// The amount of coins minted already.
     pub already_generated_coins: u64,
-    /// Timestamp to use to check time locked outputs.
-    pub time_lock_timestamp: u64,
     /// The median timestamp over the last [`BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW`] blocks, will be None if there aren't
     /// [`BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW`] blocks.
     pub median_block_timestamp: Option<u64>,
+    top_block_timestamp: Option<u64>,
     /// The height of the chain.
     pub chain_height: u64,
     /// The top blocks hash
@@ -157,6 +157,29 @@ pub struct BlockChainContext {
 }
 
 impl BlockChainContext {
+    /// Returns the timestamp the should be used when checking locked outputs.
+    ///
+    /// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time
+    pub fn current_adjusted_timestamp_for_time_lock(&self) -> u64 {
+        if self.current_hard_fork < HardFork::V13 || self.median_block_timestamp.is_none() {
+            current_time()
+        } else {
+            // This is safe as we just checked if this was None.
+            let median = self.median_block_timestamp.unwrap();
+
+            let adjusted_median = median
+                + (BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW + 1)
+                    * self.current_hard_fork.block_time().as_secs()
+                    / 2;
+
+            // This is safe as we just checked if the median was None and this will only be none for genesis and the first block.
+            let adjusted_top_block =
+                self.top_block_timestamp.unwrap() + self.current_hard_fork.block_time().as_secs();
+
+            min(adjusted_median, adjusted_top_block)
+        }
+    }
+
     pub fn block_blob_size_limit(&self) -> usize {
         self.effective_median_weight * 2 - 600
     }
@@ -227,9 +250,9 @@ impl Service<BlockChainContextRequest> for BlockChainContextService {
                 median_long_term_weight: weight_cache.median_long_term_weight(),
                 median_weight_for_block_reward: weight_cache.median_for_block_reward(&current_hf),
                 already_generated_coins: *already_generated_coins,
-                time_lock_timestamp: 0, //TODO:
+                top_block_timestamp: difficulty_cache.top_block_timestamp(),
                 median_block_timestamp: difficulty_cache
-                    .median_timestamp(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW),
+                    .median_timestamp(usize::try_from(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW).unwrap()),
                 chain_height: *chain_height,
                 top_hash: *top_block_hash,
                 current_hard_fork: current_hf,
diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs
index b7e6ba5f..c08d6e57 100644
--- a/consensus/src/lib.rs
+++ b/consensus/src/lib.rs
@@ -31,18 +31,22 @@ where
     D: Database + Clone + Send + Sync + 'static,
     D::Future: Send + 'static,
 {
-    let (context_svc, context_svc_updater) = context::initialize_blockchain_context(cfg, database.clone()).await?;
+    let (context_svc, context_svc_updater) =
+        context::initialize_blockchain_context(cfg, database.clone()).await?;
     let tx_svc = transactions::TxVerifierService::new(database);
     let block_svc = block::BlockVerifierService::new(context_svc.clone(), tx_svc.clone());
     Ok((block_svc, tx_svc, context_svc_updater))
 }
 
+// TODO: split this enum up.
 #[derive(Debug, thiserror::Error)]
 pub enum ConsensusError {
     #[error("Miner transaction invalid: {0}")]
     MinerTransaction(&'static str),
     #[error("Transaction sig invalid: {0}")]
     TransactionSignatureInvalid(&'static str),
+    #[error("Transaction has too high output amount")]
+    TransactionOutputsTooMuch,
     #[error("Transaction inputs overflow")]
     TransactionInputsOverflow,
     #[error("Transaction outputs overflow")]
@@ -111,6 +115,8 @@ pub enum DatabaseRequest {
 
     Outputs(HashMap<u64, HashSet<u64>>),
     NumberOutputsWithAmount(u64),
+    
+    CheckKIsNotSpent(HashSet<[u8; 32]>),
 
     #[cfg(feature = "binaries")]
     BlockBatchInRange(std::ops::Range<u64>),
@@ -129,6 +135,8 @@ pub enum DatabaseResponse {
     Outputs(HashMap<u64, HashMap<u64, OutputOnChain>>),
     NumberOutputsWithAmount(usize),
 
+    CheckKIsNotSpent(bool),
+
     #[cfg(feature = "binaries")]
     BlockBatchInRange(
         Vec<(
diff --git a/consensus/src/rpc.rs b/consensus/src/rpc.rs
index 9d1654d5..b593e225 100644
--- a/consensus/src/rpc.rs
+++ b/consensus/src/rpc.rs
@@ -295,7 +295,13 @@ impl<R: RpcConnection + Send + Sync + 'static> tower::Service<DatabaseRequest> f
             }
             .instrument(span)
             .boxed(),
-
+            DatabaseRequest::CheckKIsNotSpent(kis) => async move {
+                Ok(DatabaseResponse::CheckKIsNotSpent(
+                    cache.read().unwrap().are_kis_spent(kis),
+                ))
+            }
+            .instrument(span)
+            .boxed(),
             DatabaseRequest::GeneratedCoins => async move {
                 Ok(DatabaseResponse::GeneratedCoins(
                     cache.read().unwrap().already_generated_coins,
diff --git a/consensus/src/rpc/cache.rs b/consensus/src/rpc/cache.rs
index ff2321df..d44b4ce9 100644
--- a/consensus/src/rpc/cache.rs
+++ b/consensus/src/rpc/cache.rs
@@ -1,3 +1,4 @@
+use std::collections::HashSet;
 use std::io::Read;
 use std::path::Path;
 use std::{
@@ -7,7 +8,7 @@ use std::{
 };
 
 use bincode::{Decode, Encode};
-use monero_serai::transaction::{Timelock, Transaction};
+use monero_serai::transaction::{Input, Timelock, Transaction};
 use tracing_subscriber::fmt::MakeWriter;
 
 use cuprate_common::Network;
@@ -24,6 +25,7 @@ pub struct ScanningCache {
     //    network: u8,
     numb_outs: HashMap<u64, u64>,
     time_locked_out: HashMap<[u8; 32], u64>,
+    kis: HashSet<[u8; 32]>,
     pub already_generated_coins: u64,
     /// The height of the *next* block to scan.
     pub height: u64,
@@ -67,12 +69,23 @@ impl ScanningCache {
                 .outputs
                 .iter()
                 .for_each(|out| self.add_outs(out.amount.unwrap_or(0), 1));
+
+            tx.tx.prefix.inputs.iter().for_each(|inp| match inp {
+                Input::ToKey { key_image, .. } => {
+                    assert!(self.kis.insert(key_image.compress().to_bytes()))
+                }
+                _ => unreachable!(),
+            })
         });
 
         self.already_generated_coins = self.already_generated_coins.saturating_add(generated_coins);
         self.height += 1;
     }
 
+    pub fn are_kis_spent(&self, kis: HashSet<[u8; 32]>) -> bool {
+        self.kis.is_disjoint(&kis)
+    }
+
     pub fn outputs_time_lock(&self, tx: &[u8; 32]) -> Timelock {
         let time_lock = self.time_locked_out.get(tx).copied().unwrap_or(0);
         match time_lock {
diff --git a/consensus/src/transactions.rs b/consensus/src/transactions.rs
index fe719387..c5790ef7 100644
--- a/consensus/src/transactions.rs
+++ b/consensus/src/transactions.rs
@@ -19,7 +19,7 @@ mod inputs;
 pub(crate) mod outputs;
 mod ring;
 mod sigs;
-//mod time_lock;
+mod time_lock;
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
 pub enum TxVersion {
@@ -71,6 +71,7 @@ pub enum VerifyTxRequest {
     Block {
         txs: Vec<Arc<TransactionVerificationData>>,
         current_chain_height: u64,
+        time_for_time_lock: u64,
         hf: HardFork,
     },
     /// Batches the setup of [`TransactionVerificationData`] and verifies the transactions
@@ -78,6 +79,7 @@ pub enum VerifyTxRequest {
     BatchSetupVerifyBlock {
         txs: Vec<Transaction>,
         current_chain_height: u64,
+        time_for_time_lock: u64,
         hf: HardFork,
     },
 }
@@ -123,14 +125,29 @@ where
             VerifyTxRequest::Block {
                 txs,
                 current_chain_height,
+                time_for_time_lock,
                 hf,
-            } => verify_transactions_for_block(database, txs, current_chain_height, hf).boxed(),
+            } => verify_transactions_for_block(
+                database,
+                txs,
+                current_chain_height,
+                time_for_time_lock,
+                hf,
+            )
+            .boxed(),
             VerifyTxRequest::BatchSetupVerifyBlock {
                 txs,
                 current_chain_height,
+                time_for_time_lock,
                 hf,
-            } => batch_setup_verify_transactions_for_block(database, txs, current_chain_height, hf)
-                .boxed(),
+            } => batch_setup_verify_transactions_for_block(
+                database,
+                txs,
+                current_chain_height,
+                time_for_time_lock,
+                hf,
+            )
+            .boxed(),
         }
     }
 }
@@ -166,6 +183,7 @@ async fn batch_setup_verify_transactions_for_block<D>(
     database: D,
     txs: Vec<Transaction>,
     current_chain_height: u64,
+    time_for_time_lock: u64,
     hf: HardFork,
 ) -> Result<VerifyTxResponse, ConsensusError>
 where
@@ -180,7 +198,14 @@ where
     .await
     .unwrap()?;
 
-    verify_transactions_for_block(database, txs.clone(), current_chain_height, hf).await?;
+    verify_transactions_for_block(
+        database,
+        txs.clone(),
+        current_chain_height,
+        time_for_time_lock,
+        hf,
+    )
+    .await?;
     Ok(VerifyTxResponse::BatchSetupOk(txs))
 }
 
@@ -189,6 +214,7 @@ async fn verify_transactions_for_block<D>(
     database: D,
     txs: Vec<Arc<TransactionVerificationData>>,
     current_chain_height: u64,
+    time_for_time_lock: u64,
     hf: HardFork,
 ) -> Result<VerifyTxResponse, ConsensusError>
 where
@@ -202,7 +228,13 @@ where
 
     tokio::task::spawn_blocking(move || {
         txs.par_iter().try_for_each(|tx| {
-            verify_transaction_for_block(tx, current_chain_height, hf, spent_kis.clone())
+            verify_transaction_for_block(
+                tx,
+                current_chain_height,
+                time_for_time_lock,
+                hf,
+                spent_kis.clone(),
+            )
         })
     });
 
@@ -212,10 +244,11 @@ where
 fn verify_transaction_for_block(
     tx_verification_data: &TransactionVerificationData,
     current_chain_height: u64,
+    time_for_time_lock: u64,
     hf: HardFork,
     spent_kis: Arc<std::sync::Mutex<HashSet<[u8; 32]>>>,
 ) -> Result<(), ConsensusError> {
-    tracing::trace!(
+    tracing::debug!(
         "Verifying transaction: {}",
         hex::encode(tx_verification_data.tx_hash)
     );
@@ -228,7 +261,14 @@ fn verify_transaction_for_block(
         None => panic!("rings_member_info needs to be set to be able to verify!"),
     };
 
-    check_tx_version(&rings_member_info.decoy_info, &tx_version, &hf)?;
+    check_tx_version(&rings_member_info.decoy_info, tx_version, &hf)?;
+
+    time_lock::check_all_time_locks(
+        &rings_member_info.time_locked_outs,
+        current_chain_height,
+        time_for_time_lock,
+        &hf,
+    )?;
 
     let sum_outputs =
         outputs::check_outputs(&tx_verification_data.tx.prefix.outputs, &hf, tx_version)?;
@@ -242,6 +282,15 @@ fn verify_transaction_for_block(
         spent_kis,
     )?;
 
+    if tx_version == &TxVersion::RingSignatures {
+        if sum_outputs >= sum_inputs {
+            return Err(ConsensusError::TransactionOutputsTooMuch);
+        }
+        // check that monero-serai is calculating the correct value here, why can't we just use this
+        // value? because we don't have this when we create the object.
+        assert_eq!(tx_verification_data.fee, sum_inputs - sum_outputs);
+    }
+
     sigs::verify_signatures(&tx_verification_data.tx, &rings_member_info.rings)?;
 
     Ok(())
diff --git a/consensus/src/transactions/inputs.rs b/consensus/src/transactions/inputs.rs
index e5912a14..79426c7d 100644
--- a/consensus/src/transactions/inputs.rs
+++ b/consensus/src/transactions/inputs.rs
@@ -1,11 +1,11 @@
-use std::{
-    cmp::{max, min, Ordering},
-    collections::HashSet,
-    sync::Arc,
-};
+//! # Inputs
+//!
+//! This module contains all consensus rules for non-miner transaction inputs, excluding time locks.
+//!
+
+use std::{cmp::Ordering, collections::HashSet, sync::Arc};
 
 use monero_serai::transaction::Input;
-use tower::{Service, ServiceExt};
 
 use crate::{
     transactions::{
@@ -69,6 +69,7 @@ pub(crate) fn check_key_images(
 ) -> Result<(), ConsensusError> {
     match input {
         Input::ToKey { key_image, .. } => {
+            // this happens in monero-serai but we may as well duplicate the check.
             if !key_image.is_torsion_free() {
                 return Err(ConsensusError::TransactionHasInvalidInput(
                     "key image has torsion",
@@ -120,6 +121,7 @@ fn check_input_has_decoys(input: &Input) -> Result<(), ConsensusError> {
 
 /// Checks that the ring members for the input are unique after hard-fork 6.
 ///
+/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#unique-ring-members
 fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), ConsensusError> {
     if hf >= &HardFork::V6 {
         match input {
@@ -139,6 +141,9 @@ fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), Consens
     }
 }
 
+/// Checks that from hf 7 the inputs are sorted by key image.
+///
+/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#sorted-inputs
 fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), ConsensusError> {
     let get_ki = |inp: &Input| match inp {
         Input::ToKey { key_image, .. } => key_image.compress().to_bytes(),
@@ -162,6 +167,9 @@ fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), ConsensusE
     }
 }
 
+/// Checks the youngest output is at least 10 blocks old.
+///
+/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#10-block-lock
 fn check_10_block_lock(
     ring_member_info: &TxRingMembersInfo,
     current_chain_height: u64,
@@ -170,7 +178,7 @@ fn check_10_block_lock(
     if hf >= &HardFork::V12 {
         if ring_member_info.youngest_used_out_height + 10 > current_chain_height {
             Err(ConsensusError::TransactionHasInvalidRing(
-                "tx has one ring member which is too younge",
+                "tx has one ring member which is too young",
             ))
         } else {
             Ok(())
@@ -203,6 +211,10 @@ fn sum_inputs_v1(inputs: &[Input]) -> Result<u64, ConsensusError> {
     Ok(sum)
 }
 
+/// Checks all input consensus rules.
+///
+/// TODO: list rules.
+///
 pub fn check_inputs(
     inputs: &[Input],
     ring_member_info: &TxRingMembersInfo,
@@ -219,6 +231,8 @@ pub fn check_inputs(
 
     if let Some(decoy_info) = &ring_member_info.decoy_info {
         check_decoy_info(decoy_info, hf)?;
+    } else {
+        assert_eq!(hf, &HardFork::V1);
     }
 
     for input in inputs {
diff --git a/consensus/src/transactions/outputs.rs b/consensus/src/transactions/outputs.rs
index dcdffb7f..67226da9 100644
--- a/consensus/src/transactions/outputs.rs
+++ b/consensus/src/transactions/outputs.rs
@@ -1,3 +1,7 @@
+//! # Outputs
+//!
+//! Consensus rules relating to non-miner transaction outputs
+
 use std::sync::OnceLock;
 
 use monero_serai::transaction::Output;
@@ -127,11 +131,11 @@ pub fn check_outputs(
     hf: &HardFork,
     tx_version: &TxVersion,
 ) -> Result<u64, ConsensusError> {
-    check_output_types(outputs, &hf)?;
+    check_output_types(outputs, hf)?;
     check_output_keys(outputs)?;
 
     match tx_version {
-        TxVersion::RingSignatures => sum_outputs_v1(outputs, &hf),
+        TxVersion::RingSignatures => sum_outputs_v1(outputs, hf),
         _ => todo!("RingCT"),
     }
 }
diff --git a/consensus/src/transactions/ring.rs b/consensus/src/transactions/ring.rs
index bf2812cc..86b72c05 100644
--- a/consensus/src/transactions/ring.rs
+++ b/consensus/src/transactions/ring.rs
@@ -4,6 +4,8 @@
 //! ring members of inputs. This module does minimal consensus checks, only when needed, and should not be relied
 //! upon to do any.
 //!
+//! The data collected by this module can be used to perform consensus checks.
+//!
 
 use std::{
     cmp::{max, min},
@@ -13,8 +15,8 @@ use std::{
 
 use curve25519_dalek::EdwardsPoint;
 use monero_serai::{
-    ringct::{mlsag::RingMatrix, RctType},
-    transaction::{Input, Timelock, Transaction},
+    ringct::RctType,
+    transaction::{Input, Timelock},
 };
 use tower::ServiceExt;
 
@@ -23,168 +25,10 @@ use crate::{
     DatabaseResponse, HardFork, OutputOnChain,
 };
 
-/// Gets the absolute offsets from the relative offsets.
+/// Fills the `rings_member_info` field on the inputted [`TransactionVerificationData`].
 ///
-/// This function will return an error if the relative offsets are empty.
-/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#inputs-must-have-decoys
-fn get_absolute_offsets(relative_offsets: &[u64]) -> Result<Vec<u64>, ConsensusError> {
-    if relative_offsets.is_empty() {
-        return Err(ConsensusError::TransactionHasInvalidRing(
-            "ring has no members",
-        ));
-    }
-
-    let mut offsets = Vec::with_capacity(relative_offsets.len());
-    offsets.push(relative_offsets[0]);
-
-    for i in 1..relative_offsets.len() {
-        offsets.push(offsets[i - 1] + relative_offsets[i]);
-    }
-    Ok(offsets)
-}
-
-/// Inserts the outputs that are needed to verify the transaction inputs into the provided HashMap.
-///
-/// This will error if the inputs are empty
-/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#no-empty-inputs
-///
-pub fn insert_ring_member_ids(
-    inputs: &[Input],
-    output_ids: &mut HashMap<u64, HashSet<u64>>,
-) -> Result<(), ConsensusError> {
-    if inputs.is_empty() {
-        return Err(ConsensusError::TransactionHasInvalidInput(
-            "transaction has no inputs",
-        ));
-    }
-
-    for input in inputs {
-        match input {
-            Input::ToKey {
-                amount,
-                key_offsets,
-                ..
-            } => output_ids
-                .entry(amount.unwrap_or(0))
-                .or_insert_with(HashSet::new)
-                .extend(get_absolute_offsets(key_offsets)?),
-            // https://cuprate.github.io/monero-book/consensus_rules/transactions.html#input-type
-            _ => {
-                return Err(ConsensusError::TransactionHasInvalidInput(
-                    "input not ToKey",
-                ))
-            }
-        }
-    }
-    Ok(())
-}
-
-/// Represents the ring members of all the inputs.
-#[derive(Debug)]
-pub enum Rings {
-    /// Legacy, pre-ringCT, ring.
-    Legacy(Vec<Vec<EdwardsPoint>>),
-    /// TODO:
-    RingCT,
-}
-
-impl Rings {
-    /// Builds the rings for the transaction inputs, from the given outputs.
-    pub fn new(outputs: Vec<Vec<&OutputOnChain>>, rct_type: RctType) -> Rings {
-        match rct_type {
-            RctType::Null => Rings::Legacy(
-                outputs
-                    .into_iter()
-                    .map(|inp_outs| inp_outs.into_iter().map(|out| out.key).collect())
-                    .collect(),
-            ),
-            _ => todo!("RingCT"),
-        }
-    }
-}
-
-/// Information on the outputs the transaction is is referencing for inputs (ring members).
-#[derive(Debug)]
-pub struct TxRingMembersInfo {
-    pub rings: Rings,
-    /// Information on the structure of the decoys, will be [`None`] for txs before [`HardFork::V1`]
-    pub decoy_info: Option<DecoyInfo>,
-    pub youngest_used_out_height: u64,
-    pub time_locked_outs: Vec<Timelock>,
-}
-
-impl TxRingMembersInfo {
-    pub fn new(
-        used_outs: Vec<Vec<&OutputOnChain>>,
-        decoy_info: Option<DecoyInfo>,
-        rct_type: RctType,
-    ) -> TxRingMembersInfo {
-        TxRingMembersInfo {
-            youngest_used_out_height: used_outs
-                .iter()
-                .map(|inp_outs| {
-                    inp_outs
-                        .iter()
-                        .map(|out| out.height)
-                        .max()
-                        .expect("Input must have ring members")
-                })
-                .max()
-                .expect("Tx must have inputs"),
-            time_locked_outs: used_outs
-                .iter()
-                .flat_map(|inp_outs| {
-                    inp_outs
-                        .iter()
-                        .filter_map(|out| match out.time_lock {
-                            Timelock::None => None,
-                            lock => Some(lock),
-                        })
-                        .collect::<Vec<_>>()
-                })
-                .collect(),
-            rings: Rings::new(used_outs, rct_type),
-            decoy_info,
-        }
-    }
-}
-
-/// Get the ring members for the inputs from the outputs on the chain.
-fn get_ring_members_for_inputs<'a>(
-    outputs: &'a HashMap<u64, HashMap<u64, OutputOnChain>>,
-    inputs: &[Input],
-) -> Result<Vec<Vec<&'a OutputOnChain>>, ConsensusError> {
-    inputs
-        .iter()
-        .map(|inp| match inp {
-            Input::ToKey {
-                amount,
-                key_offsets,
-                ..
-            } => {
-                let offsets = get_absolute_offsets(key_offsets)?;
-                Ok(offsets
-                    .iter()
-                    .map(|offset| {
-                        // get the hashmap for this amount.
-                        outputs
-                            .get(&amount.unwrap_or(0))
-                            // get output at the index from the amount hashmap.
-                            .and_then(|amount_map| amount_map.get(offset))
-                            .ok_or(ConsensusError::TransactionHasInvalidRing(
-                                "ring member not in database",
-                            ))
-                    })
-                    .collect::<Result<_, ConsensusError>>()?)
-            }
-            _ => Err(ConsensusError::TransactionHasInvalidInput(
-                "input not ToKey",
-            )),
-        })
-        .collect::<Result<_, ConsensusError>>()
-}
-
-/// Fills the `rings_member_info` field on the inputted [`TransactionVerificationData`]
+/// This function batch gets all the ring members for the inputted transactions and fills in data about
+/// them, like the youngest used out and the time locks.
 pub async fn batch_fill_ring_member_info<D: Database + Clone + Send + Sync + 'static>(
     txs_verification_data: &[Arc<TransactionVerificationData>],
     hf: &HardFork,
@@ -230,6 +74,173 @@ pub async fn batch_fill_ring_member_info<D: Database + Clone + Send + Sync + 'st
     Ok(())
 }
 
+/// Gets the absolute offsets from the relative offsets.
+///
+/// This function will return an error if the relative offsets are empty.
+/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#inputs-must-have-decoys
+fn get_absolute_offsets(relative_offsets: &[u64]) -> Result<Vec<u64>, ConsensusError> {
+    if relative_offsets.is_empty() {
+        return Err(ConsensusError::TransactionHasInvalidRing(
+            "ring has no members",
+        ));
+    }
+
+    let mut offsets = Vec::with_capacity(relative_offsets.len());
+    offsets.push(relative_offsets[0]);
+
+    for i in 1..relative_offsets.len() {
+        offsets.push(offsets[i - 1] + relative_offsets[i]);
+    }
+    Ok(offsets)
+}
+
+/// Inserts the output IDs that are needed to verify the transaction inputs into the provided HashMap.
+///
+/// This will error if the inputs are empty
+/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#no-empty-inputs
+///
+fn insert_ring_member_ids(
+    inputs: &[Input],
+    output_ids: &mut HashMap<u64, HashSet<u64>>,
+) -> Result<(), ConsensusError> {
+    if inputs.is_empty() {
+        return Err(ConsensusError::TransactionHasInvalidInput(
+            "transaction has no inputs",
+        ));
+    }
+
+    for input in inputs {
+        match input {
+            Input::ToKey {
+                amount,
+                key_offsets,
+                ..
+            } => output_ids
+                .entry(amount.unwrap_or(0))
+                .or_insert_with(HashSet::new)
+                .extend(get_absolute_offsets(key_offsets)?),
+            // https://cuprate.github.io/monero-book/consensus_rules/transactions.html#input-type
+            _ => {
+                return Err(ConsensusError::TransactionHasInvalidInput(
+                    "input not ToKey",
+                ))
+            }
+        }
+    }
+    Ok(())
+}
+
+/// Represents the ring members of all the inputs.
+#[derive(Debug)]
+pub enum Rings {
+    /// Legacy, pre-ringCT, rings.
+    Legacy(Vec<Vec<EdwardsPoint>>),
+    /// TODO:
+    RingCT,
+}
+
+impl Rings {
+    /// Builds the rings for the transaction inputs, from the given outputs.
+    fn new(outputs: Vec<Vec<&OutputOnChain>>, rct_type: RctType) -> Rings {
+        match rct_type {
+            RctType::Null => Rings::Legacy(
+                outputs
+                    .into_iter()
+                    .map(|inp_outs| inp_outs.into_iter().map(|out| out.key).collect())
+                    .collect(),
+            ),
+            _ => todo!("RingCT"),
+        }
+    }
+}
+
+/// Information on the outputs the transaction is is referencing for inputs (ring members).
+#[derive(Debug)]
+pub struct TxRingMembersInfo {
+    pub rings: Rings,
+    /// Information on the structure of the decoys, will be [`None`] for txs before [`HardFork::V1`]
+    pub decoy_info: Option<DecoyInfo>,
+    pub youngest_used_out_height: u64,
+    pub time_locked_outs: Vec<Timelock>,
+}
+
+impl TxRingMembersInfo {
+    /// Construct a [`TxRingMembersInfo`] struct.
+    ///
+    /// The used outs must be all the ring members used in the transactions inputs.
+    fn new(
+        used_outs: Vec<Vec<&OutputOnChain>>,
+        decoy_info: Option<DecoyInfo>,
+        rct_type: RctType,
+    ) -> TxRingMembersInfo {
+        TxRingMembersInfo {
+            youngest_used_out_height: used_outs
+                .iter()
+                .map(|inp_outs| {
+                    inp_outs
+                        .iter()
+                        // the output with the highest height is the youngest
+                        .map(|out| out.height)
+                        .max()
+                        .expect("Input must have ring members")
+                })
+                .max()
+                .expect("Tx must have inputs"),
+            time_locked_outs: used_outs
+                .iter()
+                .flat_map(|inp_outs| {
+                    inp_outs
+                        .iter()
+                        .filter_map(|out| match out.time_lock {
+                            Timelock::None => None,
+                            lock => Some(lock),
+                        })
+                        .collect::<Vec<_>>()
+                })
+                .collect(),
+            rings: Rings::new(used_outs, rct_type),
+            decoy_info,
+        }
+    }
+}
+
+/// Get the ring members for the inputs from the outputs on the chain.
+///
+/// Will error if `outputs` does not contain the outputs needed.
+fn get_ring_members_for_inputs<'a>(
+    outputs: &'a HashMap<u64, HashMap<u64, OutputOnChain>>,
+    inputs: &[Input],
+) -> Result<Vec<Vec<&'a OutputOnChain>>, ConsensusError> {
+    inputs
+        .iter()
+        .map(|inp| match inp {
+            Input::ToKey {
+                amount,
+                key_offsets,
+                ..
+            } => {
+                let offsets = get_absolute_offsets(key_offsets)?;
+                Ok(offsets
+                    .iter()
+                    .map(|offset| {
+                        // get the hashmap for this amount.
+                        outputs
+                            .get(&amount.unwrap_or(0))
+                            // get output at the index from the amount hashmap.
+                            .and_then(|amount_map| amount_map.get(offset))
+                            .ok_or(ConsensusError::TransactionHasInvalidRing(
+                                "ring member not in database",
+                            ))
+                    })
+                    .collect::<Result<_, ConsensusError>>()?)
+            }
+            _ => Err(ConsensusError::TransactionHasInvalidInput(
+                "input not ToKey",
+            )),
+        })
+        .collect::<Result<_, ConsensusError>>()
+}
+
 /// A struct holding information about the inputs and their decoys.
 ///
 /// https://cuprate.github.io/monero-book/consensus_rules/transactions/decoys.html
diff --git a/consensus/src/transactions/time_lock.rs b/consensus/src/transactions/time_lock.rs
index 872dd6ec..8546842a 100644
--- a/consensus/src/transactions/time_lock.rs
+++ b/consensus/src/transactions/time_lock.rs
@@ -1,18 +1,45 @@
-use std::cmp::min;
-
+//! # Time Locks
+//!
+//! This module contains the checks for time locks, using the `check_all_time_locks` function.
+//!
 use monero_serai::transaction::Timelock;
 
-use crate::{context::difficulty::DifficultyCache, helper::current_time, HardFork};
+use crate::{ConsensusError, HardFork};
 
-const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60;
+/// Checks all the time locks are unlocked.
+///
+/// `current_time_lock_timestamp` must be: https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time
+///
+/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time
+pub fn check_all_time_locks(
+    time_locks: &[Timelock],
+    current_chain_height: u64,
+    current_time_lock_timestamp: u64,
+    hf: &HardFork,
+) -> Result<(), ConsensusError> {
+    time_locks.iter().try_for_each(|time_lock| {
+        if !output_unlocked(
+            time_lock,
+            current_chain_height,
+            current_time_lock_timestamp,
+            hf,
+        ) {
+            Err(ConsensusError::TransactionHasInvalidRing(
+                "One or more ring members locked",
+            ))
+        } else {
+            Ok(())
+        }
+    })
+}
 
 /// Checks if an outputs unlock time has passed.
 ///
 /// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time
-pub fn output_unlocked(
+fn output_unlocked(
     time_lock: &Timelock,
-    difficulty_cache: &DifficultyCache,
     current_chain_height: u64,
+    current_time_lock_timestamp: u64,
     hf: &HardFork,
 ) -> bool {
     match *time_lock {
@@ -21,7 +48,7 @@ pub fn output_unlocked(
             check_block_time_lock(unlock_height.try_into().unwrap(), current_chain_height)
         }
         Timelock::Time(unlock_time) => {
-            check_timestamp_time_lock(unlock_time, difficulty_cache, current_chain_height, hf)
+            check_timestamp_time_lock(unlock_time, current_time_lock_timestamp, hf)
         }
     }
 }
@@ -34,39 +61,14 @@ fn check_block_time_lock(unlock_height: u64, current_chain_height: u64) -> bool
     unlock_height >= current_chain_height
 }
 
-/// Returns the timestamp the should be used when checking locked outputs.
-///
-/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time
-fn get_current_timestamp(
-    difficulty_cache: &DifficultyCache,
-    current_chain_height: u64,
-    hf: &HardFork,
-) -> u64 {
-    if hf < &HardFork::V13 || current_chain_height < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW {
-        current_time()
-    } else {
-        let median = difficulty_cache
-            .median_timestamp(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW.try_into().unwrap());
-        let adjusted_median =
-            median + (BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW + 1) * hf.block_time().as_secs() / 2;
-
-        // This is safe as we just check we don't have less than 60 blocks in the chain.
-        let adjusted_top_block =
-            difficulty_cache.top_block_timestamp().unwrap() + hf.block_time().as_secs();
-
-        min(adjusted_median, adjusted_top_block)
-    }
-}
-
+/// ///
 /// Returns if a locked output, which uses a block height, can be spend.
 ///
 /// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#timestamp
 fn check_timestamp_time_lock(
     unlock_timestamp: u64,
-    difficulty_cache: &DifficultyCache,
-    current_chain_height: u64,
+    current_time_lock_timestamp: u64,
     hf: &HardFork,
 ) -> bool {
-    let timestamp = get_current_timestamp(difficulty_cache, current_chain_height, hf);
-    timestamp + hf.block_time().as_secs() >= unlock_timestamp
+    current_time_lock_timestamp + hf.block_time().as_secs() >= unlock_timestamp
 }
diff --git a/database/src/types.rs b/database/src/types.rs
index f7052d64..a4ca8b8c 100644
--- a/database/src/types.rs
+++ b/database/src/types.rs
@@ -57,12 +57,12 @@ pub struct AltBlock {
 // ---- TRANSACTIONS ----
 
 #[derive(Clone, Debug)]
-/// [`TransactionPruned`] is, as its name suggest, the pruned part of a transaction, which is the Transaction Prefix and its RingCT signatures.
+/// [`TransactionPruned`] is, as its name suggest, the pruned part of a transaction, which is the Transaction Prefix and its RingCT ring.
 /// This struct is used in the [`crate::table::txsprefix`] table.
 pub struct TransactionPruned {
     /// The transaction prefix.
     pub prefix: TransactionPrefix,
-    /// The RingCT signatures, will only contain the 'sig' field.
+    /// The RingCT ring, will only contain the 'sig' field.
     pub rct_signatures: RctSig,
 }
 
@@ -80,7 +80,7 @@ impl bincode::Decode for TransactionPruned {
 
         // Handle the prefix accordingly to its version
         match *prefix.version {
-            // First transaction format, Pre-RingCT, so the signatures are None
+            // First transaction format, Pre-RingCT, so the ring are None
             1 => Ok(TransactionPruned {
                 prefix,
                 rct_signatures: RctSig { sig: None, p: None },
@@ -94,7 +94,7 @@ impl bincode::Decode for TransactionPruned {
                         rct_signatures,
                     });
                 }
-                // Otherwise get the RingCT signatures for the tx inputs
+                // Otherwise get the RingCT ring for the tx inputs
                 if let Some(sig) = RctSigBase::consensus_decode(&mut r, inputs, outputs)
                     .map_err(|_| bincode::error::DecodeError::Other("Monero-rs decoding failed"))?
                 {
@@ -123,10 +123,10 @@ impl bincode::Encode for TransactionPruned {
         let buf = monero::consensus::serialize(&self.prefix);
         writer.write(&buf)?;
         match *self.prefix.version {
-            1 => {} // First transaction format, Pre-RingCT, so the there is no Rct signatures to add
+            1 => {} // First transaction format, Pre-RingCT, so the there is no Rct ring to add
             _ => {
                 if let Some(sig) = &self.rct_signatures.sig {
-                    // If there is signatures then we append it at the end
+                    // If there is ring then we append it at the end
                     let buf = monero::consensus::serialize(sig);
                     writer.write(&buf)?;
                 }