cuprate_consensus_context/
distribution.rs1use std::{num::NonZero, ops::Range};
7
8use tower::ServiceExt;
9use tracing::instrument;
10
11use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
12use cuprate_types::{
13 blockchain::{BlockchainReadRequest, BlockchainResponse},
14 rpc::OutputDistributionData,
15 VerifiedBlockInformation,
16};
17
18use crate::{hardforks::HardForkConfig, ContextCacheError, Database, HardFork};
19
20#[derive(Debug, Clone, Eq, PartialEq)]
22pub struct CumulativeRctOutsCache {
23 pub start_height: usize,
25 pub cumulative_rct_outs: Vec<u64>,
27}
28
29impl CumulativeRctOutsCache {
30 #[instrument(
32 name = "init_rct_outs_cache",
33 level = "info",
34 skip(hard_fork_cfg, database)
35 )]
36 pub async fn init_from_chain_height<D: Database + Clone>(
37 chain_height: usize,
38 hard_fork_cfg: HardForkConfig,
39 database: D,
40 ) -> Result<Self, ContextCacheError> {
41 tracing::info!("Initializing output distribution cache, this may take a while.");
42
43 let rct_start_height = hard_fork_cfg.info.info_for_hf(&HardFork::V4).height();
45
46 let cumulative_rct_outs = if rct_start_height < chain_height {
47 get_cumulative_rct_outs(database, rct_start_height..chain_height).await?
48 } else {
49 Vec::new()
50 };
51
52 Ok(Self {
53 start_height: rct_start_height,
54 cumulative_rct_outs,
55 })
56 }
57
58 pub fn new_block(&mut self, height: usize, numb_rct_outputs: usize) {
60 if height < self.start_height {
61 debug_assert_eq!(numb_rct_outputs, 0);
62 return;
63 }
64 assert_eq!(self.start_height + self.cumulative_rct_outs.len(), height);
65
66 let last = self.cumulative_rct_outs.last().copied().unwrap_or(0);
67 self.cumulative_rct_outs
68 .push(last + usize_to_u64(numb_rct_outputs));
69 }
70
71 pub fn pop_blocks_main_chain(&mut self, numb_blocks: usize) {
73 self.cumulative_rct_outs
74 .truncate(self.cumulative_rct_outs.len().saturating_sub(numb_blocks));
75 }
76
77 pub fn distribution(
79 &self,
80 from_height: u64,
81 to_height: Option<NonZero<u64>>,
82 cumulative: bool,
83 chain_height: usize,
84 ) -> Result<OutputDistributionData, tower::BoxError> {
85 if to_height.is_some_and(|h| h.get() < from_height) {
86 return Err("`to_height` is below `from_height`".into());
87 }
88
89 let to_height = to_height.map_or(chain_height - 1, |h| {
90 let h = h.get();
91 u64_to_usize(h)
92 });
93
94 if to_height >= chain_height {
95 return Err("`to_height` is above the chain height".into());
96 }
97
98 let start_height = u64_to_usize(from_height).max(self.start_height);
100
101 if start_height > to_height {
102 return Ok(OutputDistributionData {
103 amount: 0,
104 distribution: Vec::new(),
105 start_height: usize_to_u64(start_height),
106 base: 0,
107 });
108 }
109
110 let idx = |height: usize| height - self.start_height;
111
112 let base = if start_height <= self.start_height {
115 0
116 } else {
117 self.cumulative_rct_outs[idx(start_height - 1)]
118 };
119
120 let mut distribution =
121 self.cumulative_rct_outs[idx(start_height)..=idx(to_height)].to_vec();
122
123 if !cumulative {
124 let mut prev = base;
125 for cumulative_outs in &mut distribution {
126 let delta = *cumulative_outs - prev;
127 prev = *cumulative_outs;
128 *cumulative_outs = delta;
129 }
130 }
131
132 Ok(OutputDistributionData {
133 amount: 0,
134 distribution,
135 start_height: usize_to_u64(start_height),
136 base,
137 })
138 }
139}
140
141pub fn rct_output_count(block: &VerifiedBlockInformation) -> usize {
143 let miner_tx = block.block.miner_transaction();
144 let miner_tx_outputs = if miner_tx.version() == 2 {
145 miner_tx.prefix().outputs.len()
146 } else {
147 0
148 };
149
150 miner_tx_outputs
151 + block
152 .txs
153 .iter()
154 .filter(|tx| tx.tx.version() == 2)
155 .map(|tx| tx.tx.prefix().outputs.len())
156 .sum::<usize>()
157}
158
159#[instrument(name = "get_cumulative_rct_outs", skip(database), level = "info")]
161async fn get_cumulative_rct_outs<D: Database>(
162 database: D,
163 block_heights: Range<usize>,
164) -> Result<Vec<u64>, ContextCacheError> {
165 let BlockchainResponse::CumulativeRctOutsInRange(cumulative_rct_outs) = database
166 .oneshot(BlockchainReadRequest::CumulativeRctOutsInRange(
167 block_heights,
168 ))
169 .await?
170 else {
171 panic!("Database sent incorrect response");
172 };
173
174 Ok(cumulative_rct_outs)
175}