Skip to content

Commit

Permalink
refactor(katana): historical block execution context (#1436)
Browse files Browse the repository at this point in the history
Resolves #1347
  • Loading branch information
kariy committed Jan 17, 2024
1 parent b470a09 commit 3287ffa
Show file tree
Hide file tree
Showing 27 changed files with 568 additions and 386 deletions.
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

0 comments on commit 3287ffa

Please sign in to comment.