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

Abort blocks #388

Merged
merged 38 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d675769
refactor to hash_to_state_diff, hash_to_state
Mar 7, 2024
f67b651
StarknetBlocks struct change
Mar 7, 2024
2461ef7
apply format
Mar 7, 2024
37efd3c
clippy fix
Mar 7, 2024
db1e145
Test on ci/cd
Mar 8, 2024
aa2fdab
add bug fix
Mar 13, 2024
5aff39c
self code review
Mar 13, 2024
1065207
Update mod.rs
Mar 13, 2024
70a4dae
Merge branch 'main' into block-struct-change
Mar 13, 2024
c44b87d
Add fixes after merge
Mar 14, 2024
5b73b1b
Merge branch 'main' into abort-blocks
Mar 15, 2024
5b2db8f
Add simple version and test
Mar 15, 2024
308aa41
Update test_abort_blocks.rs
Mar 15, 2024
f41c352
Add while loop and update tests
Mar 19, 2024
66bc19b
Add revert of txs and add test
Mar 19, 2024
7688726
Add query_aborted_block_by_number_should_fail
Mar 20, 2024
ab5bb26
Add block revert and abort_block_state_revert test
Mar 25, 2024
9b09295
Fix ci/cd
Mar 25, 2024
3223ce5
Add init_state and error with UnsupportedAction
Mar 26, 2024
c52c4da
apply formatting
Mar 26, 2024
46e03ad
Add error and test
Mar 26, 2024
4c9bae5
Update mod.rs
Mar 26, 2024
4c04b1f
some last fixes
Mar 26, 2024
95b1c0c
Update test_abort_blocks.rs
Mar 26, 2024
1807da0
Merge branch 'main' into abort-blocks
Mar 26, 2024
b846b50
merge
Mar 26, 2024
6eb1931
Add doc
Mar 26, 2024
80922ab
Add block_number update and edit test
Mar 27, 2024
9559e5e
starting_block_hash as snake_case
Mar 27, 2024
ace808f
Fix of next_block_to_abort_hash
Mar 27, 2024
e312071
Merge branch 'main' into abort-blocks
Mar 27, 2024
8f1af63
change posts to create_block() function
Mar 27, 2024
684d7ea
Add utility function abort_blocks
Mar 27, 2024
c48ff0c
Add assert_block_rejected
Mar 27, 2024
94be08b
add use of assert_tx_reverted
Mar 27, 2024
1c0ad1e
Fix of assert_tx_reverted and change of take from clonme
Mar 27, 2024
a2eeb56
change aborted_blocks assert
Mar 27, 2024
89684f3
add abort_same_block_twice test
Mar 27, 2024
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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,33 @@ Response:

{'block_hash': '0x115e1b390cafa7942b6ab141ab85040defe7dee9bef3bc31d8b5b3d01cc9c67'}

### Abort blocks

This functionality allows to simulate block abortion that can occur on mainnet.

You can abort blocks and revert transactions from the specified block to the currently latest block. Newly created blocks after the abortion will have accepted status and will continue with numbering where the last accepted block left off.

The state of Devnet will be reverted to the state of the last accepted block.

E.g. assume there are 3 accepted blocks numbered 1, 2 and 3. Upon receiving a request to abort blocks starting with block 2, the blocks numbered 2 and 3 are aborted and their transactions reverted. The state of network will be as it was in block 1. Once a new block is mined, it will be accepted and it will have number 2.

Aborted blocks can only be queried by block hash. Aborting the blocks in forking origin and already aborted blocks is not supported and results in an error.

```
POST /abort_blocks
{
"starting_block_hash": BLOCK_HASH
}
```

Response:

```
{
"aborted": [BLOCK_HASH_0, BLOCK_HASH_1, ...]
}
```

## Advancing time

Block timestamp can be manipulated by setting the exact time or setting the time offset. By default, timestamp methods `/set_time` and `/increase_time` generate a new block. This can be changed for `/set_time` by setting the optional parameter `generate_block` to `false`. This skips immediate new block generation, but will use the specified timestamp whenever the next block is supposed to be generated.
Expand Down
2 changes: 2 additions & 0 deletions crates/starknet-devnet-core/src/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub(crate) struct StarknetBlocks {
pub(crate) last_block_hash: Option<BlockHash>,
pub(crate) hash_to_state_diff: HashMap<BlockHash, StateDiff>,
pub(crate) hash_to_state: HashMap<BlockHash, StarknetState>,
pub(crate) aborted_blocks: Vec<Felt>,
}

