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

refactor(katana): historical block execution context #1436

Merged
merged 1 commit into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion crates/dojo-test-utils/src/sequencer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ pub struct TestSequencer {

impl TestSequencer {
pub async fn start(config: SequencerConfig, starknet_config: StarknetConfig) -> Self {
let sequencer = Arc::new(KatanaSequencer::new(config, starknet_config).await);
let sequencer = Arc::new(
KatanaSequencer::new(config, starknet_config)
.await
.expect("Failed to create sequencer"),
);

let handle = spawn(
Arc::clone(&sequencer),
Expand Down
39 changes: 9 additions & 30 deletions crates/katana/core/src/backend/config.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
use blockifier::block_context::{BlockContext, FeeTokenAddresses, GasPrices};
use katana_primitives::block::GasPrices;
use katana_primitives::chain::ChainId;
use starknet_api::block::{BlockNumber, BlockTimestamp};
use katana_primitives::env::BlockEnv;
use url::Url;

use crate::constants::{
DEFAULT_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_VALIDATE_MAX_STEPS, FEE_TOKEN_ADDRESS,
SEQUENCER_ADDRESS,
};
use crate::env::{get_default_vm_resource_fee_cost, BlockContextGenerator};
use crate::constants::{DEFAULT_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_VALIDATE_MAX_STEPS};
use crate::env::BlockContextGenerator;

#[derive(Debug, Clone)]
pub struct StarknetConfig {
Expand All @@ -21,28 +18,10 @@ pub struct StarknetConfig {
}

impl StarknetConfig {
pub fn block_context(&self) -> BlockContext {
BlockContext {
block_number: BlockNumber::default(),
chain_id: self.env.chain_id.into(),
block_timestamp: BlockTimestamp::default(),
sequencer_address: (*SEQUENCER_ADDRESS).into(),
// As the fee has two currencies, we also have to adjust their addresses.
// https://github.com/starkware-libs/blockifier/blob/51b343fe38139a309a69b2482f4b484e8caa5edf/crates/blockifier/src/block_context.rs#L34
fee_token_addresses: FeeTokenAddresses {
eth_fee_token_address: (*FEE_TOKEN_ADDRESS).into(),
strk_fee_token_address: Default::default(),
},
vm_resource_fee_cost: get_default_vm_resource_fee_cost().into(),
// Gas prices are dual too.
// https://github.com/starkware-libs/blockifier/blob/51b343fe38139a309a69b2482f4b484e8caa5edf/crates/blockifier/src/block_context.rs#L49
gas_prices: GasPrices {
eth_l1_gas_price: self.env.gas_price,
strk_l1_gas_price: Default::default(),
},
validate_max_n_steps: self.env.validate_max_steps,
invoke_tx_max_n_steps: self.env.invoke_max_steps,
max_recursion_depth: 1000,
pub fn block_env(&self) -> BlockEnv {
BlockEnv {
l1_gas_prices: GasPrices { eth: self.env.gas_price, ..Default::default() },
..Default::default()
}
}

Expand All @@ -68,7 +47,7 @@ impl Default for StarknetConfig {
#[derive(Debug, Clone)]
pub struct Environment {
pub chain_id: ChainId,
pub gas_price: u128,
pub gas_price: u64,
pub invoke_max_steps: u32,
pub validate_max_steps: u32,
}
Expand Down
153 changes: 107 additions & 46 deletions crates/katana/core/src/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::sync::Arc;

use blockifier::block_context::BlockContext;
use katana_primitives::block::{
Block, FinalityStatus, GasPrices, Header, PartialHeader, SealedBlockWithStatus,
};
use katana_primitives::chain::ChainId;
use katana_primitives::contract::ContractAddress;
use katana_primitives::env::{BlockEnv, CfgEnv, FeeTokenAddressses};
use katana_primitives::receipt::Receipt;
use katana_primitives::state::StateUpdatesWithDeclaredClasses;
use katana_primitives::transaction::TxWithHash;
Expand All @@ -20,7 +19,6 @@ use starknet::core::types::{BlockId, BlockStatus, MaybePendingBlockWithTxHashes}
use starknet::core::utils::parse_cairo_short_string;
use starknet::providers::jsonrpc::HttpTransport;
use starknet::providers::{JsonRpcClient, Provider};
use starknet_api::block::{BlockNumber, BlockTimestamp};
use tracing::{info, trace};

pub mod config;
Expand All @@ -30,28 +28,27 @@ pub mod storage;
use self::config::StarknetConfig;
use self::storage::Blockchain;
use crate::accounts::{Account, DevAccountGenerator};
use crate::constants::DEFAULT_PREFUNDED_ACCOUNT_BALANCE;
use crate::env::{BlockContextGenerator, Env};
use crate::service::block_producer::MinedBlockOutcome;
use crate::constants::{DEFAULT_PREFUNDED_ACCOUNT_BALANCE, FEE_TOKEN_ADDRESS, MAX_RECURSION_DEPTH};
use crate::env::{get_default_vm_resource_fee_cost, BlockContextGenerator};
use crate::service::block_producer::{BlockProductionError, MinedBlockOutcome};
use crate::utils::get_current_timestamp;

pub struct Backend {
/// The config used to generate the backend.
pub config: RwLock<StarknetConfig>,
pub config: StarknetConfig,
/// stores all block related data in memory
pub blockchain: Blockchain,
/// The chain id.
pub chain_id: ChainId,
/// The chain environment values.
pub env: Arc<RwLock<Env>>,
/// The block context generator.
pub block_context_generator: RwLock<BlockContextGenerator>,
/// Prefunded dev accounts
pub accounts: Vec<Account>,
}

impl Backend {
pub async fn new(config: StarknetConfig) -> Self {
let mut block_context = config.block_context();
let mut block_env = config.block_env();
let block_context_generator = config.block_context_generator();

let accounts = DevAccountGenerator::new(config.total_accounts)
Expand Down Expand Up @@ -80,11 +77,9 @@ impl Backend {
panic!("block to be forked is a pending block")
};

block_context.block_number = BlockNumber(block.block_number);
block_context.block_timestamp = BlockTimestamp(block.timestamp);
block_context.sequencer_address = ContractAddress(block.sequencer_address).into();
block_context.chain_id =
starknet_api::core::ChainId(parse_cairo_short_string(&forked_chain_id).unwrap());
block_env.number = block.block_number;
block_env.timestamp = block.timestamp;
block_env.sequencer_address = block.sequencer_address.into();

trace!(
target: "backend",
Expand All @@ -98,7 +93,7 @@ impl Backend {
ForkedProvider::new(provider, forked_block_num.into()).unwrap(),
block.block_hash,
block.parent_hash,
&block_context,
&block_env,
block.new_root,
match block.status {
BlockStatus::AcceptedOnL1 => FinalityStatus::AcceptedOnL1,
Expand All @@ -110,14 +105,12 @@ impl Backend {

(blockchain, forked_chain_id.into())
} else {
let blockchain = Blockchain::new_with_genesis(InMemoryProvider::new(), &block_context)
let blockchain = Blockchain::new_with_genesis(InMemoryProvider::new(), &block_env)
.expect("able to create blockchain from genesis block");

(blockchain, config.env.chain_id)
};

let env = Env { block: block_context };

for acc in &accounts {
acc.deploy_and_fund(blockchain.provider())
.expect("should be able to deploy and fund dev account");
Expand All @@ -127,8 +120,7 @@ impl Backend {
chain_id,
accounts,
blockchain,
config: RwLock::new(config),
env: Arc::new(RwLock::new(env)),
config,
block_context_generator: RwLock::new(block_context_generator),
}
}
Expand All @@ -139,38 +131,38 @@ impl Backend {
/// is running in `interval` mining mode.
pub fn mine_pending_block(
&self,
block_env: &BlockEnv,
tx_receipt_pairs: Vec<(TxWithHash, Receipt)>,
state_updates: StateUpdatesWithDeclaredClasses,
) -> (MinedBlockOutcome, Box<dyn StateProvider>) {
let block_context = self.env.read().block.clone();
let outcome = self.do_mine_block(block_context, tx_receipt_pairs, state_updates);
let new_state = StateFactoryProvider::latest(&self.blockchain.provider()).unwrap();
(outcome, new_state)
) -> Result<(MinedBlockOutcome, Box<dyn StateProvider>), BlockProductionError> {
let outcome = self.do_mine_block(block_env, tx_receipt_pairs, state_updates)?;
let new_state = StateFactoryProvider::latest(&self.blockchain.provider())?;
Ok((outcome, new_state))
}

pub fn do_mine_block(
&self,
block_context: BlockContext,
block_env: &BlockEnv,
tx_receipt_pairs: Vec<(TxWithHash, Receipt)>,
state_updates: StateUpdatesWithDeclaredClasses,
) -> MinedBlockOutcome {
) -> Result<MinedBlockOutcome, BlockProductionError> {
let (txs, receipts): (Vec<TxWithHash>, Vec<Receipt>) = tx_receipt_pairs.into_iter().unzip();

let prev_hash = BlockHashProvider::latest_hash(self.blockchain.provider()).unwrap();
let prev_hash = BlockHashProvider::latest_hash(self.blockchain.provider())?;

let partial_header = PartialHeader {
parent_hash: prev_hash,
version: CURRENT_STARKNET_VERSION,
timestamp: block_context.block_timestamp.0,
sequencer_address: block_context.sequencer_address.into(),
timestamp: block_env.timestamp,
sequencer_address: block_env.sequencer_address,
gas_prices: GasPrices {
eth_gas_price: block_context.gas_prices.eth_l1_gas_price.try_into().unwrap(),
strk_gas_price: block_context.gas_prices.strk_l1_gas_price.try_into().unwrap(),
eth: block_env.l1_gas_prices.eth,
strk: block_env.l1_gas_prices.strk,
},
};

let tx_count = txs.len();
let block_number = block_context.block_number.0;
let block_number = block_env.number;

let header = Header::new(partial_header, block_number, FieldElement::ZERO);
let block = Block { header, body: txs }.seal();
Expand All @@ -181,17 +173,15 @@ impl Backend {
block,
state_updates,
receipts,
)
.unwrap();
)?;

info!(target: "backend", "⛏️ Block {block_number} mined with {tx_count} transactions");

MinedBlockOutcome { block_number }
Ok(MinedBlockOutcome { block_number })
}

pub fn update_block_context(&self) {
pub fn update_block_env(&self, block_env: &mut BlockEnv) {
let mut context_gen = self.block_context_generator.write();
let block_context = &mut self.env.write().block;
let current_timestamp_secs = get_current_timestamp().as_secs() as i64;

let timestamp = if context_gen.next_block_start_time == 0 {
Expand All @@ -203,14 +193,85 @@ impl Backend {
timestamp
};

block_context.block_number = block_context.block_number.next();
block_context.block_timestamp = BlockTimestamp(timestamp);
block_env.number += 1;
block_env.timestamp = timestamp;
}

/// Retrieves the chain configuration environment values.
pub(crate) fn chain_cfg_env(&self) -> CfgEnv {
CfgEnv {
chain_id: self.chain_id,
vm_resource_fee_cost: get_default_vm_resource_fee_cost(),
invoke_tx_max_n_steps: self.config.env.invoke_max_steps,
validate_max_n_steps: self.config.env.validate_max_steps,
max_recursion_depth: MAX_RECURSION_DEPTH,
fee_token_addresses: FeeTokenAddressses {
eth: (*FEE_TOKEN_ADDRESS),
strk: Default::default(),
},
}
}

pub fn mine_empty_block(
&self,
block_env: &BlockEnv,
) -> Result<MinedBlockOutcome, BlockProductionError> {
self.do_mine_block(block_env, Default::default(), Default::default())
}
}

#[cfg(test)]
mod tests {
use katana_provider::traits::block::{BlockNumberProvider, BlockProvider};
use katana_provider::traits::env::BlockEnvProvider;

use super::Backend;
use crate::backend::config::{Environment, StarknetConfig};

fn create_test_starknet_config() -> StarknetConfig {
StarknetConfig {
seed: [0u8; 32],
total_accounts: 2,
disable_fee: true,
env: Environment::default(),
..Default::default()
}
}

async fn create_test_backend() -> Backend {
Backend::new(create_test_starknet_config()).await
}

/// Updates the block context and mines an empty block.
pub fn mine_empty_block(&self) -> MinedBlockOutcome {
self.update_block_context();
let block_context = self.env.read().block.clone();
self.do_mine_block(block_context, Default::default(), Default::default())
#[tokio::test]
async fn test_creating_blocks() {
let backend = create_test_backend().await;

let provider = backend.blockchain.provider();

assert_eq!(BlockNumberProvider::latest_number(provider).unwrap(), 0);

let block_num = provider.latest_number().unwrap();
let mut block_env = provider.block_env_at(block_num.into()).unwrap().unwrap();
backend.update_block_env(&mut block_env);
backend.mine_empty_block(&block_env).unwrap();

let block_num = provider.latest_number().unwrap();
let mut block_env = provider.block_env_at(block_num.into()).unwrap().unwrap();
backend.update_block_env(&mut block_env);
backend.mine_empty_block(&block_env).unwrap();

let block_num = provider.latest_number().unwrap();
let block_env = provider.block_env_at(block_num.into()).unwrap().unwrap();

assert_eq!(BlockNumberProvider::latest_number(provider).unwrap(), 2);
assert_eq!(block_env.number, 2);

let block0 = BlockProvider::block_by_number(provider, 0).unwrap().unwrap();
let block1 = BlockProvider::block_by_number(provider, 1).unwrap().unwrap();
let block2 = BlockProvider::block_by_number(provider, 2).unwrap().unwrap();

assert_eq!(block0.header.number, 0);
assert_eq!(block1.header.number, 1);
assert_eq!(block2.header.number, 2);
}
}
Loading
Loading