ckb_reward_calculator/
lib.rs1use ckb_chain_spec::consensus::Consensus;
4use ckb_dao::DaoCalculator;
5use ckb_dao_utils::DaoError;
6use ckb_logger::debug;
7use ckb_store::ChainStore;
8use ckb_types::{
9 core::{BlockReward, Capacity, CapacityResult, HeaderView},
10 packed::{Byte32, CellbaseWitness, ProposalShortId, Script},
11 prelude::*,
12};
13use std::cmp;
14use std::collections::HashSet;
15
16#[cfg(test)]
17mod tests;
18
19pub struct RewardCalculator<'a, CS> {
34 consensus: &'a Consensus,
35 store: &'a CS,
36}
37
38impl<'a, CS: ChainStore> RewardCalculator<'a, CS> {
39 pub fn new(consensus: &'a Consensus, store: &'a CS) -> Self {
41 RewardCalculator { consensus, store }
42 }
43
44 pub fn block_reward_to_finalize(
46 &self,
47 parent: &HeaderView,
48 ) -> Result<(Script, BlockReward), DaoError> {
49 let block_number = parent.number() + 1;
50 let target_number = self
51 .consensus
52 .finalize_target(block_number)
53 .expect("block number checked before involving finalize_target");
54 let target = self
55 .store
56 .get_block_hash(target_number)
57 .and_then(|hash| self.store.get_block_header(&hash))
58 .expect("block hash checked before involving get_ancestor");
59 self.block_reward_internal(&target, parent)
60 }
61
62 pub fn block_reward_for_target(
64 &self,
65 target: &HeaderView,
66 ) -> Result<(Script, BlockReward), DaoError> {
67 let finalization_parent_number =
68 target.number() + self.consensus.finalization_delay_length() - 1;
69 let parent = self
70 .store
71 .get_block_hash(finalization_parent_number)
72 .and_then(|hash| self.store.get_block_header(&hash))
73 .expect("block hash checked before involving get_ancestor");
74 self.block_reward_internal(target, &parent)
75 }
76
77 fn block_reward_internal(
83 &self,
84 target: &HeaderView,
85 parent: &HeaderView,
86 ) -> Result<(Script, BlockReward), DaoError> {
87 let target_lock = CellbaseWitness::from_slice(
88 &self
89 .store
90 .get_cellbase(&target.hash())
91 .expect("target cellbase exist")
92 .witnesses()
93 .get(0)
94 .expect("target witness exist")
95 .raw_data(),
96 )
97 .expect("cellbase loaded from store should has non-empty witness")
98 .lock();
99
100 let txs_fees = self.txs_fees(target)?;
101 let proposal_reward = self.proposal_reward(parent, target)?;
102 let (primary, secondary) = self.base_block_reward(target)?;
103
104 let total = txs_fees
105 .safe_add(proposal_reward)?
106 .safe_add(primary)?
107 .safe_add(secondary)?;
108
109 debug!(
110 "[RewardCalculator] target {} {}\n
111 txs_fees {:?}, proposal_reward {:?}, primary {:?}, secondary: {:?}, total_reward {:?}",
112 target.number(),
113 target.hash(),
114 txs_fees,
115 proposal_reward,
116 primary,
117 secondary,
118 total,
119 );
120
121 let block_reward = BlockReward {
122 total,
123 primary,
124 secondary,
125 tx_fee: txs_fees,
126 proposal_reward,
127 };
128
129 Ok((target_lock, block_reward))
130 }
131
132 fn txs_fees(&self, target: &HeaderView) -> CapacityResult<Capacity> {
135 let consensus = self.consensus;
136 let target_ext = self
137 .store
138 .get_block_ext(&target.hash())
139 .expect("block body stored");
140
141 target_ext
142 .txs_fees
143 .iter()
144 .try_fold(Capacity::zero(), |acc, tx_fee| {
145 tx_fee
146 .safe_mul_ratio(consensus.proposer_reward_ratio())
147 .and_then(|proposer| {
148 tx_fee
149 .safe_sub(proposer)
150 .and_then(|miner| acc.safe_add(miner))
151 })
152 })
153 }
154
155 fn proposal_reward(
171 &self,
172 parent: &HeaderView,
173 target: &HeaderView,
174 ) -> CapacityResult<Capacity> {
175 let mut target_proposals = self.get_proposal_ids_by_hash(&target.hash());
176
177 let proposal_window = self.consensus.tx_proposal_window();
178 let proposer_ratio = self.consensus.proposer_reward_ratio();
179 let block_number = parent.number() + 1;
180 let store = self.store;
181
182 let mut reward = Capacity::zero();
183
184 let competing_commit_start = cmp::max(
186 block_number.saturating_sub(proposal_window.length()),
187 1 + proposal_window.closest(),
188 );
189
190 let mut proposed: HashSet<ProposalShortId> = HashSet::new();
191 let mut index = parent.to_owned();
192
193 let committed_idx_proc = |hash: &Byte32| -> Vec<ProposalShortId> {
196 store
197 .get_block_txs_hashes(hash)
198 .into_iter()
199 .skip(1)
200 .map(|tx_hash| ProposalShortId::from_tx_hash(&tx_hash))
201 .collect()
202 };
203
204 let txs_fees_proc = |hash: &Byte32| -> Vec<Capacity> {
205 store
206 .get_block_ext(hash)
207 .expect("block ext stored")
208 .txs_fees
209 };
210
211 let committed_idx = committed_idx_proc(&index.hash());
212
213 let has_committed = target_proposals
214 .intersection(&committed_idx.iter().cloned().collect::<HashSet<_>>())
215 .next()
216 .is_some();
217 if has_committed {
218 for (id, tx_fee) in committed_idx
219 .into_iter()
220 .zip(txs_fees_proc(&index.hash()).iter())
221 {
222 if target_proposals.remove(&id) {
224 reward = reward.safe_add(tx_fee.safe_mul_ratio(proposer_ratio)?)?;
225 }
226 }
227 }
228
229 while index.number() > competing_commit_start && !target_proposals.is_empty() {
230 index = store
231 .get_block_header(&index.data().raw().parent_hash())
232 .expect("header stored");
233
234 let competing_proposal_start =
236 cmp::max(index.number().saturating_sub(proposal_window.farthest()), 1);
237
238 let previous_ids = store
239 .get_block_hash(competing_proposal_start)
240 .map(|hash| self.get_proposal_ids_by_hash(&hash))
241 .expect("finalize target exist");
242
243 proposed.extend(previous_ids);
244
245 let committed_idx = committed_idx_proc(&index.hash());
246
247 let has_committed = target_proposals
248 .intersection(&committed_idx.iter().cloned().collect::<HashSet<_>>())
249 .next()
250 .is_some();
251 if has_committed {
252 for (id, tx_fee) in committed_idx
253 .into_iter()
254 .zip(txs_fees_proc(&index.hash()).iter())
255 {
256 if target_proposals.remove(&id) && !proposed.contains(&id) {
257 reward = reward.safe_add(tx_fee.safe_mul_ratio(proposer_ratio)?)?;
258 }
259 }
260 }
261 }
262 Ok(reward)
263 }
264
265 fn base_block_reward(&self, target: &HeaderView) -> Result<(Capacity, Capacity), DaoError> {
266 let data_loader = self.store.borrow_as_data_loader();
267 let calculator = DaoCalculator::new(self.consensus, &data_loader);
268 let primary_block_reward = calculator.primary_block_reward(target)?;
269 let secondary_block_reward = calculator.secondary_block_reward(target)?;
270
271 Ok((primary_block_reward, secondary_block_reward))
272 }
273
274 fn get_proposal_ids_by_hash(&self, hash: &Byte32) -> HashSet<ProposalShortId> {
275 let mut ids_set = HashSet::new();
276 if let Some(ids) = self.store.get_block_proposal_txs_ids(hash) {
277 ids_set.extend(ids)
278 }
279 if let Some(us) = self.store.get_block_uncles(hash) {
280 for u in us.data().into_iter() {
281 ids_set.extend(u.proposals().into_iter());
282 }
283 }
284 ids_set
285 }
286}