Skip to content

Commit

Permalink
feat: implement blockifier State and StateReader traits (#107)
Browse files Browse the repository at this point in the history
* implement blockifier

* move blockifier from workspace to maintain two versions

* view toml

* update manifests

* lock

* update from comments

* add rust doc, update should panic

* remove unnecessary comments

* rename constants + TODO
  • Loading branch information
greged93 authored Sep 21, 2023
1 parent a1747cb commit fdd6c97
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 125 deletions.
180 changes: 71 additions & 109 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ chrono = { version = "0.4.26", features = ["serde"] }
ctor = "0.2.4"
dotenv = "0.15.0"
eyre = "0.6.8"
lazy_static = "1.4.0"
regex = "1.9.3"
reqwest = { version = "0.11.20", features = ["gzip"] }
rstest = "0.18.1"
thiserror = "1.0.47"
tokio = { version = "1.21.2", features = ["macros"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
walkdir = "2.3.3"
zip = "0.6.6"

Expand Down
4 changes: 2 additions & 2 deletions crates/ef-testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ regex = { workspace = true }
reqwest = { workspace = true, optional = true }
rstest = { workspace = true }
tokio = { workspace = true }
tracing = "0.1.37"
tracing = { workspace = true }
walkdir = { workspace = true }
zip = { workspace = true, optional = true }

[dev-dependencies]
tracing-subscriber = "0.3.17"
tracing-subscriber = { workspace = true }

[features]
ef-tests = []
Expand Down
7 changes: 6 additions & 1 deletion crates/sequencer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ license.workspace = true

[dependencies]
# Starknet
blockifier = { git = "https://github.com/starkware-libs/blockifier.git", tag = "v0.3.0-rc0" }
# TODO: remove the blockifier patch on the workspace once we can remove Katana.
blockifier = { package = "blockifier", git = "https://github.com/starkware-libs/blockifier.git", tag = "v0.3.0-rc0" }
starknet_api = { workspace = true }
starknet = { workspace = true }

# Other
rustc-hash = "1.1.0"

[dev-dependencies]
lazy_static = { workspace = true }
1 change: 0 additions & 1 deletion crates/sequencer/src/config.rs

This file was deleted.

29 changes: 29 additions & 0 deletions crates/sequencer/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#[cfg(test)]
pub mod test_constants {
use starknet::core::types::FieldElement;
use starknet_api::{
block::{BlockNumber, BlockTimestamp},
core::{ClassHash, CompiledClassHash, ContractAddress, PatriciaKey},
hash::StarkFelt,
};

lazy_static::lazy_static! {
pub static ref TEST_CONTRACT_ADDRESS: ContractAddress = ContractAddress(*ONE_PATRICIA);
pub static ref TEST_CONTRACT_ACCOUNT: ContractAddress = ContractAddress(*TWO_PATRICIA);
pub static ref TEST_ADDRESS: StarkFelt = *ONE_FELT;
pub static ref TEST_CONTRACT: StarkFelt = *TWO_FELT;
pub static ref SENDER_ADDRESS: FieldElement = FieldElement::from(2u8);
pub static ref SEQUENCER_ADDRESS: ContractAddress = ContractAddress(TryInto::<PatriciaKey>::try_into(StarkFelt::from(1234u16)).unwrap());
pub static ref FEE_TOKEN_ADDRESS: ContractAddress = ContractAddress(TryInto::<PatriciaKey>::try_into(StarkFelt::from(12345u16)).unwrap());

pub static ref ONE_FELT: StarkFelt = StarkFelt::from(1u8);
pub static ref TWO_FELT: StarkFelt = StarkFelt::from(2u8);
pub static ref ONE_PATRICIA: PatriciaKey = TryInto::<PatriciaKey>::try_into(*ONE_FELT).unwrap();
pub static ref TWO_PATRICIA: PatriciaKey = TryInto::<PatriciaKey>::try_into(*TWO_FELT).unwrap();
pub static ref ONE_CLASS_HASH: ClassHash = ClassHash(*ONE_FELT);
pub static ref TWO_CLASS_HASH: ClassHash = ClassHash(*TWO_FELT);
pub static ref ONE_COMPILED_CLASS_HASH: CompiledClassHash = CompiledClassHash(*ONE_FELT);
pub static ref ONE_BLOCK_NUMBER: BlockNumber = BlockNumber(1);
pub static ref ONE_BLOCK_TIMESTAMP: BlockTimestamp = BlockTimestamp(1);
}
}
2 changes: 1 addition & 1 deletion crates/sequencer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pub mod config;
pub mod constants;
pub mod sequencer;
pub mod state;
22 changes: 16 additions & 6 deletions crates/sequencer/src/sequencer.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
use crate::config::SequencerConfig;
use blockifier::block_context::BlockContext;
use blockifier::state::state_api::{State, StateReader};

/// Sequencer is the main struct of the sequencer crate.
/// Using a trait bound for the state allows for better
/// speed, as the type of the state is known at compile time.
pub struct Sequencer<S: State + StateReader> {
pub config: SequencerConfig,
/// We bound S such that a mutable reference to S (&'a mut S)
/// must implement State and StateReader. The `for` keyword
/// indicates that the bound must hold for any lifetime 'a.
/// For more details, check out https://doc.rust-lang.org/nomicon/hrtb.html
pub struct Sequencer<S>
where
for<'a> &'a mut S: State + StateReader,
{
pub context: BlockContext,
pub state: S,
}

impl<S: State + StateReader> Sequencer<S> {
impl<S> Sequencer<S>
where
for<'a> &'a mut S: State + StateReader,
{
/// Creates a new Sequencer instance.
pub fn new(config: SequencerConfig, state: S) -> Self {
Self { config, state }
pub fn new(context: BlockContext, state: S) -> Self {
Self { context, state }
}
}
242 changes: 237 additions & 5 deletions crates/sequencer/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use blockifier::state::cached_state::CommitmentStateDiff;
use blockifier::state::errors::StateError;
use blockifier::state::state_api::{
State as BlockifierState, StateReader as BlockifierStateReader, StateResult,
};
use blockifier::{
execution::contract_class::ContractClass, state::cached_state::ContractStorageKey,
};
use rustc_hash::FxHashMap;
use starknet_api::core::CompiledClassHash;
use starknet_api::state::StorageKey;
use starknet_api::{
core::{ClassHash, ContractAddress, Nonce},
hash::StarkFelt,
Expand All @@ -16,9 +22,235 @@ use starknet_api::{
/// See [rustc-hash](https://crates.io/crates/rustc-hash) for more information.
#[derive(Default)]
pub struct State {
pub classes: FxHashMap<ClassHash, ContractClass>,
pub compiled_classes: FxHashMap<ClassHash, CompiledClassHash>,
pub contracts: FxHashMap<ContractAddress, ClassHash>,
pub storage: FxHashMap<ContractStorageKey, StarkFelt>,
pub nonces: FxHashMap<ContractAddress, Nonce>,
classes: FxHashMap<ClassHash, ContractClass>,
compiled_class_hashes: FxHashMap<ClassHash, CompiledClassHash>,
contracts: FxHashMap<ContractAddress, ClassHash>,
storage: FxHashMap<ContractStorageKey, StarkFelt>,
nonces: FxHashMap<ContractAddress, Nonce>,
}

/// State implementation for the sequencer. We use a mutable reference to the state
/// because this is what will be available during the implementation of the execution.
impl BlockifierState for &mut State {
fn set_storage_at(
&mut self,
contract_address: ContractAddress,
key: StorageKey,
value: StarkFelt,
) {
self.storage.insert((contract_address, key), value);
}

fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> {
let current_nonce = self
.nonces
.get(&contract_address)
.cloned()
.unwrap_or_default();

let mut current_nonce: u64 = current_nonce.0.try_into()?;
current_nonce += 1;

self.nonces
.insert(contract_address, Nonce(StarkFelt::from(current_nonce)));

Ok(())
}

fn set_class_hash_at(
&mut self,
contract_address: ContractAddress,
class_hash: ClassHash,
) -> StateResult<()> {
self.contracts.insert(contract_address, class_hash);
Ok(())
}

fn set_contract_class(
&mut self,
class_hash: &ClassHash,
contract_class: ContractClass,
) -> StateResult<()> {
self.classes.insert(class_hash.to_owned(), contract_class);
Ok(())
}

fn set_compiled_class_hash(
&mut self,
class_hash: ClassHash,
compiled_class_hash: CompiledClassHash,
) -> StateResult<()> {
self.compiled_class_hashes
.insert(class_hash, compiled_class_hash);
Ok(())
}

fn to_state_diff(&self) -> CommitmentStateDiff {
unreachable!("to_state_diff should not be called in the sequencer")
}
}

impl BlockifierStateReader for &mut State {
fn get_storage_at(
&mut self,
contract_address: ContractAddress,
key: StorageKey,
) -> StateResult<StarkFelt> {
Ok(self
.storage
.get(&(contract_address, key))
.cloned()
.unwrap_or_default())
}

fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult<Nonce> {
Ok(self
.nonces
.get(&contract_address)
.cloned()
.unwrap_or_default())
}

fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult<ClassHash> {
Ok(self
.contracts
.get(&contract_address)
.cloned()
.unwrap_or_default())
}

/// # Errors
///
/// If the compiled class is not declared.
fn get_compiled_contract_class(
&mut self,
class_hash: &ClassHash,
) -> StateResult<ContractClass> {
self.classes
.get(class_hash)
.cloned()
.ok_or_else(|| StateError::UndeclaredClassHash(class_hash.to_owned()))
}

/// # Errors
///
/// If the compiled class hash is not declared.
fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult<CompiledClassHash> {
self.compiled_class_hashes
.get(&class_hash)
.cloned()
.ok_or_else(|| StateError::UndeclaredClassHash(class_hash))
}
}

#[cfg(test)]
mod tests {
use blockifier::execution::contract_class::ContractClassV0;

use crate::constants::test_constants::{
ONE_CLASS_HASH, ONE_COMPILED_CLASS_HASH, ONE_FELT, ONE_PATRICIA, TEST_CONTRACT_ADDRESS,
};

use super::*;

#[test]
fn test_storage() {
// Given
let mut state = &mut State::default();

// When
state.set_storage_at(*TEST_CONTRACT_ADDRESS, StorageKey(*ONE_PATRICIA), *ONE_FELT);

// Then
let expected = *ONE_FELT;
let actual = state
.get_storage_at(*TEST_CONTRACT_ADDRESS, StorageKey(*ONE_PATRICIA))
.unwrap();
assert_eq!(expected, actual);
}

#[test]
fn test_nonce() {
// Given
let mut state = &mut State::default();

// When
state.increment_nonce(*TEST_CONTRACT_ADDRESS).unwrap();

// Then
let expected = Nonce(*ONE_FELT);
let actual = state.get_nonce_at(*TEST_CONTRACT_ADDRESS).unwrap();
assert_eq!(expected, actual);
}

#[test]
fn test_class_hash() {
// Given
let mut state = &mut State::default();

// When
state
.set_class_hash_at(*TEST_CONTRACT_ADDRESS, *ONE_CLASS_HASH)
.unwrap();

// Then
let expected = *ONE_CLASS_HASH;
let actual = state.get_class_hash_at(*TEST_CONTRACT_ADDRESS).unwrap();
assert_eq!(expected, actual);
}

#[test]
fn test_contract_class() {
// Given
let mut state = &mut State::default();

// When
state
.set_contract_class(
&ONE_CLASS_HASH,
ContractClass::V0(ContractClassV0::default()),
)
.unwrap();

// Then
let expected = ContractClass::V0(ContractClassV0::default());
let actual = state.get_compiled_contract_class(&ONE_CLASS_HASH).unwrap();
assert_eq!(expected, actual);
}

#[test]
#[should_panic(expected = "UndeclaredClassHash")]
fn test_uninitialized_contract_class() {
// Given
let mut state = &mut State::default();

// When
state.get_compiled_contract_class(&ONE_CLASS_HASH).unwrap();
}

#[test]
fn test_compiled_class_hash() {
// Given
let mut state = &mut State::default();

// When
state
.set_compiled_class_hash(*ONE_CLASS_HASH, *ONE_COMPILED_CLASS_HASH)
.unwrap();

// Then
let expected = *ONE_COMPILED_CLASS_HASH;
let actual = state.get_compiled_class_hash(*ONE_CLASS_HASH).unwrap();
assert_eq!(expected, actual);
}

#[test]
#[should_panic(expected = "UndeclaredClassHash")]
fn test_uninitialized_compiled_class_hash() {
// Given
let mut state = &mut State::default();

// When
state.get_compiled_class_hash(*ONE_CLASS_HASH).unwrap();
}
}

0 comments on commit fdd6c97

Please sign in to comment.