essential_node_types/
lib.rs

1//! Core types used within this implementation of the Essential protocol.
2
3#![forbid(unsafe_code)]
4#![deny(missing_docs)]
5
6#[doc(inline)]
7pub use block::{Block, Header as BlockHeader};
8use essential_types::{
9    contract::Contract,
10    convert::{word_4_from_u8_32, word_from_bytes_slice},
11    predicate::{PredicateEncodeError, Program},
12    solution::{Mutation, Solution, SolutionSet},
13    PredicateAddress, Word,
14};
15use serde::{Deserialize, Serialize};
16
17pub mod block;
18
19/// Wrappers around tokio's `watch` channel for notifying of new blocks.
20#[cfg(feature = "block-notify")]
21pub mod block_notify;
22
23/// The default big-bang configuration.
24pub const DEFAULT_BIG_BANG: &str = include_str!("../big-bang.yml");
25
26/// Describes how to construct the big-bang (aka "genesis") block.
27#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, Ord, Serialize, Deserialize)]
28pub struct BigBang {
29    /// The address of the contract used to track block state.
30    ///
31    /// This contract includes special keys for the block number and block timestamp. E.g.
32    ///
33    /// - `[0]` is the key for the block number, which is a `Word`.
34    /// - `[1]` is the key for the block timestamp, which is a `Word` for seconds since
35    ///   `UNIX_EPOCH`.
36    pub block_state: PredicateAddress,
37    /// The address of the contract used to register contracts and their associated predicates.
38    ///
39    /// There are two primary regions of storage for the contract registry. The layout can be
40    /// thought of as the following.
41    ///
42    /// ```ignore
43    /// storage {
44    ///     contracts: (b256 => Contract),
45    ///     predicates: (b256 => Predicate),
46    /// }
47    /// ```
48    ///
49    /// - `contracts` have key prefix `[0]`
50    /// - `predicates` have key prefix `[1]`
51    ///
52    /// ## Contracts
53    ///
54    /// Contract entries contain the salt and the addresses of its predicates. E.g.
55    ///
56    /// - `[0, <contract-ca>, 0]` is the key to the "salt", a `b256`.
57    /// - `[0, <contract-ca>, <predicate-ca>]` is a key whose non-empty value specifies that the
58    ///   predicate with the given address is associated with the contract.
59    ///
60    /// ## Predicates
61    ///
62    /// Predicate entries contain their length in bytes as a `Word` and their fully byte-encoded
63    /// form within a `int[]` with padding in the final word if necessary. E.g.
64    ///
65    /// - `[1, <predicate-ca>]` to get the length bytes as `Word` followed by the fully encoded
66    ///   word-padded data as `int[]`.
67    pub contract_registry: PredicateAddress,
68    /// The address of the contract used to register programs.
69    ///
70    /// ```ignore
71    /// storage {
72    ///     programs: (b256 => Program),
73    /// }
74    /// ```
75    ///
76    /// ## Programs
77    ///
78    /// Program entries contain their length in bytes as a `Word` and their bytecode within a
79    /// `int[]` with padding in the final word if necessary. E.g.
80    ///
81    /// - `[0, <program-ca>]` to get the bytecode length as `Word` followed by the bytecode as
82    ///   `int[]`.
83    pub program_registry: PredicateAddress,
84    /// The `SolutionSet` used to initialize arbitrary state for the big bang block.
85    ///
86    /// The primary purpose is setting the initial block state and registering the big bang
87    /// contracts.
88    ///
89    /// If constructing a custom `BigBang` configuration, care must be taken to ensure that this
90    /// `SolutionSet` does actually register the aforementioned contracts correctly.
91    pub solution_set: SolutionSet,
92}
93
94impl BigBang {
95    /// Produce the big bang [`Block`].
96    pub fn block(&self) -> Block {
97        Block {
98            header: BlockHeader {
99                number: 0,
100                timestamp: std::time::Duration::from_secs(0),
101            },
102            solution_sets: vec![self.solution_set.clone()],
103        }
104    }
105}
106
107impl Default for BigBang {
108    fn default() -> Self {
109        serde_yaml::from_str(DEFAULT_BIG_BANG)
110            .expect("default `big-bang-block.yml` must be valid (checked in tests)")
111    }
112}
113
114/// Functions for constructing keys into the "contract registry" contract state.
115pub mod contract_registry {
116    use crate::padded_words_from_bytes;
117    use essential_types::{ContentAddress, Key, PredicateAddress, Word};
118
119    const CONTRACTS_PREFIX: Word = 0;
120    const PREDICATES_PREFIX: Word = 1;
121
122    /// A key that may be used to refer to a contract's `salt` in state.
123    ///
124    /// The returned key is formatted as: `[0, <contract-ca>, 0]`
125    pub fn contract_salt_key(contract_ca: &ContentAddress) -> Key {
126        Some(CONTRACTS_PREFIX)
127            .into_iter()
128            .chain(padded_words_from_bytes(&contract_ca.0))
129            .chain(Some(0))
130            .collect()
131    }
132
133    /// A key that may be used to test if the predicate exists within the contract specified in the
134    /// `PredicateAddress`.
135    ///
136    /// The returned key is formatted as: `[0, <contract-ca>, <predicate-ca>]`
137    pub fn contract_predicate_key(pred_addr: &PredicateAddress) -> Key {
138        Some(CONTRACTS_PREFIX)
139            .into_iter()
140            .chain(padded_words_from_bytes(&pred_addr.contract.0))
141            .chain(padded_words_from_bytes(&pred_addr.predicate.0))
142            .collect()
143    }
144
145    /// A key that may be used to retrieve the full `Predicate` from the contract registry state.
146    ///
147    /// When queried, the `Predicate` data will be preceded by a single word that describes the
148    /// length of the predicate in bytes.
149    ///
150    /// The returned key is formatted as: `[1, <predicate-ca>]`
151    pub fn predicate_key(pred_ca: &ContentAddress) -> Key {
152        Some(PREDICATES_PREFIX)
153            .into_iter()
154            .chain(padded_words_from_bytes(&pred_ca.0))
155            .collect()
156    }
157}
158
159/// Functions for constructing keys into the "program registry" contract state.
160pub mod program_registry {
161    use crate::padded_words_from_bytes;
162    use essential_types::{ContentAddress, Key, Word};
163
164    const PROGRAMS_PREFIX: Word = 0;
165
166    /// A key that may be used to retrieve the full `Program` from the program registry state.
167    ///
168    /// When queried, the `Program` data will be preceded by a single word that describes the
169    /// length of the program in bytes.
170    ///
171    /// The returned key is formatted as `[0, <program-ca>]`
172    pub fn program_key(prog_ca: &ContentAddress) -> Key {
173        Some(PROGRAMS_PREFIX)
174            .into_iter()
175            .chain(padded_words_from_bytes(&prog_ca.0))
176            .collect()
177    }
178}
179
180/// Create a solution for registering the given contract at the given contract registry.
181pub fn register_contract_solution(
182    contract_registry: PredicateAddress,
183    contract: &Contract,
184) -> Result<Solution, PredicateEncodeError> {
185    Ok(Solution {
186        predicate_to_solve: contract_registry,
187        predicate_data: vec![],
188        state_mutations: register_contract_mutations(contract)?,
189    })
190}
191
192/// Generate the mutations required to register a given contract within the big bang's "contract
193/// registry" contract. This is useful for constructing contract deployment `Solution`s.
194///
195/// Learn more about the layout of state within the contract registry in
196/// [`BigBang::contract_registry`].
197pub fn register_contract_mutations(
198    contract: &Contract,
199) -> Result<Vec<Mutation>, PredicateEncodeError> {
200    let mut muts = vec![];
201
202    // Add the mutations that register the contract's salt and length.
203    let contract_ca = essential_hash::content_addr(contract);
204    let salt_w = word_4_from_u8_32(contract.salt);
205
206    // Add the salt at `[0, <contract-ca>, 0]`.
207    muts.push(Mutation {
208        key: contract_registry::contract_salt_key(&contract_ca),
209        value: salt_w.to_vec(),
210    });
211
212    // Register the predicates.
213    for pred in &contract.predicates {
214        let pred_ca = essential_hash::content_addr(pred);
215        let pred_addr = PredicateAddress {
216            contract: contract_ca.clone(),
217            predicate: pred_ca,
218        };
219
220        // Add to the contract `[0, <contract-addr>, <pred-addr>]`
221        let key = contract_registry::contract_predicate_key(&pred_addr);
222        muts.push(Mutation {
223            key,
224            value: vec![1],
225        });
226
227        // Encode the predicate so that it may be registered.
228        let pred_bytes: Vec<u8> = pred.encode()?.collect();
229        let len_bytes = pred_bytes.len();
230        let len_bytes_w = Word::try_from(len_bytes).expect("checked during `encode`");
231
232        // Add the encoded predicate.
233        let key = contract_registry::predicate_key(&pred_addr.predicate);
234        muts.push(Mutation {
235            key,
236            value: Some(len_bytes_w)
237                .into_iter()
238                .chain(padded_words_from_bytes(&pred_bytes))
239                .collect(),
240        });
241    }
242
243    Ok(muts)
244}
245
246/// Create a solution for registering the given program at the given program registry.
247pub fn register_program_solution(
248    program_registry: PredicateAddress,
249    program: &Program,
250) -> Solution {
251    Solution {
252        predicate_to_solve: program_registry,
253        predicate_data: vec![],
254        state_mutations: register_program_mutations(program),
255    }
256}
257
258/// Generate the mutations required to register a given program within the big bang's "program
259/// registry" contract. This is useful for constructing program deployment `Solution`s.
260///
261/// Learn more about the layout of state within the contract registry in
262/// [`BigBang::program_registry`].
263pub fn register_program_mutations(program: &Program) -> Vec<Mutation> {
264    let mut muts = vec![];
265
266    // Register the program.
267    let prog_ca = essential_hash::content_addr(program);
268    let prog_bytes = &program.0;
269    let len_bytes = prog_bytes.len();
270    // FIXME: relevant issue https://github.com/essential-contributions/essential-base/issues/240
271    let len_bytes_w = Word::try_from(len_bytes).expect("should be enforced by Program::MAX_SIZE");
272
273    // Add to the program `[0, <program-ca>]`
274    let key = program_registry::program_key(&prog_ca);
275    muts.push(Mutation {
276        key,
277        value: Some(len_bytes_w)
278            .into_iter()
279            .chain(padded_words_from_bytes(prog_bytes))
280            .collect(),
281    });
282
283    muts
284}
285
286/// Generate a solution that sets the block state to the given block number and timestamp.
287pub fn block_state_solution(
288    block_state: PredicateAddress,
289    block_number: Word,
290    block_timestamp_secs: Word,
291) -> Solution {
292    Solution {
293        predicate_to_solve: block_state,
294        predicate_data: vec![],
295        state_mutations: block_state_mutations(block_number, block_timestamp_secs),
296    }
297}
298
299/// Generate the mutations required for a solution that sets the block state to the given block
300/// number and timestamp.
301pub fn block_state_mutations(block_number: Word, block_timestamp_secs: Word) -> Vec<Mutation> {
302    vec![
303        Mutation {
304            key: vec![0],
305            value: vec![block_number],
306        },
307        Mutation {
308            key: vec![1],
309            value: vec![block_timestamp_secs],
310        },
311    ]
312}
313
314fn padded_words_from_bytes(bytes: &[u8]) -> impl '_ + Iterator<Item = Word> {
315    bytes
316        .chunks(core::mem::size_of::<Word>())
317        .map(word_from_bytes_slice)
318}