Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: invoke sequencer #144

Merged
merged 10 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/sequencer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ starknet_api = { workspace = true }
starknet = { workspace = true }

# Other
tracing = { workspace = true }
rustc-hash = "1.1.0"

[dev-dependencies]
lazy_static = { workspace = true }
serde_json = { workspace = true }
29 changes: 29 additions & 0 deletions crates/sequencer/src/commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use blockifier::state::{
cached_state::CachedState,
state_api::{State as BlockifierState, StateReader as BlockifierStateReader},
};

pub trait Committer<S>
Eikix marked this conversation as resolved.
Show resolved Hide resolved
where
for<'a> &'a mut S: BlockifierState + BlockifierStateReader,
{
fn commit(cached_state: &mut CachedState<&mut S>) {
let diff = cached_state.to_state_diff();
for (address, class_hash) in diff.address_to_class_hash {
let _ = cached_state.state.set_class_hash_at(address, class_hash);
greged93 marked this conversation as resolved.
Show resolved Hide resolved
}
for (address, _) in diff.address_to_nonce {
let _ = cached_state.state.increment_nonce(address);
greged93 marked this conversation as resolved.
Show resolved Hide resolved
}
for (address, storage_updates) in diff.storage_updates {
for (k, v) in storage_updates {
cached_state.state.set_storage_at(address, k, v);
}
}
for (class_hash, compiled_class_hash) in diff.class_hash_to_compiled_class_hash {
let _ = cached_state
greged93 marked this conversation as resolved.
Show resolved Hide resolved
.state
.set_compiled_class_hash(class_hash, compiled_class_hash);
}
}
}
1 change: 1 addition & 0 deletions crates/sequencer/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod test_constants {
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 ZERO_FELT: StarkFelt = StarkFelt::from(0u8);
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();
Expand Down
7 changes: 7 additions & 0 deletions crates/sequencer/src/execution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use blockifier::transaction::{
errors::TransactionExecutionError, transaction_execution::Transaction,
};

pub trait Execution {
fn execute(&mut self, transaction: Transaction) -> Result<(), TransactionExecutionError>;
}
2 changes: 2 additions & 0 deletions crates/sequencer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod commit;
pub mod constants;
pub mod execution;
pub mod sequencer;
pub mod state;
185 changes: 183 additions & 2 deletions crates/sequencer/src/sequencer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
use blockifier::block_context::BlockContext;
use blockifier::state::state_api::{State, StateReader};
use crate::{commit::Committer, execution::Execution};
use blockifier::{
block_context::BlockContext,
state::{
cached_state::CachedState,
state_api::{State, StateReader},
},
transaction::{
errors::TransactionExecutionError, transaction_execution::Transaction,
transactions::ExecutableTransaction,
},
};
use tracing::{trace, warn};

/// Sequencer is the main struct of the sequencer crate.
/// Using a trait bound for the state allows for better
Expand All @@ -25,3 +36,173 @@ where
Self { context, state }
}
}