impl HashIdentified for StarknetBlocks {
Expand All @@ -45,6 +46,7 @@ impl Default for StarknetBlocks {
last_block_hash: None,
hash_to_state_diff: HashMap::new(),
hash_to_state: HashMap::new(),
aborted_blocks: Vec::new(),
}
}
}
Expand Down
79 changes: 77 additions & 2 deletions crates/starknet-devnet-core/src/starknet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use starknet_api::block::{BlockNumber, BlockStatus, BlockTimestamp, GasPrice, Ga
use starknet_api::core::SequencerContractAddress;
use starknet_api::transaction::Fee;
use starknet_rs_core::types::{
BlockId, MsgFromL1, TransactionExecutionStatus, TransactionFinalityStatus,
BlockId, ExecutionResult, MsgFromL1, TransactionExecutionStatus, TransactionFinalityStatus,
};
use starknet_rs_core::utils::get_selector_from_name;
use starknet_rs_ff::FieldElement;
Expand Down Expand Up @@ -88,6 +88,8 @@ pub(crate) mod transaction_trace;

pub struct Starknet {
pub(in crate::starknet) state: StarknetState,
pub(in crate::starknet) init_state: StarknetState, /* This will be refactored during the
* genesis block PR */
predeployed_accounts: PredeployedAccounts,
pub(in crate::starknet) block_context: BlockContext,
// To avoid repeating some logic related to blocks,
Expand All @@ -113,6 +115,7 @@ impl Default for Starknet {
DEVNET_DEFAULT_STARTING_BLOCK_NUMBER,
),
state: Default::default(),
init_state: Default::default(),
predeployed_accounts: Default::default(),
blocks: Default::default(),
transactions: Default::default(),
Expand Down Expand Up @@ -184,6 +187,7 @@ impl Starknet {
config.fork_config.block_number.map_or(DEVNET_DEFAULT_STARTING_BLOCK_NUMBER, |n| n + 1);
let mut this = Self {
state,
init_state: StarknetState::default(),
predeployed_accounts,
block_context: Self::init_block_context(
config.gas_price,
Expand All @@ -204,6 +208,10 @@ impl Starknet {

this.restart_pending_block()?;

// Set init_state for abort blocks functionality
// This will be refactored during the genesis block PR
this.init_state = this.state.clone_historic();

// Load starknet transactions
if this.config.dump_path.is_some() && this.config.re_execute_on_init {
// Try to load transactions from dump_path, if there is no file skip this step
Expand Down Expand Up @@ -266,7 +274,9 @@ impl Starknet {
new_block.set_timestamp(block_timestamp);
Self::update_block_context_block_timestamp(&mut self.block_context, block_timestamp);

let new_block_number = new_block.block_number();
let new_block_number =
BlockNumber(new_block.block_number().0 - self.blocks.aborted_blocks.len() as u64);
new_block.header.block_number = new_block_number;
let new_block_hash: Felt = new_block.header.block_hash.0.into();

// update txs block hash block number for each transaction in the pending block
Expand Down Expand Up @@ -768,6 +778,71 @@ impl Starknet {
state_update::state_update_by_block_id(self, block_id)
}

pub fn abort_blocks(&mut self, starting_block_hash: Felt) -> DevnetResult<Vec<Felt>> {
if self.config.state_archive != StateArchiveCapacity::Full {
return Err(Error::UnsupportedAction {
msg: ("The abort blocks feature requires state-archive-capacity set to full."
.into()),
});
}

if self.blocks.aborted_blocks.contains(&starting_block_hash) {
return Err(Error::UnsupportedAction { msg: "Block is already aborted".into() });
}

let mut next_block_to_abort_hash = self
.blocks
.last_block_hash
.ok_or(Error::UnsupportedAction { msg: "No blocks to abort".into() })?;
let mut reached_starting_block = false;
let mut aborted: Vec<Felt> = Vec::new();

// Abort blocks from latest to starting (iterating backwards) and revert transactions.
while !reached_starting_block {
reached_starting_block = next_block_to_abort_hash == starting_block_hash;
let block_to_abort = self.blocks.hash_to_block.get_mut(&next_block_to_abort_hash);

if let Some(block) = block_to_abort {
block.status = BlockStatus::Rejected;
self.blocks.num_to_hash.shift_remove(&block.block_number());

// Revert transactions
for tx_hash in block.get_transactions() {
let tx =
self.transactions.get_by_hash_mut(tx_hash).ok_or(Error::NoTransaction)?;
tx.execution_result =
ExecutionResult::Reverted { reason: "Block aborted manually".to_string() };
}

aborted.push(block.block_hash());

// Update next block hash to abort
next_block_to_abort_hash = block.parent_hash();
}
}
let last_reached_block_hash = next_block_to_abort_hash;

// Update last_block_hash based on last reached block and revert state only if
// starting block is reached in while loop.
if last_reached_block_hash == Felt::from(0) && reached_starting_block {
self.blocks.last_block_hash = None;
self.state = self.init_state.clone_historic(); // This will be refactored during the genesis block PR
} else if reached_starting_block {
let current_block =
self.blocks.hash_to_block.get(&last_reached_block_hash).ok_or(Error::NoBlock)?;
self.blocks.last_block_hash = Some(current_block.block_hash());

let reverted_state = self.blocks.hash_to_state.get(&current_block.block_hash()).ok_or(
Error::NoStateAtBlock { block_id: BlockId::Number(current_block.block_number().0) },
)?;
self.state = reverted_state.clone_historic();
}

self.blocks.aborted_blocks = aborted.clone();

Ok(aborted)
}

pub fn get_block_txs_count(&self, block_id: &BlockId) -> DevnetResult<u64> {
let block = self.blocks.get_by_block_id(block_id).ok_or(Error::NoBlock)?;

Expand Down
2 changes: 1 addition & 1 deletion crates/starknet-devnet-core/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl StarknetState {
GlobalContractCache::new(GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST),
),
rpc_contract_classes: self.rpc_contract_classes.clone(),
historic_state: None,
historic_state: Some(self.historic_state.as_ref().unwrap().clone()),
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions crates/starknet-devnet-server/src/api/http/endpoints/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ pub async fn create_block(
}
}

pub async fn abort_blocks(Json(_data): Json<AbortingBlocks>) -> HttpApiResult<Json<AbortedBlocks>> {
Err(HttpApiError::GeneralError("Unimplemented".into()))
pub async fn abort_blocks(
Json(data): Json<AbortingBlocks>,
Extension(state): Extension<HttpApiHandler>,
) -> HttpApiResult<Json<AbortedBlocks>> {
let mut starknet = state.api.starknet.write().await;
let aborted = starknet
.abort_blocks(data.starting_block_hash)
.map_err(|err| HttpApiError::BlockAbortError { msg: (err.to_string()) })?;

Ok(Json(AbortedBlocks { aborted }))
}
5 changes: 5 additions & 0 deletions crates/starknet-devnet-server/src/api/http/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub enum HttpApiError {
BlockSetTimeError { msg: String },
#[error("The increase time operation failed: {msg}")]
BlockIncreaseTimeError { msg: String },
#[error("The block abortion failed: {msg}")]
BlockAbortError { msg: String },
#[error("Could not restart: {msg}")]
RestartError { msg: String },
#[error("Messaging error: {msg}")]
Expand All @@ -39,6 +41,9 @@ impl IntoResponse for HttpApiError {
err @ HttpApiError::FileNotFound => (StatusCode::BAD_REQUEST, err.to_string()),
err @ HttpApiError::DumpError { msg: _ } => (StatusCode::BAD_REQUEST, err.to_string()),
err @ HttpApiError::LoadError => (StatusCode::BAD_REQUEST, err.to_string()),
err @ HttpApiError::BlockAbortError { msg: _ } => {
(StatusCode::BAD_REQUEST, err.to_string())
}
err @ HttpApiError::ReExecutionError => (StatusCode::BAD_REQUEST, err.to_string()),
err @ HttpApiError::CreateEmptyBlockError { msg: _ } => {
(StatusCode::BAD_REQUEST, err.to_string())
Expand Down
5 changes: 2 additions & 3 deletions crates/starknet-devnet-server/src/api/http/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,12 @@ pub struct CreatedBlock {

#[derive(Deserialize)]
pub struct AbortingBlocks {
#[serde(rename = "startingBlockHash")]
starting_block_hash: BlockHash,
pub(crate) starting_block_hash: BlockHash,
}

#[derive(Serialize)]
pub struct AbortedBlocks {
aborted: Vec<BlockHash>,
pub(crate) aborted: Vec<BlockHash>,
}

#[derive(Deserialize)]
Expand Down
3 changes: 2 additions & 1 deletion crates/starknet-devnet/tests/common/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::sync::Arc;

use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass;
use hyper::{Body, Response};
use server::test_utils::exported_test_utils::assert_contains;
use starknet_core::random_number_generator::generate_u32_random_number;
use starknet_rs_accounts::{Account, SingleOwnerAccount};
use starknet_rs_contract::ContractFactory;
Expand Down Expand Up @@ -127,7 +128,7 @@ pub async fn assert_tx_reverted<T: Provider>(
match receipt.execution_result() {
ExecutionResult::Reverted { reason } => {
for expected_reason in expected_failure_reasons {
reason.contains(expected_reason);
assert_contains(reason, expected_reason);
}
}
other => panic!("Should have reverted; got: {other:?}; receipt: {receipt:?}"),
Expand Down
Loading