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}