impl<S> Execution for Sequencer<S>
where
for<'a> &'a mut S: State + StateReader + Committer<S>,
{
fn execute(&mut self, transaction: Transaction) -> Result<(), TransactionExecutionError> {
greged93 marked this conversation as resolved.
Show resolved Hide resolved
let mut cached_state = CachedState::new(&mut self.state);
let res = transaction.execute(&mut cached_state, &self.context, false);
greged93 marked this conversation as resolved.
Show resolved Hide resolved

match res {
Err(err) => {
warn!("Transaction execution failed: {:?}", err)
}
Ok(execution_information) => {
<&mut S>::commit(&mut cached_state);
match execution_information.revert_error {
Some(err) => {
warn!("Transaction execution failed: {:?}", err)
greged93 marked this conversation as resolved.
Show resolved Hide resolved
}
None => {
trace!("Transaction execution succeeded {execution_information:?}")
}
}
}
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use std::fs::File;
use std::sync::Arc;

use blockifier::abi::abi_utils::get_storage_var_address;
use blockifier::execution::contract_class::{ContractClass, ContractClassV0};
use blockifier::state::state_api::State as BlockifierState;
use blockifier::transaction::account_transaction::AccountTransaction;
use starknet::core::types::FieldElement;
use starknet_api::core::{ChainId, ClassHash, ContractAddress, Nonce};
use starknet_api::hash::StarkFelt;
use starknet_api::transaction::{
Calldata, Fee, InvokeTransaction, InvokeTransactionV1, TransactionHash,
TransactionSignature,
};

use crate::constants::test_constants::{
FEE_TOKEN_ADDRESS, ONE_BLOCK_NUMBER, ONE_BLOCK_TIMESTAMP, ONE_CLASS_HASH,
SEQUENCER_ADDRESS, TEST_ADDRESS, TEST_CONTRACT_ACCOUNT, TEST_CONTRACT_ADDRESS,
TWO_CLASS_HASH, ZERO_FELT,
};
use crate::state::State;

use super::*;

fn read_contract_class(path: &str) -> ContractClass {
greged93 marked this conversation as resolved.
Show resolved Hide resolved
let reader = File::open(path).unwrap();
let contract_class: ContractClassV0 = serde_json::from_reader(reader).unwrap();

ContractClass::V0(contract_class)
}

fn declare_and_deploy_contract(
path: &str,
address: ContractAddress,
class_hash: ClassHash,
mut state: &mut State,
) {
let contract_class = read_contract_class(path);

state
.set_contract_class(&class_hash, contract_class)
.unwrap();
state.set_class_hash_at(address, class_hash).unwrap();
}

fn fund(address: StarkFelt, mut state: &mut State) {
state.set_storage_at(
*FEE_TOKEN_ADDRESS,
get_storage_var_address("ERC20_balances", &[address]).unwrap(),
StarkFelt::from(u128::MAX),
);
}

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

declare_and_deploy_contract(
"src/test_data/compiled_classes/counter.json",
*TEST_CONTRACT_ADDRESS,
*ONE_CLASS_HASH,
mutable,
);
declare_and_deploy_contract(
"src/test_data/compiled_classes/account.json",
*TEST_CONTRACT_ACCOUNT,
*TWO_CLASS_HASH,
mutable,
);
fund(*TEST_ADDRESS, mutable);

let context = BlockContext {
chain_id: ChainId("KKRT".into()),
block_number: *ONE_BLOCK_NUMBER,
block_timestamp: *ONE_BLOCK_TIMESTAMP,
sequencer_address: *SEQUENCER_ADDRESS,
fee_token_address: *FEE_TOKEN_ADDRESS,

vm_resource_fee_cost: Arc::new(
Eikix marked this conversation as resolved.
Show resolved Hide resolved
[
(String::from("n_steps"), 1_f64),
("pedersen_builtin".to_string(), 1_f64),
("range_check_builtin".to_string(), 1_f64),
("ecdsa_builtin".to_string(), 1_f64),
("bitwise_builtin".to_string(), 1_f64),
("poseidon_builtin".to_string(), 1_f64),
("output_builtin".to_string(), 1_f64),
("ec_op_builtin".to_string(), 1_f64),
("keccak_builtin".to_string(), 1_f64),
("segment_arena_builtin".to_string(), 1_f64),
]
.into_iter()
.collect(),
),
gas_price: 1,
invoke_tx_max_n_steps: 4_000_000,
validate_max_n_steps: 4_000_000,
max_recursion_depth: 1_000,
};
let mut sequencer = Sequencer::new(context, state);

// When
let transaction = Transaction::AccountTransaction(AccountTransaction::Invoke(
InvokeTransaction::V1(InvokeTransactionV1 {
transaction_hash: TransactionHash(*ZERO_FELT),
sender_address: *TEST_CONTRACT_ACCOUNT,
calldata: Calldata(
vec![
*TEST_ADDRESS, // destination
FieldElement::from_hex_be(
greged93 marked this conversation as resolved.
Show resolved Hide resolved
"0x3b82f69851fa1625b367ea6c116252a84257da483dcec4d4e4bc270eb5c70a7",
) // selector (inc)
.unwrap()
.into(),
*ZERO_FELT, // no data
]
.into(),
),
max_fee: Fee(1_000_000),
signature: TransactionSignature(vec![]),
nonce: Nonce(*ZERO_FELT),
}),
));
sequencer.execute(transaction).unwrap();

// Then
let expected = StarkFelt::from(1u8);
let actual = (&mut sequencer.state)
.get_storage_at(
*TEST_CONTRACT_ADDRESS,
get_storage_var_address("counter", &[]).unwrap(),
)
.unwrap();
assert_eq!(expected, actual);
}
}
4 changes: 4 additions & 0 deletions crates/sequencer/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use starknet_api::{
hash::StarkFelt,
};

use crate::commit::Committer;

/// Generic state structure for the sequencer.
/// The use of FxHashMap allows for a better performance.
/// This hash map is used by rustc. It uses a non cryptographic hash function
Expand All @@ -29,6 +31,8 @@ pub struct State {
nonces: FxHashMap<ContractAddress, Nonce>,
}

impl Committer<State> for &mut State {}

/// 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 {
Expand Down
72 changes: 72 additions & 0 deletions crates/sequencer/src/test_data/classes/account.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// A dummy account contract without any validations.

%lang starknet

from starkware.cairo.common.bool import TRUE
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.starknet.common.syscalls import (
call_contract,
deploy,
get_caller_address,
get_contract_address,
)

@event
func ContractDeployed(
address: felt, deployer: felt, classHash: felt, calldata_len: felt, calldata: felt*, salt: felt
) {
}

@external
func __validate_declare__(class_hash: felt) {
return ();
}

@external
func __validate_deploy__(class_hash: felt, contract_address_salt: felt) {
return ();
}

@external
func __validate__(contract_address, selector: felt, calldata_len: felt, calldata: felt*) {
return ();
}

@external
@raw_output
func __execute__{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
contract_address, selector: felt, calldata_len: felt, calldata: felt*
) -> (retdata_size: felt, retdata: felt*) {
let (retdata_size: felt, retdata: felt*) = call_contract(
contract_address=contract_address,
function_selector=selector,
calldata_size=calldata_len,
calldata=calldata,
);
return (retdata_size=calldata_len, retdata=calldata);
}

@external
func deploy_contract{syscall_ptr: felt*, range_check_ptr}(
class_hash: felt,
contract_address_salt: felt,
constructor_calldata_len: felt,
constructor_calldata: felt*,
) -> (contract_address: felt) {
let (contract_address) = deploy(
class_hash=class_hash,
contract_address_salt=contract_address_salt,
constructor_calldata_size=constructor_calldata_len,
constructor_calldata=constructor_calldata,
deploy_from_zero=TRUE,
);
ContractDeployed.emit(
address=contract_address,
deployer=0,
classHash=class_hash,
calldata_len=constructor_calldata_len,
calldata=constructor_calldata,
salt=contract_address_salt,
);
return (contract_address=0);
}
24 changes: 24 additions & 0 deletions crates/sequencer/src/test_data/classes/counter.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
%lang starknet

from starkware.cairo.common.uint256 import (
Uint256,
uint256_mul_div_mod,
)
from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin


@storage_var
func counter() -> (value: felt){
}

@external
func inc {
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*
}(){
let (current_counter) = counter.read();
counter.write(current_counter + 1);
return();
}
Loading