From d634bea297a7b65b276ab903b07bb5220191a52c Mon Sep 17 00:00:00 2001
From: "hinto.janai" <hinto.janai@protonmail.com>
Date: Fri, 15 Nov 2024 15:51:06 -0500
Subject: [PATCH] `get_output_distribution`

---
 binaries/cuprated/src/rpc/handler.rs   |  2 +-
 binaries/cuprated/src/rpc/json.rs      | 49 ++++++++++++++++++++++----
 rpc/interface/src/route/bin.rs         |  2 +-
 rpc/interface/src/route/json_rpc.rs    |  2 +-
 rpc/interface/src/route/other.rs       |  2 +-
 rpc/interface/src/rpc_handler.rs       |  2 +-
 rpc/interface/src/rpc_handler_dummy.rs |  3 +-
 rpc/types/src/json.rs                  |  4 +++
 8 files changed, 54 insertions(+), 12 deletions(-)

diff --git a/binaries/cuprated/src/rpc/handler.rs b/binaries/cuprated/src/rpc/handler.rs
index fd9be38..5db473d 100644
--- a/binaries/cuprated/src/rpc/handler.rs
+++ b/binaries/cuprated/src/rpc/handler.rs
@@ -180,7 +180,7 @@ impl CupratedRpcHandler {
 }
 
 impl RpcHandler for CupratedRpcHandler {
-    fn restricted(&self) -> bool {
+    fn is_restricted(&self) -> bool {
         self.restricted
     }
 }
diff --git a/binaries/cuprated/src/rpc/json.rs b/binaries/cuprated/src/rpc/json.rs
index 00bbaea..b445931 100644
--- a/binaries/cuprated/src/rpc/json.rs
+++ b/binaries/cuprated/src/rpc/json.rs
@@ -32,7 +32,8 @@ use cuprate_rpc_types::{
         GetConnectionsRequest, GetConnectionsResponse, GetFeeEstimateRequest,
         GetFeeEstimateResponse, GetInfoRequest, GetInfoResponse, GetLastBlockHeaderRequest,
         GetLastBlockHeaderResponse, GetMinerDataRequest, GetMinerDataResponse,
-        GetOutputHistogramRequest, GetOutputHistogramResponse, GetTransactionPoolBacklogRequest,
+        GetOutputDistributionRequest, GetOutputDistributionResponse, GetOutputHistogramRequest,
+        GetOutputHistogramResponse, GetTransactionPoolBacklogRequest,
         GetTransactionPoolBacklogResponse, GetTxIdsLooseRequest, GetTxIdsLooseResponse,
         GetVersionRequest, GetVersionResponse, HardForkInfoRequest, HardForkInfoResponse,
         JsonRpcRequest, JsonRpcResponse, OnGetBlockHashRequest, OnGetBlockHashResponse,
@@ -41,8 +42,8 @@ use cuprate_rpc_types::{
         SyncInfoResponse,
     },
     misc::{
-        AuxPow, BlockHeader, ChainInfo, GetBan, GetMinerDataTxBacklogEntry, HardforkEntry,
-        HistogramEntry, Status, SyncInfoPeer, TxBacklogEntry,
+        AuxPow, BlockHeader, ChainInfo, Distribution, GetBan, GetMinerDataTxBacklogEntry,
+        HardforkEntry, HistogramEntry, Status, SyncInfoPeer, TxBacklogEntry,
     },
     CORE_RPC_VERSION,
 };
@@ -109,6 +110,9 @@ pub(super) async fn map_request(
         Req::GetTransactionPoolBacklog(r) => {
             Resp::GetTransactionPoolBacklog(get_transaction_pool_backlog(state, r).await?)
         }
+        Req::GetOutputDistribution(r) => {
+            Resp::GetOutputDistribution(get_output_distribution(state, r).await?)
+        }
         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?),
@@ -217,7 +221,7 @@ async fn get_block_header_by_hash(
     mut state: CupratedRpcHandler,
     request: GetBlockHeaderByHashRequest,
 ) -> Result<GetBlockHeaderByHashResponse, Error> {
-    if state.restricted() && request.hashes.len() > RESTRICTED_BLOCK_COUNT {
+    if state.is_restricted() && request.hashes.len() > RESTRICTED_BLOCK_COUNT {
         return Err(anyhow!(
             "Too many block headers requested in restricted mode"
         ));
@@ -282,7 +286,7 @@ async fn get_block_headers_range(
         request.end_height.saturating_sub(request.start_height) + 1 > RESTRICTED_BLOCK_HEADER_RANGE
     };
 
-    if state.restricted() && too_many_blocks() {
+    if state.is_restricted() && too_many_blocks() {
         return Err(anyhow!("Too many block headers requested."));
     }
 
@@ -374,7 +378,7 @@ async fn get_info(
     mut state: CupratedRpcHandler,
     request: GetInfoRequest,
 ) -> Result<GetInfoResponse, Error> {
-    let restricted = state.restricted();
+    let restricted = state.is_restricted();
     let context = blockchain_context::context(&mut state.blockchain_context).await?;
 
     let c = context.unchecked_blockchain_context();
@@ -866,6 +870,39 @@ async fn get_transaction_pool_backlog(
     })
 }
 
+/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3352-L3398>
+async fn get_output_distribution(
+    mut state: CupratedRpcHandler,
+    request: GetOutputDistributionRequest,
+) -> Result<GetOutputDistributionResponse, Error> {
+    if state.is_restricted() && request.amounts != [1, 0] {
+        return Err(anyhow!(
+            "Restricted RPC can only get output distribution for RCT outputs. Use your own node."
+        ));
+    }
+
+    // 0 is placeholder for the whole chain
+    let req_to_height = if request.to_height == 0 {
+        helper::top_height(&mut state).await?.0.saturating_sub(1)
+    } else {
+        request.to_height
+    };
+
+    let distributions = request.amounts.into_iter().map(|amount| {
+        fn get_output_distribution() -> Result<Distribution, Error> {
+            todo!("https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/rpc/rpc_handler.cpp#L29");
+            Err(anyhow!("Failed to get output distribution"))
+        }
+
+        get_output_distribution()
+    }).collect::<Result<Vec<Distribution>, _>>()?;
+
+    Ok(GetOutputDistributionResponse {
+        base: AccessResponseBase::OK,
+        distributions,
+    })
+}
+
 /// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1998-L2033>
 async fn get_miner_data(
     mut state: CupratedRpcHandler,
diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs
index f7e3a01..6d4fb9a 100644
--- a/rpc/interface/src/route/bin.rs
+++ b/rpc/interface/src/route/bin.rs
@@ -72,7 +72,7 @@ macro_rules! generate_endpoints_inner {
         paste::paste! {
             {
                 // Check if restricted.
-                if [<$variant Request>]::IS_RESTRICTED && $handler.restricted() {
+                if [<$variant Request>]::IS_RESTRICTED && $handler.is_restricted() {
                     // TODO: mimic `monerod` behavior.
                     return Err(StatusCode::FORBIDDEN);
                 }
diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route/json_rpc.rs
index 7efb851..bb86586 100644
--- a/rpc/interface/src/route/json_rpc.rs
+++ b/rpc/interface/src/route/json_rpc.rs
@@ -37,7 +37,7 @@ pub(crate) async fn json_rpc<H: RpcHandler>(
 
     // 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() {
+    if request.body.is_restricted() && handler.is_restricted() {
         let error_object = ErrorObject {
             code: ErrorCode::ServerError(-1 /* TODO */),
             message: Cow::Borrowed("Restricted. TODO: mimic monerod message"),
diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs
index 3ff8448..3993bd0 100644
--- a/rpc/interface/src/route/other.rs
+++ b/rpc/interface/src/route/other.rs
@@ -75,7 +75,7 @@ macro_rules! generate_endpoints_inner {
         paste::paste! {
             {
                 // Check if restricted.
-                if [<$variant Request>]::IS_RESTRICTED && $handler.restricted() {
+                if [<$variant Request>]::IS_RESTRICTED && $handler.is_restricted() {
                     // TODO: mimic `monerod` behavior.
                     return Err(StatusCode::FORBIDDEN);
                 }
diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs
index 1d2676c..c5c5768 100644
--- a/rpc/interface/src/rpc_handler.rs
+++ b/rpc/interface/src/rpc_handler.rs
@@ -46,5 +46,5 @@ pub trait RpcHandler:
     ///
     /// will automatically be denied access when using the
     /// [`axum::Router`] provided by [`RouterBuilder`](crate::RouterBuilder).
-    fn restricted(&self) -> bool;
+    fn is_restricted(&self) -> bool;
 }
diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs
index 9d5009e..ac48cec 100644
--- a/rpc/interface/src/rpc_handler_dummy.rs
+++ b/rpc/interface/src/rpc_handler_dummy.rs
@@ -39,7 +39,7 @@ pub struct RpcHandlerDummy {
 }
 
 impl RpcHandler for RpcHandlerDummy {
-    fn restricted(&self) -> bool {
+    fn is_restricted(&self) -> bool {
         self.restricted
     }
 }
@@ -85,6 +85,7 @@ impl Service<JsonRpcRequest> for RpcHandlerDummy {
             Req::GetTransactionPoolBacklog(_) => {
                 Resp::GetTransactionPoolBacklog(Default::default())
             }
+            Req::GetOutputDistribution(_) => Resp::GetOutputDistribution(Default::default()),
             Req::GetMinerData(_) => Resp::GetMinerData(Default::default()),
             Req::PruneBlockchain(_) => Resp::PruneBlockchain(Default::default()),
             Req::CalcPow(_) => Resp::CalcPow(Default::default()),
diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs
index cb55e64..d4087da 100644
--- a/rpc/types/src/json.rs
+++ b/rpc/types/src/json.rs
@@ -1623,6 +1623,7 @@ pub enum JsonRpcRequest {
     RelayTx(RelayTxRequest),
     SyncInfo(SyncInfoRequest),
     GetTransactionPoolBacklog(GetTransactionPoolBacklogRequest),
+    GetOutputDistribution(GetOutputDistributionRequest),
     GetMinerData(GetMinerDataRequest),
     PruneBlockchain(PruneBlockchainRequest),
     CalcPow(CalcPowRequest),
@@ -1648,6 +1649,7 @@ impl RpcCallValue for JsonRpcRequest {
             Self::GetVersion(x) => x.is_restricted(),
             Self::GetFeeEstimate(x) => x.is_restricted(),
             Self::GetTransactionPoolBacklog(x) => x.is_restricted(),
+            Self::GetOutputDistribution(x) => x.is_restricted(),
             Self::GetMinerData(x) => x.is_restricted(),
             Self::AddAuxPow(x) => x.is_restricted(),
             Self::GetTxIdsLoose(x) => x.is_restricted(),
@@ -1683,6 +1685,7 @@ impl RpcCallValue for JsonRpcRequest {
             Self::GetVersion(x) => x.is_empty(),
             Self::GetFeeEstimate(x) => x.is_empty(),
             Self::GetTransactionPoolBacklog(x) => x.is_empty(),
+            Self::GetOutputDistribution(x) => x.is_empty(),
             Self::GetMinerData(x) => x.is_empty(),
             Self::AddAuxPow(x) => x.is_empty(),
             Self::GetTxIdsLoose(x) => x.is_empty(),
@@ -1755,6 +1758,7 @@ pub enum JsonRpcResponse {
     RelayTx(RelayTxResponse),
     SyncInfo(SyncInfoResponse),
     GetTransactionPoolBacklog(GetTransactionPoolBacklogResponse),
+    GetOutputDistribution(GetOutputDistributionResponse),
     GetMinerData(GetMinerDataResponse),
     PruneBlockchain(PruneBlockchainResponse),
     CalcPow(CalcPowResponse),