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