From d4b30333bbd547fbf15d4e472773fde14d938a65 Mon Sep 17 00:00:00 2001
From: "hinto.janai" <hinto.janai@protonmail.com>
Date: Fri, 13 Dec 2024 16:43:39 -0500
Subject: [PATCH] most of `/send_raw_transaction`

---
 Cargo.lock                                    |   1 +
 binaries/cuprated/src/constants.rs            |   3 +
 binaries/cuprated/src/rpc/json.rs             |   7 +-
 binaries/cuprated/src/rpc/other.rs            | 144 ++++++++++++++++--
 .../cuprated/src/rpc/request/blockchain.rs    |  16 ++
 binaries/cuprated/src/rpc/request/txpool.rs   |  12 +-
 rpc/types/src/constants.rs                    |   3 +
 rpc/types/src/lib.rs                          |   2 +-
 rpc/types/src/misc/status.rs                  |  12 +-
 storage/blockchain/src/service/read.rs        |  15 +-
 types/types/Cargo.toml                        |   1 +
 types/types/src/blockchain.rs                 |   6 +
 types/types/src/lib.rs                        |   3 +-
 types/types/src/types.rs                      |  15 ++
 14 files changed, 220 insertions(+), 20 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 661e5f23..81fac819 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1142,6 +1142,7 @@ dependencies = [
 name = "cuprate-types"
 version = "0.0.0"
 dependencies = [
+ "bitflags 2.6.0",
  "bytes",
  "cfg-if",
  "cuprate-epee-encoding",
diff --git a/binaries/cuprated/src/constants.rs b/binaries/cuprated/src/constants.rs
index 057e8bd0..2685f663 100644
--- a/binaries/cuprated/src/constants.rs
+++ b/binaries/cuprated/src/constants.rs
@@ -18,6 +18,9 @@ pub const VERSION_BUILD: &str = if cfg!(debug_assertions) {
 pub const PANIC_CRITICAL_SERVICE_ERROR: &str =
     "A service critical to Cuprate's function returned an unexpected error.";
 
+/// The error message returned when an unsupported RPC call is requested.
+pub const UNSUPPORTED_RPC_CALL: &str = "This RPC call is not supported by Cuprate.";
+
 pub const EXAMPLE_CONFIG: &str = include_str!("../Cuprated.toml");
 
 #[cfg(test)]
diff --git a/binaries/cuprated/src/rpc/json.rs b/binaries/cuprated/src/rpc/json.rs
index e704ce0e..8dff6e76 100644
--- a/binaries/cuprated/src/rpc/json.rs
+++ b/binaries/cuprated/src/rpc/json.rs
@@ -58,7 +58,7 @@ use cuprate_types::{
 };
 
 use crate::{
-    constants::VERSION_BUILD,
+    constants::{UNSUPPORTED_RPC_CALL, VERSION_BUILD},
     rpc::{
         helper,
         request::{address_book, blockchain, blockchain_context, blockchain_manager, txpool},
@@ -122,9 +122,10 @@ pub(super) async fn map_request(
         Req::GetMinerData(r) => Resp::GetMinerData(get_miner_data(state, r).await?),
         Req::PruneBlockchain(r) => Resp::PruneBlockchain(prune_blockchain(state, r).await?),
         Req::CalcPow(r) => Resp::CalcPow(calc_pow(state, r).await?),
-        Req::FlushCache(r) => Resp::FlushCache(flush_cache(state, r).await?),
         Req::AddAuxPow(r) => Resp::AddAuxPow(add_aux_pow(state, r).await?),
-        Req::GetTxIdsLoose(r) => Resp::GetTxIdsLoose(get_tx_ids_loose(state, r).await?),
+
+        // Unsupported RPC calls.
+        Req::GetTxIdsLoose(_) | Req::FlushCache(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
     })
 }
 
diff --git a/binaries/cuprated/src/rpc/other.rs b/binaries/cuprated/src/rpc/other.rs
index 6ef0a75b..a88633c7 100644
--- a/binaries/cuprated/src/rpc/other.rs
+++ b/binaries/cuprated/src/rpc/other.rs
@@ -34,15 +34,16 @@ use cuprate_rpc_types::{
 };
 use cuprate_types::{
     rpc::{KeyImageSpentStatus, OutKey, PoolInfo, PoolTxInfo},
-    TxInPool,
+    TxInPool, TxRelayChecks,
 };
-use monero_serai::transaction::Transaction;
+use monero_serai::transaction::{Input, Transaction};
 
 use crate::{
-    rpc::CupratedRpcHandler,
+    constants::UNSUPPORTED_RPC_CALL,
     rpc::{
         helper,
         request::{blockchain, blockchain_context, blockchain_manager, txpool},
+        CupratedRpcHandler,
     },
 };
 
@@ -84,7 +85,6 @@ pub(super) async fn map_request(
         Req::InPeers(r) => Resp::InPeers(in_peers(state, r).await?),
         Req::GetNetStats(r) => Resp::GetNetStats(get_net_stats(state, r).await?),
         Req::GetOuts(r) => Resp::GetOuts(get_outs(state, r).await?),
-        Req::Update(r) => Resp::Update(update(state, r).await?),
         Req::PopBlocks(r) => Resp::PopBlocks(pop_blocks(state, r).await?),
         Req::GetTransactionPoolHashes(r) => {
             Resp::GetTransactionPoolHashes(get_transaction_pool_hashes(state, r).await?)
@@ -92,12 +92,11 @@ pub(super) async fn map_request(
         Req::GetPublicNodes(r) => Resp::GetPublicNodes(get_public_nodes(state, r).await?),
 
         // Unsupported requests.
-        Req::StartMining(_)
+        Req::Update(_)
+        | Req::StartMining(_)
         | Req::StopMining(_)
         | Req::MiningStatus(_)
-        | Req::SetLogHashRate(_) => {
-            return Err(anyhow!("Mining RPC calls are not supported by Cuprate"))
-        }
+        | Req::SetLogHashRate(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
     })
 }
 
@@ -329,13 +328,134 @@ async fn is_key_image_spent(
 
 /// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1307-L1411>
 async fn send_raw_transaction(
-    state: CupratedRpcHandler,
+    mut state: CupratedRpcHandler,
     request: SendRawTransactionRequest,
 ) -> Result<SendRawTransactionResponse, Error> {
-    Ok(SendRawTransactionResponse {
+    let mut resp = SendRawTransactionResponse {
         base: AccessResponseBase::OK,
-        ..todo!()
-    })
+        double_spend: false,
+        fee_too_low: false,
+        invalid_input: false,
+        invalid_output: false,
+        low_mixin: false,
+        nonzero_unlock_time: false,
+        not_relayed: request.do_not_relay,
+        overspend: false,
+        reason: String::new(),
+        sanity_check_failed: false,
+        too_big: false,
+        too_few_outputs: false,
+        tx_extra_too_big: false,
+    };
+
+    let tx = {
+        let blob = hex::decode(request.tx_as_hex)?;
+        Transaction::read(&mut blob.as_slice())?
+    };
+
+    if request.do_sanity_checks {
+        // FIXME: these checks could be defined elsewhere.
+        //
+        // <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/cryptonote_core/tx_sanity_check.cpp#L42>
+        fn tx_sanity_check(tx: &Transaction, rct_outs_available: u64) -> Result<(), &'static str> {
+            let Some(input) = tx.prefix().inputs.get(0) else {
+                return Err("No inputs");
+            };
+
+            let mut rct_indices = BTreeSet::new();
+            let n_indices: usize = 0;
+
+            for input in tx.prefix().inputs {
+                match input {
+                    Input::Gen(_) => return Err("Transaction is coinbase"),
+                    Input::ToKey {
+                        amount,
+                        key_offsets,
+                        key_image,
+                    } => {
+                        let Some(amount) = amount else {
+                            continue;
+                        };
+
+                        n_indices += key_offsets.len();
+                        let absolute = todo!();
+                        rct_indices.extend(absolute);
+                    }
+                }
+            }
+
+            if n_indices <= 10 {
+                return Ok(());
+            }
+
+            if rct_outs_available < 10_000 {
+                return Ok(());
+            }
+
+            let rct_indices_len = rct_indices.len();
+            if rct_indices_len < n_indices * 8 / 10 {
+                return Err("amount of unique indices is too low (amount of rct indices is {rct_indices_len} out of total {n_indices} indices.");
+            }
+
+            let offsets = Vec::with_capacity(rct_indices_len);
+            let median = todo!();
+            if median < rct_outs_available * 6 / 10 {
+                return Err("median offset index is too low (median is {median} out of total {rct_outs_available} offsets). Transactions should contain a higher fraction of recent outputs.");
+            }
+
+            Ok(())
+        }
+
+        let rct_outs_available = blockchain::total_rct_outputs(&mut state.blockchain_read).await?;
+
+        if let Err(e) = tx_sanity_check(&tx, rct_outs_available) {
+            resp.base.response_base.status = Status::Failed;
+            resp.reason.push_str(&format!("Sanity check failed: {e}"));
+            resp.sanity_check_failed = true;
+            return Ok(resp);
+        }
+    }
+
+    let tx_relay_checks =
+        txpool::check_maybe_relay_local(&mut state.txpool_manager, tx, !request.do_not_relay)
+            .await?;
+
+    if tx_relay_checks.is_empty() {
+        return Ok(resp);
+    }
+
+    // <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L124>
+    fn add_reason(reasons: &mut String, reason: &'static str) {
+        if !reasons.is_empty() {
+            reasons.push_str(", ");
+        }
+        reasons.push_str(reason);
+    }
+
+    let mut reasons = String::new();
+
+    #[rustfmt::skip]
+    let array = [
+        (&mut resp.double_spend, TxRelayChecks::DOUBLE_SPEND, "double spend"),
+        (&mut resp.fee_too_low, TxRelayChecks::FEE_TOO_LOW, "fee too low"),
+        (&mut resp.invalid_input, TxRelayChecks::INVALID_INPUT, "invalid input"),
+        (&mut resp.invalid_output, TxRelayChecks::INVALID_OUTPUT, "invalid output"),
+        (&mut resp.low_mixin, TxRelayChecks::LOW_MIXIN, "bad ring size"),
+        (&mut resp.nonzero_unlock_time, TxRelayChecks::NONZERO_UNLOCK_TIME, "tx unlock time is not zero"),
+        (&mut resp.overspend, TxRelayChecks::OVERSPEND, "overspend"),
+        (&mut resp.too_big, TxRelayChecks::TOO_BIG, "too big"),
+        (&mut resp.too_few_outputs, TxRelayChecks::TOO_FEW_OUTPUTS, "too few outputs"),
+        (&mut resp.tx_extra_too_big, TxRelayChecks::TX_EXTRA_TOO_BIG, "tx-extra too big"),
+    ];
+
+    for (field, flag, reason) in array {
+        if tx_relay_checks.contains(flag) {
+            *field = true;
+            add_reason(&mut reasons, reason);
+        }
+    }
+
+    Ok(resp)
 }
 
 /// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1413-L1462>
diff --git a/binaries/cuprated/src/rpc/request/blockchain.rs b/binaries/cuprated/src/rpc/request/blockchain.rs
index 1c0f4c07..e5c470f9 100644
--- a/binaries/cuprated/src/rpc/request/blockchain.rs
+++ b/binaries/cuprated/src/rpc/request/blockchain.rs
@@ -394,3 +394,19 @@ pub(crate) async fn transactions(
 
     Ok((txs, missed_txs))
 }
+
+/// [`BlockchainReadRequest::TotalRctOutputs`].
+pub(crate) async fn total_rct_outputs(
+    blockchain_read: &mut BlockchainReadHandle,
+) -> Result<u64, Error> {
+    let BlockchainResponse::TotalRctOutputs(total_rct_outputs) = blockchain_read
+        .ready()
+        .await?
+        .call(BlockchainReadRequest::TotalRctOutputs)
+        .await?
+    else {
+        unreachable!();
+    };
+
+    Ok(total_rct_outputs)
+}
diff --git a/binaries/cuprated/src/rpc/request/txpool.rs b/binaries/cuprated/src/rpc/request/txpool.rs
index f6ff2793..95d49ef1 100644
--- a/binaries/cuprated/src/rpc/request/txpool.rs
+++ b/binaries/cuprated/src/rpc/request/txpool.rs
@@ -3,6 +3,7 @@
 use std::{convert::Infallible, num::NonZero};
 
 use anyhow::{anyhow, Error};
+use monero_serai::transaction::Transaction;
 use tower::{Service, ServiceExt};
 
 use cuprate_helper::cast::usize_to_u64;
@@ -15,7 +16,7 @@ use cuprate_txpool::{
 };
 use cuprate_types::{
     rpc::{PoolInfo, PoolInfoFull, PoolInfoIncremental, PoolTxInfo},
-    TxInPool,
+    TxInPool, TxRelayChecks,
 };
 
 // FIXME: use `anyhow::Error` over `tower::BoxError` in txpool.
@@ -145,3 +146,12 @@ pub(crate) async fn relay(
     todo!();
     Ok(())
 }
+
+/// TODO
+pub(crate) async fn check_maybe_relay_local(
+    txpool_manager: &mut Infallible,
+    tx: Transaction,
+    relay: bool,
+) -> Result<TxRelayChecks, Error> {
+    Ok(todo!())
+}
diff --git a/rpc/types/src/constants.rs b/rpc/types/src/constants.rs
index a6a68c32..ce601d79 100644
--- a/rpc/types/src/constants.rs
+++ b/rpc/types/src/constants.rs
@@ -36,6 +36,9 @@ pub const CORE_RPC_STATUS_NOT_MINING: &str = "NOT MINING";
 #[doc = monero_definition_link!("cc73fe71162d564ffda8e549b79a350bca53c454", "/rpc/core_rpc_server_commands_defs.h", 81)]
 pub const CORE_RPC_STATUS_PAYMENT_REQUIRED: &str = "PAYMENT REQUIRED";
 
+/// Not defined in `monerod` although used frequently.
+pub const CORE_RPC_STATUS_FAILED: &str = "Failed";
+
 //---------------------------------------------------------------------------------------------------- Versions
 #[doc = monero_definition_link!("cc73fe71162d564ffda8e549b79a350bca53c454", "/rpc/core_rpc_server_commands_defs.h", 90)]
 /// RPC major version.
diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs
index 353ca001..ea2077aa 100644
--- a/rpc/types/src/lib.rs
+++ b/rpc/types/src/lib.rs
@@ -24,7 +24,7 @@ pub mod misc;
 pub mod other;
 
 pub use constants::{
-    CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
+    CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_FAILED, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
     CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_VERSION, CORE_RPC_VERSION_MAJOR,
     CORE_RPC_VERSION_MINOR,
 };
diff --git a/rpc/types/src/misc/status.rs b/rpc/types/src/misc/status.rs
index 79725cff..297addee 100644
--- a/rpc/types/src/misc/status.rs
+++ b/rpc/types/src/misc/status.rs
@@ -13,7 +13,7 @@ use cuprate_epee_encoding::{
 };
 
 use crate::constants::{
-    CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
+    CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_FAILED, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
     CORE_RPC_STATUS_PAYMENT_REQUIRED,
 };
 
@@ -40,24 +40,28 @@ use crate::constants::{
 /// let other = Status::Other("OTHER".into());
 ///
 /// assert_eq!(to_string(&Status::Ok).unwrap(),              r#""OK""#);
+/// assert_eq!(to_string(&Status::Failed).unwrap(),          r#""Failed""#);
 /// assert_eq!(to_string(&Status::Busy).unwrap(),            r#""BUSY""#);
 /// assert_eq!(to_string(&Status::NotMining).unwrap(),       r#""NOT MINING""#);
 /// assert_eq!(to_string(&Status::PaymentRequired).unwrap(), r#""PAYMENT REQUIRED""#);
 /// assert_eq!(to_string(&other).unwrap(),                   r#""OTHER""#);
 ///
 /// assert_eq!(Status::Ok.as_ref(),              CORE_RPC_STATUS_OK);
+/// assert_eq!(Status::Failed.as_ref(),          CORE_RPC_STATUS_FAILED);
 /// assert_eq!(Status::Busy.as_ref(),            CORE_RPC_STATUS_BUSY);
 /// assert_eq!(Status::NotMining.as_ref(),       CORE_RPC_STATUS_NOT_MINING);
 /// assert_eq!(Status::PaymentRequired.as_ref(), CORE_RPC_STATUS_PAYMENT_REQUIRED);
 /// assert_eq!(other.as_ref(),                   "OTHER");
 ///
 /// assert_eq!(format!("{}", Status::Ok),              CORE_RPC_STATUS_OK);
+/// assert_eq!(format!("{}", Status::Failed),          CORE_RPC_STATUS_FAILED);
 /// assert_eq!(format!("{}", Status::Busy),            CORE_RPC_STATUS_BUSY);
 /// assert_eq!(format!("{}", Status::NotMining),       CORE_RPC_STATUS_NOT_MINING);
 /// assert_eq!(format!("{}", Status::PaymentRequired), CORE_RPC_STATUS_PAYMENT_REQUIRED);
 /// assert_eq!(format!("{}", other),                   "OTHER");
 ///
 /// assert_eq!(format!("{:?}", Status::Ok),              "Ok");
+/// assert_eq!(format!("{:?}", Status::Failed),          "Failed");
 /// assert_eq!(format!("{:?}", Status::Busy),            "Busy");
 /// assert_eq!(format!("{:?}", Status::NotMining),       "NotMining");
 /// assert_eq!(format!("{:?}", Status::PaymentRequired), "PaymentRequired");
@@ -74,6 +78,10 @@ pub enum Status {
     #[default]
     Ok,
 
+    /// Generic request failure.
+    #[cfg_attr(feature = "serde", serde(rename = "Failed"))]
+    Failed,
+
     /// The daemon is busy, try later; [`CORE_RPC_STATUS_BUSY`].
     #[cfg_attr(feature = "serde", serde(rename = "BUSY"))]
     Busy,
@@ -101,6 +109,7 @@ impl From<String> for Status {
             CORE_RPC_STATUS_BUSY => Self::Busy,
             CORE_RPC_STATUS_NOT_MINING => Self::NotMining,
             CORE_RPC_STATUS_PAYMENT_REQUIRED => Self::PaymentRequired,
+            CORE_RPC_STATUS_FAILED => Self::Failed,
             _ => Self::Other(s),
         }
     }
@@ -110,6 +119,7 @@ impl AsRef<str> for Status {
     fn as_ref(&self) -> &str {
         match self {
             Self::Ok => CORE_RPC_STATUS_OK,
+            Self::Failed => CORE_RPC_STATUS_FAILED,
             Self::Busy => CORE_RPC_STATUS_BUSY,
             Self::NotMining => CORE_RPC_STATUS_NOT_MINING,
             Self::PaymentRequired => CORE_RPC_STATUS_PAYMENT_REQUIRED,
diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs
index 3efbb9a2..831dca23 100644
--- a/storage/blockchain/src/service/read.rs
+++ b/storage/blockchain/src/service/read.rs
@@ -51,7 +51,9 @@ use crate::{
         free::{compact_history_genesis_not_included, compact_history_index_to_height_offset},
         types::{BlockchainReadHandle, ResponseResult},
     },
-    tables::{AltBlockHeights, BlockHeights, BlockInfos, OpenTables, Tables, TablesIter},
+    tables::{
+        AltBlockHeights, BlockHeights, BlockInfos, OpenTables, RctOutputs, Tables, TablesIter,
+    },
     types::{
         AltBlockHeight, Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId,
     },
@@ -135,6 +137,7 @@ fn map_request(
         R::AltChains => alt_chains(env),
         R::AltChainCount => alt_chain_count(env),
         R::Transactions { tx_hashes } => transactions(env, tx_hashes),
+        R::TotalRctOutputs => total_rct_outputs(env),
     }
 
     /* SOMEDAY: post-request handling, run some code for each request? */
@@ -779,3 +782,13 @@ fn transactions(env: &ConcreteEnv, tx_hashes: HashSet<[u8; 32]>) -> ResponseResu
         missed_txs: todo!(),
     })
 }
+
+/// [`BlockchainReadRequest::TotalRctOutputs`]
+fn total_rct_outputs(env: &ConcreteEnv) -> ResponseResult {
+    // Single-threaded, no `ThreadLocal` required.
+    let env_inner = env.env_inner();
+    let tx_ro = env_inner.tx_ro()?;
+    let len = env_inner.open_db_ro::<RctOutputs>(&tx_ro)?.len()?;
+
+    Ok(BlockchainResponse::TotalRctOutputs(len))
+}
diff --git a/types/types/Cargo.toml b/types/types/Cargo.toml
index 84901a3f..c2b316de 100644
--- a/types/types/Cargo.toml
+++ b/types/types/Cargo.toml
@@ -23,6 +23,7 @@ cuprate-helper        = { workspace = true, optional = true, features = ["cast"]
 cuprate-fixed-bytes   = { workspace = true, features = ["std", "serde"] }
 cuprate-hex           = { workspace = true, optional = true }
 
+bitflags         = { workspace = true }
 bytes            = { workspace = true }
 cfg-if           = { workspace = true }
 curve25519-dalek = { workspace = true }
diff --git a/types/types/src/blockchain.rs b/types/types/src/blockchain.rs
index 3ce1efbd..143a7c20 100644
--- a/types/types/src/blockchain.rs
+++ b/types/types/src/blockchain.rs
@@ -163,6 +163,9 @@ pub enum BlockchainReadRequest {
 
     /// TODO
     Transactions { tx_hashes: HashSet<[u8; 32]> },
+
+    /// TODO
+    TotalRctOutputs,
 }
 
 //---------------------------------------------------------------------------------------------------- WriteRequest
@@ -358,6 +361,9 @@ pub enum BlockchainResponse {
         missed_txs: Vec<[u8; 32]>,
     },
 
+    /// Response to [`BlockchainReadRequest::TotalRctOutputs`].
+    TotalRctOutputs(u64),
+
     //------------------------------------------------------ Writes
     /// A generic Ok response to indicate a request was successfully handled.
     ///
diff --git a/types/types/src/lib.rs b/types/types/src/lib.rs
index f4c7d034..c2ac90e0 100644
--- a/types/types/src/lib.rs
+++ b/types/types/src/lib.rs
@@ -25,7 +25,8 @@ pub use transaction_verification_data::{
 };
 pub use types::{
     AltBlockInformation, BlockTemplate, Chain, ChainId, ExtendedBlockHeader, OutputOnChain,
-    TxInBlockchain, TxInPool, TxsInBlock, VerifiedBlockInformation, VerifiedTransactionInformation,
+    TxInBlockchain, TxInPool, TxRelayChecks, TxsInBlock, VerifiedBlockInformation,
+    VerifiedTransactionInformation,
 };
 
 //---------------------------------------------------------------------------------------------------- Feature-gated
diff --git a/types/types/src/types.rs b/types/types/src/types.rs
index 3f899306..8291a252 100644
--- a/types/types/src/types.rs
+++ b/types/types/src/types.rs
@@ -191,6 +191,21 @@ pub struct TxInPool {
     pub relayed: bool,
 }
 
+bitflags::bitflags! {
+    pub struct TxRelayChecks: u16 {
+        const DOUBLE_SPEND        = 1;
+        const FEE_TOO_LOW         = 1 << 1;
+        const INVALID_INPUT       = 1 << 2;
+        const INVALID_OUTPUT      = 1 << 3;
+        const LOW_MIXIN           = 1 << 4;
+        const NONZERO_UNLOCK_TIME = 1 << 5;
+        const OVERSPEND           = 1 << 6;
+        const TOO_BIG             = 1 << 7;
+        const TOO_FEW_OUTPUTS     = 1 << 8;
+        const TX_EXTRA_TOO_BIG    = 1 << 9;
+    }
+}
+
 //---------------------------------------------------------------------------------------------------- Tests
 #[cfg(test)]
 mod test {