Skip to main content

chia_consensus/
flags.rs

1use bitflags::bitflags;
2use clvmr::MEMPOOL_MODE as CLVM_MEMPOOL_MODE;
3
4#[cfg(feature = "py-bindings")]
5use pyo3::{Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python, types::PyInt};
6
7bitflags! {
8    /// Full flag set for CLVM execution and consensus (condition parsing, validation, generator mode).
9    /// Combines flags from clvmr (lower bytes) and consensus (upper bytes).
10    /// The end goal should be to make these flags independent, but we still
11    /// have at least one quirk in chia-protocol's Program::run_rust() where it
12    /// would be ideal to take Consensusflags, but it can't depend on
13    /// chia-consensus, so it has to take ClvmFlags instead. those aren't exposed
14    /// to python, so it relies on these flags matching.
15    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
16    pub struct ConsensusFlags: u32 {
17        // Flags from clvmr (chia_dialect)
18        // we still rely on these bits matching exactly the flags in clvm_rs
19        // via the python binding, which "launders" the type of the flags
20        const CANONICAL_INTS = 0x0001;
21        const NO_UNKNOWN_OPS = 0x0002;
22        const LIMIT_HEAP = 0x0004;
23        const RELAXED_BLS = 0x0008;
24        const ENABLE_KECCAK_OPS_OUTSIDE_GUARD = 0x0100;
25        const DISABLE_OP = 0x0200;
26        const ENABLE_SHA256_TREE = 0x0400;
27        const ENABLE_SECP_OPS = 0x0800;
28
29        // Consensus flags
30        /// Skip validating AGG_SIG / condition signatures.
31        const DONT_VALIDATE_SIGNATURE = 0x1_0000;
32
33        /// Unknown condition codes are disallowed (mempool-mode).
34        const NO_UNKNOWN_CONDS = 0x2_0000;
35
36        /// Compute condition fingerprints for spends eligible for dedup.
37        const COMPUTE_FINGERPRINT = 0x4_0000;
38
39        /// Conditions require the exact supported argument count (mempool-mode).
40        const STRICT_ARGS_COUNT = 0x8_0000;
41
42        /// Add flat cost to conditions (active after hard fork 2).
43        const COST_CONDITIONS = 0x80_0000;
44
45        /// Simpler generator rules (hard fork behavior).
46        const SIMPLE_GENERATOR = 0x100_0000;
47    }
48}
49
50impl ConsensusFlags {
51    /// Convert clvmr's ClvmFlags to the corresponding ConsensusFlags (shared flags only).
52    /// For each clvmr flag we check whether it is set (using contains()), then set our corresponding flag.
53    #[must_use]
54    const fn from_clvm_flags(clvm: clvmr::chia_dialect::ClvmFlags) -> Self {
55        use clvmr::chia_dialect::ClvmFlags;
56        let mut out = ConsensusFlags::empty();
57        if clvm.contains(ClvmFlags::CANONICAL_INTS) {
58            out = out.union(ConsensusFlags::CANONICAL_INTS);
59        }
60        if clvm.contains(ClvmFlags::NO_UNKNOWN_OPS) {
61            out = out.union(ConsensusFlags::NO_UNKNOWN_OPS);
62        }
63        if clvm.contains(ClvmFlags::LIMIT_HEAP) {
64            out = out.union(ConsensusFlags::LIMIT_HEAP);
65        }
66        if clvm.contains(ClvmFlags::RELAXED_BLS) {
67            out = out.union(ConsensusFlags::RELAXED_BLS);
68        }
69        if clvm.contains(ClvmFlags::ENABLE_KECCAK_OPS_OUTSIDE_GUARD) {
70            out = out.union(ConsensusFlags::ENABLE_KECCAK_OPS_OUTSIDE_GUARD);
71        }
72        if clvm.contains(ClvmFlags::DISABLE_OP) {
73            out = out.union(ConsensusFlags::DISABLE_OP);
74        }
75        if clvm.contains(ClvmFlags::ENABLE_SHA256_TREE) {
76            out = out.union(ConsensusFlags::ENABLE_SHA256_TREE);
77        }
78        if clvm.contains(ClvmFlags::ENABLE_SECP_OPS) {
79            out = out.union(ConsensusFlags::ENABLE_SECP_OPS);
80        }
81        out
82    }
83
84    /// Convert to clvmr's ClvmFlags by mapping each shared flag to its ClvmFlags counterpart.
85    /// Does not rely on underlying bits being the same; consensus-only flags are ignored.
86    pub fn to_clvm_flags(self) -> clvmr::chia_dialect::ClvmFlags {
87        use clvmr::chia_dialect::ClvmFlags;
88        let mut out = ClvmFlags::empty();
89        if self.contains(ConsensusFlags::CANONICAL_INTS) {
90            out.insert(ClvmFlags::CANONICAL_INTS);
91        }
92        if self.contains(ConsensusFlags::NO_UNKNOWN_OPS) {
93            out.insert(ClvmFlags::NO_UNKNOWN_OPS);
94        }
95        if self.contains(ConsensusFlags::LIMIT_HEAP) {
96            out.insert(ClvmFlags::LIMIT_HEAP);
97        }
98        if self.contains(ConsensusFlags::RELAXED_BLS) {
99            out.insert(ClvmFlags::RELAXED_BLS);
100        }
101        if self.contains(ConsensusFlags::ENABLE_KECCAK_OPS_OUTSIDE_GUARD) {
102            out.insert(ClvmFlags::ENABLE_KECCAK_OPS_OUTSIDE_GUARD);
103        }
104        if self.contains(ConsensusFlags::DISABLE_OP) {
105            out.insert(ClvmFlags::DISABLE_OP);
106        }
107        if self.contains(ConsensusFlags::ENABLE_SHA256_TREE) {
108            out.insert(ClvmFlags::ENABLE_SHA256_TREE);
109        }
110        if self.contains(ConsensusFlags::ENABLE_SECP_OPS) {
111            out.insert(ClvmFlags::ENABLE_SECP_OPS);
112        }
113        out
114    }
115}
116
117/// Mempool-mode: clvmr MEMPOOL_MODE plus consensus stricter checking.
118pub const MEMPOOL_MODE: ConsensusFlags = ConsensusFlags::from_clvm_flags(CLVM_MEMPOOL_MODE)
119    .union(ConsensusFlags::NO_UNKNOWN_CONDS)
120    .union(ConsensusFlags::STRICT_ARGS_COUNT);
121
122impl Default for ConsensusFlags {
123    fn default() -> Self {
124        Self::empty()
125    }
126}
127
128#[cfg(feature = "py-bindings")]
129impl<'py> FromPyObject<'py, 'py> for ConsensusFlags {
130    type Error = PyErr;
131
132    fn extract(obj: pyo3::Borrowed<'py, 'py, PyAny>) -> PyResult<Self> {
133        let b: u32 = obj.extract()?;
134        Ok(ConsensusFlags::from_bits_truncate(b))
135    }
136}
137
138#[cfg(feature = "py-bindings")]
139impl<'py> IntoPyObject<'py> for ConsensusFlags {
140    type Target = PyInt;
141    type Output = Bound<'py, Self::Target>;
142    type Error = std::convert::Infallible;
143
144    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
145        Ok(PyInt::new(py, self.bits()))
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::ConsensusFlags;
152    use bitflags::Flags;
153    use clvmr::chia_dialect::ClvmFlags;
154
155    /// No two flags may share any bit
156    #[test]
157    fn no_overlapping_bits() {
158        let all = ConsensusFlags::FLAGS;
159        for (i, a) in all.iter().enumerate() {
160            for b in &all[i + 1..] {
161                let a_bits = a.value().bits();
162                let b_bits = b.value().bits();
163                assert_eq!(
164                    a_bits & b_bits,
165                    0,
166                    "overlapping bits between {:?} ({a_bits:x}) and {:?} ({b_bits:x})",
167                    a.value(),
168                    b.value(),
169                );
170            }
171        }
172    }
173
174    /// Every ClvmFlags flag must exist in ConsensusFlags and have the exact same bits.
175    /// We rely on this for the python binding (Program::run_rust) which launders flags as u32.
176    #[test]
177    fn clvm_flags_bits_match_consensus_flags() {
178        let clvm_flags = ClvmFlags::FLAGS;
179        for flag in clvm_flags {
180            assert!(flag.is_named());
181            let name = flag.name();
182            let clvm_bits = flag.value().bits();
183            let Some(consensus) = ConsensusFlags::from_name(name) else {
184                panic!(
185                    "ClvmFlags flag {name} has no corresponding ConsensusFlags; \
186                     every ClvmFlags flag must exist in ConsensusFlags"
187                )
188            };
189            assert_eq!(
190                clvm_bits,
191                consensus.bits(),
192                "ClvmFlags and ConsensusFlags must have the same bits for flag {:?} (name = {name}); \
193                 we rely on exact bit compatibility",
194                flag.value(),
195            );
196        }
197    }
198
199    /// Every shared flag round-trips through from_clvm_flags / to_clvm_flags,
200    /// and consensus-only flags never leak into ClvmFlags.
201    #[test]
202    fn shared_flags_round_trip_through_conversion() {
203        for flag in ClvmFlags::FLAGS {
204            assert!(flag.is_named());
205            let clvm = *flag.value();
206            let name = flag.name();
207
208            let consensus = ConsensusFlags::from_clvm_flags(clvm);
209            let expected = ConsensusFlags::from_name(name).unwrap();
210            assert_eq!(
211                consensus, expected,
212                "from_clvm_flags did not convert ClvmFlags::{name} correctly"
213            );
214
215            let back = expected.to_clvm_flags();
216            assert_eq!(
217                back, clvm,
218                "to_clvm_flags did not convert ConsensusFlags::{name} back to ClvmFlags::{name}"
219            );
220        }
221
222        // ConsensusFlags is a strict superset: consensus-only flags exist
223        // and must not leak into ClvmFlags via to_clvm_flags.
224        let consensus_only =
225            ConsensusFlags::all().difference(ConsensusFlags::from_clvm_flags(ClvmFlags::all()));
226        assert!(
227            !consensus_only.is_empty(),
228            "ConsensusFlags should be a strict superset of ClvmFlags"
229        );
230        assert!(
231            consensus_only.to_clvm_flags().is_empty(),
232            "consensus-only flags must not appear in to_clvm_flags output"
233        );
234    }
235}