chik_protocol/
block_record.rs

1use chik_streamable_macro::streamable;
2
3use crate::{Bytes32, ClassgroupElement, Coin, SubEpochSummary};
4
5#[cfg(feature = "py-bindings")]
6use pyo3::prelude::*;
7
8// This class is not included or hashed into the blockchain, but it is kept in memory as a more
9// efficient way to maintain data about the blockchain. This allows us to validate future blocks,
10// difficulty adjustments, etc, without saving the whole header block in memory.
11#[streamable]
12pub struct BlockRecord {
13    header_hash: Bytes32,
14    // Header hash of the previous block
15    prev_hash: Bytes32,
16    height: u32,
17    // Total cumulative difficulty of all ancestor blocks since genesis
18    weight: u128,
19    // Total number of VDF iterations since genesis, including this block
20    total_iters: u128,
21    signage_point_index: u8,
22    // This is the intermediary VDF output at ip_iters in challenge chain
23    challenge_vdf_output: ClassgroupElement,
24    // This is the intermediary VDF output at ip_iters in infused cc, iff deficit <= 3
25    infused_challenge_vdf_output: Option<ClassgroupElement>,
26    // The reward chain infusion output, input to next VDF
27    reward_infusion_new_challenge: Bytes32,
28    // Hash of challenge chain data, used to validate end of slots in the future
29    challenge_block_info_hash: Bytes32,
30    // Current network sub_slot_iters parameter
31    sub_slot_iters: u64,
32    // Need to keep track of these because Coins are created in a future block
33    pool_puzzle_hash: Bytes32,
34    farmer_puzzle_hash: Bytes32,
35    // The number of iters required for this proof of space
36    required_iters: u64,
37    // A deficit of 16 is an overflow block after an infusion. Deficit of 15 is a challenge block
38    deficit: u8,
39    overflow: bool,
40    prev_transaction_block_height: u32,
41
42    // Transaction block (present iff is_transaction_block)
43    timestamp: Option<u64>,
44    // Header hash of the previous transaction block
45    prev_transaction_block_hash: Option<Bytes32>,
46    fees: Option<u64>,
47    reward_claims_incorporated: Option<Vec<Coin>>,
48
49    // Slot (present iff this is the first SB in sub slot)
50    finished_challenge_slot_hashes: Option<Vec<Bytes32>>,
51    finished_infused_challenge_slot_hashes: Option<Vec<Bytes32>>,
52    finished_reward_slot_hashes: Option<Vec<Bytes32>>,
53
54    // Sub-epoch (present iff this is the first SB after sub-epoch)
55    sub_epoch_summary_included: Option<SubEpochSummary>,
56}
57
58impl BlockRecord {
59    pub fn is_transaction_block(&self) -> bool {
60        self.timestamp.is_some()
61    }
62
63    pub fn first_in_sub_slot(&self) -> bool {
64        self.finished_challenge_slot_hashes.is_some()
65    }
66
67    pub fn is_challenge_block(&self, min_blocks_per_challenge_block: u8) -> bool {
68        self.deficit == min_blocks_per_challenge_block - 1
69    }
70}
71
72#[cfg(feature = "py-bindings")]
73use pyo3::types::PyDict;
74
75#[cfg(feature = "py-bindings")]
76use pyo3::exceptions::PyValueError;
77
78#[cfg(feature = "py-bindings")]
79use chik_traits::ChikToPython;
80
81#[cfg(feature = "py-bindings")]
82#[pymethods]
83impl BlockRecord {
84    #[getter]
85    #[pyo3(name = "is_transaction_block")]
86    fn py_is_transaction_block(&self) -> bool {
87        self.is_transaction_block()
88    }
89
90    #[getter]
91    #[pyo3(name = "first_in_sub_slot")]
92    fn py_first_in_sub_slot(&self) -> bool {
93        self.first_in_sub_slot()
94    }
95
96    #[pyo3(name = "is_challenge_block")]
97    fn py_is_challenge_block(&self, constants: &Bound<'_, PyAny>) -> PyResult<bool> {
98        Ok(self.is_challenge_block(
99            constants
100                .getattr("MIN_BLOCKS_PER_CHALLENGE_BLOCK")?
101                .extract::<u8>()?,
102        ))
103    }
104
105    // TODO: at some point it would be nice to port
106    // chik.consensus.pot_iterations to rust, and make this less hacky
107    fn sp_sub_slot_total_iters_impl(
108        &self,
109        py: Python<'_>,
110        constants: &Bound<'_, PyAny>,
111    ) -> PyResult<u128> {
112        let ret = self
113            .total_iters
114            .checked_sub(self.ip_iters_impl(py, constants)? as u128)
115            .ok_or(PyValueError::new_err("uint128 overflow"))?;
116        if self.overflow {
117            ret.checked_sub(self.sub_slot_iters as u128)
118                .ok_or(PyValueError::new_err("uint128 overflow"))
119        } else {
120            Ok(ret)
121        }
122    }
123
124    fn ip_sub_slot_total_iters_impl(
125        &self,
126        py: Python<'_>,
127        constants: &Bound<'_, PyAny>,
128    ) -> PyResult<u128> {
129        self.total_iters
130            .checked_sub(self.ip_iters_impl(py, constants)? as u128)
131            .ok_or(PyValueError::new_err("uint128 overflow"))
132    }
133
134    fn sp_iters_impl(&self, py: Python<'_>, constants: &Bound<'_, PyAny>) -> PyResult<u64> {
135        let ctx = PyDict::new(py);
136        ctx.set_item("sub_slot_iters", self.sub_slot_iters)?;
137        ctx.set_item("signage_point_index", self.signage_point_index)?;
138        ctx.set_item("constants", constants)?;
139        py.run(
140            c"from chik.consensus.pot_iterations import calculate_ip_iters, calculate_sp_iters\n\
141            ret = calculate_sp_iters(constants, sub_slot_iters, signage_point_index)\n",
142            None,
143            Some(&ctx),
144        )?;
145        ctx.get_item("ret").unwrap().unwrap().extract::<u64>()
146    }
147
148    fn ip_iters_impl(&self, py: Python<'_>, constants: &Bound<'_, PyAny>) -> PyResult<u64> {
149        let ctx = PyDict::new(py);
150        ctx.set_item("sub_slot_iters", self.sub_slot_iters)?;
151        ctx.set_item("signage_point_index", self.signage_point_index)?;
152        ctx.set_item("required_iters", self.required_iters)?;
153        ctx.set_item("constants", constants)?;
154        py.run(
155            c"from chik.consensus.pot_iterations import calculate_ip_iters, calculate_sp_iters\n\
156            ret = calculate_ip_iters(constants, sub_slot_iters, signage_point_index, required_iters)\n",
157            None,
158            Some(&ctx),
159            )?;
160        ctx.get_item("ret").unwrap().unwrap().extract::<u64>()
161    }
162
163    fn sp_total_iters_impl(&self, py: Python<'_>, constants: &Bound<'_, PyAny>) -> PyResult<u128> {
164        self.sp_sub_slot_total_iters_impl(py, constants)?
165            .checked_add(self.sp_iters_impl(py, constants)? as u128)
166            .ok_or(PyValueError::new_err("uint128 overflow"))
167    }
168
169    fn sp_sub_slot_total_iters<'a>(
170        &self,
171        py: Python<'a>,
172        constants: &Bound<'_, PyAny>,
173    ) -> PyResult<Bound<'a, PyAny>> {
174        ChikToPython::to_python(&self.sp_sub_slot_total_iters_impl(py, constants)?, py)
175    }
176
177    fn ip_sub_slot_total_iters<'a>(
178        &self,
179        py: Python<'a>,
180        constants: &Bound<'_, PyAny>,
181    ) -> PyResult<Bound<'a, PyAny>> {
182        ChikToPython::to_python(&self.ip_sub_slot_total_iters_impl(py, constants)?, py)
183    }
184
185    fn sp_iters<'a>(
186        &self,
187        py: Python<'a>,
188        constants: &Bound<'_, PyAny>,
189    ) -> PyResult<Bound<'a, PyAny>> {
190        ChikToPython::to_python(&self.sp_iters_impl(py, constants)?, py)
191    }
192
193    fn ip_iters<'a>(
194        &self,
195        py: Python<'a>,
196        constants: &Bound<'_, PyAny>,
197    ) -> PyResult<Bound<'a, PyAny>> {
198        ChikToPython::to_python(&self.ip_iters_impl(py, constants)?, py)
199    }
200
201    fn sp_total_iters<'a>(
202        &self,
203        py: Python<'a>,
204        constants: &Bound<'_, PyAny>,
205    ) -> PyResult<Bound<'a, PyAny>> {
206        ChikToPython::to_python(&self.sp_total_iters_impl(py, constants)?, py)
207    }
208}