ckb_dao/
lib.rs

1//! This crate provides implementation to calculate dao field.
2
3use byteorder::{ByteOrder, LittleEndian};
4use ckb_chain_spec::consensus::Consensus;
5use ckb_dao_utils::{DaoError, extract_dao_data, pack_dao_data};
6use ckb_traits::{CellDataProvider, EpochProvider, HeaderProvider};
7use ckb_types::{
8    bytes::Bytes,
9    core::{
10        Capacity, CapacityResult, EpochExt, HeaderView, ScriptHashType,
11        cell::{CellMeta, ResolvedTransaction},
12    },
13    packed::{Byte32, CellOutput, Script, WitnessArgs},
14    prelude::*,
15};
16use std::collections::HashSet;
17
18#[cfg(test)]
19mod tests;
20
21/// Dao field calculator
22/// `DaoCalculator` is a facade to calculate the dao field.
23pub struct DaoCalculator<'a, DL> {
24    consensus: &'a Consensus,
25    data_loader: &'a DL,
26}
27
28impl<'a, DL: CellDataProvider + HeaderProvider> DaoCalculator<'a, DL> {
29    /// Returns the total transactions fee of `rtx`.
30    pub fn transaction_fee(&self, rtx: &ResolvedTransaction) -> Result<Capacity, DaoError> {
31        let maximum_withdraw = self.transaction_maximum_withdraw(rtx)?;
32        rtx.transaction
33            .outputs_capacity()
34            .and_then(|y| maximum_withdraw.safe_sub(y))
35            .map_err(Into::into)
36    }
37    fn transaction_maximum_withdraw(
38        &self,
39        rtx: &ResolvedTransaction,
40    ) -> Result<Capacity, DaoError> {
41        let header_deps: HashSet<Byte32> = rtx.transaction.header_deps_iter().collect();
42        rtx.resolved_inputs.iter().enumerate().try_fold(
43            Capacity::zero(),
44            |capacities, (i, cell_meta)| {
45                let capacity: Result<Capacity, DaoError> = {
46                    let output = &cell_meta.cell_output;
47                    let is_dao_type_script = |type_script: Script| {
48                        Into::<u8>::into(type_script.hash_type())
49                            == Into::<u8>::into(ScriptHashType::Type)
50                            && type_script.code_hash() == self.consensus.dao_type_hash()
51                    };
52                    let is_withdrawing_input =
53                        |cell_meta: &CellMeta| match self.data_loader.load_cell_data(cell_meta) {
54                            Some(data) => data.len() == 8 && LittleEndian::read_u64(&data) > 0,
55                            None => false,
56                        };
57                    if output
58                        .type_()
59                        .to_opt()
60                        .map(is_dao_type_script)
61                        .unwrap_or(false)
62                        && is_withdrawing_input(cell_meta)
63                    {
64                        let withdrawing_header_hash = cell_meta
65                            .transaction_info
66                            .as_ref()
67                            .map(|info| &info.block_hash)
68                            .filter(|hash| header_deps.contains(hash))
69                            .ok_or(DaoError::InvalidOutPoint)?;
70                        let deposit_header_hash = rtx
71                            .transaction
72                            .witnesses()
73                            .get(i)
74                            .ok_or(DaoError::InvalidOutPoint)
75                            .and_then(|witness_data| {
76                                // dao contract stores header deps index as u64 in the input_type field of WitnessArgs
77                                let witness =
78                                    WitnessArgs::from_slice(&Into::<Bytes>::into(witness_data))
79                                        .map_err(|_| DaoError::InvalidDaoFormat)?;
80                                let header_deps_index_data: Option<Bytes> =
81                                    witness.input_type().to_opt().map(|witness| witness.into());
82                                if header_deps_index_data.is_none()
83                                    || header_deps_index_data.clone().map(|data| data.len())
84                                        != Some(8)
85                                {
86                                    return Err(DaoError::InvalidDaoFormat);
87                                }
88                                Ok(LittleEndian::read_u64(&header_deps_index_data.unwrap()))
89                            })
90                            .and_then(|header_dep_index| {
91                                rtx.transaction
92                                    .header_deps()
93                                    .get(header_dep_index as usize)
94                                    .and_then(|hash| header_deps.get(&hash))
95                                    .ok_or(DaoError::InvalidOutPoint)
96                            })?;
97                        self.calculate_maximum_withdraw(
98                            output,
99                            Capacity::bytes(cell_meta.data_bytes as usize)?,
100                            deposit_header_hash,
101                            withdrawing_header_hash,
102                        )
103                    } else {
104                        Ok(output.capacity().into())
105                    }
106                };
107                capacity.and_then(|c| c.safe_add(capacities).map_err(Into::into))
108            },
109        )
110    }
111
112    /// Calculate maximum withdraw capacity of a deposited dao output
113    pub fn calculate_maximum_withdraw(
114        &self,
115        output: &CellOutput,
116        output_data_capacity: Capacity,
117        deposit_header_hash: &Byte32,
118        withdrawing_header_hash: &Byte32,
119    ) -> Result<Capacity, DaoError> {
120        let deposit_header = self
121            .data_loader
122            .get_header(deposit_header_hash)
123            .ok_or(DaoError::InvalidHeader)?;
124        let withdrawing_header = self
125            .data_loader
126            .get_header(withdrawing_header_hash)
127            .ok_or(DaoError::InvalidHeader)?;
128        if deposit_header.number() >= withdrawing_header.number() {
129            return Err(DaoError::InvalidOutPoint);
130        }
131
132        let (deposit_ar, _, _, _) = extract_dao_data(deposit_header.dao());
133        let (withdrawing_ar, _, _, _) = extract_dao_data(withdrawing_header.dao());
134
135        let occupied_capacity = output.occupied_capacity(output_data_capacity)?;
136        let output_capacity: Capacity = output.capacity().into();
137        let counted_capacity = output_capacity.safe_sub(occupied_capacity)?;
138        let withdraw_counted_capacity = u128::from(counted_capacity.as_u64())
139            * u128::from(withdrawing_ar)
140            / u128::from(deposit_ar);
141        let withdraw_capacity =
142            Capacity::shannons(withdraw_counted_capacity as u64).safe_add(occupied_capacity)?;
143
144        Ok(withdraw_capacity)
145    }
146    /// Creates a new `DaoCalculator`.
147    pub fn new(consensus: &'a Consensus, data_loader: &'a DL) -> Self {
148        DaoCalculator {
149            consensus,
150            data_loader,
151        }
152    }
153}
154
155impl<'a, DL: CellDataProvider + EpochProvider + HeaderProvider> DaoCalculator<'a, DL> {
156    /// Returns the primary block reward for `target` block.
157    pub fn primary_block_reward(&self, target: &HeaderView) -> Result<Capacity, DaoError> {
158        let target_epoch = self
159            .data_loader
160            .get_epoch_ext(target)
161            .ok_or(DaoError::InvalidHeader)?;
162
163        target_epoch
164            .block_reward(target.number())
165            .map_err(Into::into)
166    }
167
168    /// Returns the secondary block reward for `target` block.
169    pub fn secondary_block_reward(&self, target: &HeaderView) -> Result<Capacity, DaoError> {
170        if target.number() == 0 {
171            return Ok(Capacity::zero());
172        }
173
174        let target_parent_hash = target.data().raw().parent_hash();
175        let target_parent = self
176            .data_loader
177            .get_header(&target_parent_hash)
178            .ok_or(DaoError::InvalidHeader)?;
179        let target_epoch = self
180            .data_loader
181            .get_epoch_ext(target)
182            .ok_or(DaoError::InvalidHeader)?;
183
184        let target_g2 = target_epoch
185            .secondary_block_issuance(target.number(), self.consensus.secondary_epoch_reward())?;
186        let (_, target_parent_c, _, target_parent_u) = extract_dao_data(target_parent.dao());
187        let reward128 = u128::from(target_g2.as_u64()) * u128::from(target_parent_u.as_u64())
188            / u128::from(target_parent_c.as_u64());
189        let reward = u64::try_from(reward128).map_err(|_| DaoError::Overflow)?;
190        Ok(Capacity::shannons(reward))
191    }
192
193    /// Calculates the new dao field with specified [`EpochExt`].
194    pub fn dao_field_with_current_epoch(
195        &self,
196        rtxs: impl Iterator<Item = &'a ResolvedTransaction> + Clone,
197        parent: &HeaderView,
198        current_block_epoch: &EpochExt,
199    ) -> Result<Byte32, DaoError> {
200        // Freed occupied capacities from consumed inputs
201        let freed_occupied_capacities =
202            rtxs.clone().try_fold(Capacity::zero(), |capacities, rtx| {
203                self.input_occupied_capacities(rtx)
204                    .and_then(|c| capacities.safe_add(c))
205            })?;
206        let added_occupied_capacities = self.added_occupied_capacities(rtxs.clone())?;
207        let withdrawed_interests = self.withdrawed_interests(rtxs)?;
208
209        let (parent_ar, parent_c, parent_s, parent_u) = extract_dao_data(parent.dao());
210
211        // g contains both primary issuance and secondary issuance,
212        // g2 is the secondary issuance for the block, which consists of
213        // issuance for the miner, NervosDAO and treasury.
214        // When calculating issuance in NervosDAO, we use the real
215        // issuance for each block(which will only be issued on chain
216        // after the finalization delay), not the capacities generated
217        // in the cellbase of current block.
218        let current_block_number = parent.number() + 1;
219        let current_g2 = current_block_epoch.secondary_block_issuance(
220            current_block_number,
221            self.consensus.secondary_epoch_reward(),
222        )?;
223        let current_g = current_block_epoch
224            .block_reward(current_block_number)
225            .and_then(|c| c.safe_add(current_g2))?;
226
227        let miner_issuance128 = u128::from(current_g2.as_u64()) * u128::from(parent_u.as_u64())
228            / u128::from(parent_c.as_u64());
229        let miner_issuance =
230            Capacity::shannons(u64::try_from(miner_issuance128).map_err(|_| DaoError::Overflow)?);
231        let nervosdao_issuance = current_g2.safe_sub(miner_issuance)?;
232
233        let current_c = parent_c.safe_add(current_g)?;
234        let current_u = parent_u
235            .safe_add(added_occupied_capacities)
236            .and_then(|u| u.safe_sub(freed_occupied_capacities))?;
237        let current_s = parent_s
238            .safe_add(nervosdao_issuance)
239            .and_then(|s| s.safe_sub(withdrawed_interests))?;
240
241        let ar_increase128 =
242            u128::from(parent_ar) * u128::from(current_g2.as_u64()) / u128::from(parent_c.as_u64());
243        let ar_increase = u64::try_from(ar_increase128).map_err(|_| DaoError::Overflow)?;
244        let current_ar = parent_ar
245            .checked_add(ar_increase)
246            .ok_or(DaoError::Overflow)?;
247
248        Ok(pack_dao_data(current_ar, current_c, current_s, current_u))
249    }
250
251    /// Calculates the new dao field after packaging these transactions. It returns the dao field in [`Byte32`] format. Please see [`extract_dao_data`] if you intend to see the detailed content.
252    ///
253    /// [`Byte32`]: ../ckb_types/packed/struct.Byte32.html
254    /// [`extract_dao_data`]: ../ckb_dao_utils/fn.extract_dao_data.html
255    pub fn dao_field(
256        &self,
257        rtxs: impl Iterator<Item = &'a ResolvedTransaction> + Clone,
258        parent: &HeaderView,
259    ) -> Result<Byte32, DaoError> {
260        let current_block_epoch = self
261            .consensus
262            .next_epoch_ext(parent, self.data_loader)
263            .ok_or(DaoError::InvalidHeader)?
264            .epoch();
265        self.dao_field_with_current_epoch(rtxs, parent, &current_block_epoch)
266    }
267
268    fn added_occupied_capacities(
269        &self,
270        mut rtxs: impl Iterator<Item = &'a ResolvedTransaction>,
271    ) -> CapacityResult<Capacity> {
272        // Newly added occupied capacities from outputs
273        let added_occupied_capacities = rtxs.try_fold(Capacity::zero(), |capacities, rtx| {
274            rtx.transaction
275                .outputs_with_data_iter()
276                .enumerate()
277                .try_fold(Capacity::zero(), |tx_capacities, (_, (output, data))| {
278                    Capacity::bytes(data.len())
279                        .and_then(|c| output.occupied_capacity(c))
280                        .and_then(|c| tx_capacities.safe_add(c))
281                })
282                .and_then(|c| capacities.safe_add(c))
283        })?;
284
285        Ok(added_occupied_capacities)
286    }
287
288    fn input_occupied_capacities(&self, rtx: &ResolvedTransaction) -> CapacityResult<Capacity> {
289        rtx.resolved_inputs
290            .iter()
291            .try_fold(Capacity::zero(), |capacities, cell_meta| {
292                let current_capacity = modified_occupied_capacity(cell_meta, self.consensus);
293                current_capacity.and_then(|c| capacities.safe_add(c))
294            })
295    }
296
297    fn withdrawed_interests(
298        &self,
299        mut rtxs: impl Iterator<Item = &'a ResolvedTransaction> + Clone,
300    ) -> Result<Capacity, DaoError> {
301        let maximum_withdraws = rtxs.clone().try_fold(Capacity::zero(), |capacities, rtx| {
302            self.transaction_maximum_withdraw(rtx)
303                .and_then(|c| capacities.safe_add(c).map_err(Into::into))
304        })?;
305        let input_capacities = rtxs.try_fold(Capacity::zero(), |capacities, rtx| {
306            let tx_input_capacities = rtx.resolved_inputs.iter().try_fold(
307                Capacity::zero(),
308                |tx_capacities, cell_meta| {
309                    let output_capacity: Capacity = cell_meta.cell_output.capacity().into();
310                    tx_capacities.safe_add(output_capacity)
311                },
312            )?;
313            capacities.safe_add(tx_input_capacities)
314        })?;
315        maximum_withdraws
316            .safe_sub(input_capacities)
317            .map_err(Into::into)
318    }
319}
320
321/// return special occupied capacity if cell is satoshi's gift
322/// otherwise return cell occupied capacity
323pub fn modified_occupied_capacity(
324    cell_meta: &CellMeta,
325    consensus: &Consensus,
326) -> CapacityResult<Capacity> {
327    if let Some(tx_info) = &cell_meta.transaction_info {
328        if tx_info.is_genesis()
329            && tx_info.is_cellbase()
330            && cell_meta.cell_output.lock().args().raw_data() == consensus.satoshi_pubkey_hash.0[..]
331        {
332            return Into::<Capacity>::into(cell_meta.cell_output.capacity())
333                .safe_mul_ratio(consensus.satoshi_cell_occupied_ratio);
334        }
335    }
336    cell_meta.occupied_capacity()
337}