GithubHelp home page GithubHelp logo

starkware-libs / blockifier Goto Github PK

View Code? Open in Web Editor NEW
161.0 161.0 94.0 14.25 MB

Blockifier is a Rust implementation for the transaction-executing component in the StarkNet sequencer, in charge of creating state diffs and blocks.

License: Apache License 2.0

Rust 92.23% Cairo 6.69% Shell 0.02% Python 0.79% Starlark 0.12% JavaScript 0.16%

blockifier's People

Contributors

adi-yakovian avatar alon-dotan-starkware avatar alonh5 avatar amosstarkware avatar aner-starkware avatar arnistarkware avatar avi-starkware avatar ayeletstarkware avatar barak-b-starkware avatar bartekryba avatar dafnamatsry avatar dorimedini-starkware avatar elintul avatar giladchase avatar ilyalesokhin-starkware avatar liorgold2 avatar lucaslvy avatar meship-starkware avatar mohammadnassar1 avatar nimrod-starkware avatar nirlevi-starkware avatar noaov1 avatar oristarkware avatar spapinistarkware avatar tdelabro avatar tzahitaub avatar yael-starkware avatar yoni-starkware avatar yuvalsw avatar zuphitf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

blockifier's Issues

`AccountTransaction` should impl `From` for each one of it's variant

Right now if I want to create and Transaction from an InvokeTransaction I have to write this code:

Transaction::AccountTransaction(AccountTransaction::Invoke(transaction))

This is overly verbose.
From<InvokeTransaction> for AccountTransaction and From<InvokeTransaction> for Transaction are easy to implement and will make the code more straightforward.
Same for all other tx types

Impl serde::Serialize Deserialize for ContractClass

Hi all,

I'm writing an issue here in relation with Katana, the Rust / Dojo devnet for Starknet -> dojoengine/dojo#370

I'm currently trying to implement a feature for Katana Rust Devnet (reading from dumps and writing to dumps the state of the local chain).

This requires to me to implement serde::Serialize and Deserialize for structs that define the state of the local chain:
Here is the structure of a local chain:

pub struct StarknetConfig {
    pub seed: [u8; 32],
    pub gas_price: u128,
    pub chain_id: String,
    pub total_accounts: u8,
    pub blocks_on_demand: bool,
    pub allow_zero_max_fee: bool,
    pub account_path: Option<PathBuf>,
}

pub struct StarknetWrapper {
    pub config: StarknetConfig,
    pub blocks: StarknetBlocks,
    pub block_context: BlockContext,
    pub transactions: StarknetTransactions,
    pub state: DictStateReader,
    pub predeployed_accounts: PredeployedAccounts,
    pub pending_state: CachedState<DictStateReader>,
}

I'm hitting a blocker for ContractClass,
Here is my error:

the trait bound `blockifier::execution::contract_class::ContractClass: Deserialize<'_>` is not satisfied
the following other types implement trait `Deserialize<'de>`:
  <&'a Path as Deserialize<'de>>
  <&'a [u8] as Deserialize<'de>>
  <&'a serde_json::value::RawValue as Deserialize<'de>>
  <&'a str as Deserialize<'de>>
  <() as Deserialize<'de>>
  <(T0, T1) as Deserialize<'de>>
  <(T0, T1, T2) as Deserialize<'de>>
  <(T0, T1, T2, T3) as Deserialize<'de>>
and 611 others
required for `HashMap<starknet_api::core::ClassHash, blockifier::execution::contract_class::ContractClass>` to implement `Deserialize<'_>`

I'm guessing it comes from here:

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ContractClass {
    V0(ContractClassV0),
    V1(ContractClassV1),
}

I think the inner ContractClassV0 does implement Deserialize, but ContractClassV1does not.
Is there a plan to implement Deserialize on ContractClassV1? Is there a blocker for it?

Additionally, I'm not too familiar with serde, do we not need to implement Serialize as well for those structs?

Thanks, have a good day!
PS: If you have a better idea on how to implement persistent state between local chain runs, happy to hear it.

add common getters for all tx types

https://github.com/keep-starknet-strange/madara/blob/main/crates/primitives/transactions/src/getters.rs#L51

In madara we added a set of common getters for all our tx types.
You should do that for the one defined in blockifier too.
It would avoid lib users having to write this type of bad boilerplate code all the time:

            let sender_address = match &transaction.tx {
                starknet_api::transaction::InvokeTransaction::V0(tx) => tx.contract_address,
                starknet_api::transaction::InvokeTransaction::V1(tx) => tx.sender_address,
                starknet_api::transaction::InvokeTransaction::V3(tx) => tx.sender_address,
            };

This should be a trait.

`EntryPointExecutionContext::new_invoke` is a crazy misleading name

    pub fn new_validate(
        tx_context: Arc<TransactionContext>,
        limit_steps_by_resources: bool,
    ) -> TransactionExecutionResult<Self> {
        Self::new(tx_context, ExecutionMode::Validate, limit_steps_by_resources)
    }

    pub fn new_invoke(
        tx_context: Arc<TransactionContext>,
        limit_steps_by_resources: bool,
    ) -> TransactionExecutionResult<Self> {
        Self::new(tx_context, ExecutionMode::Execute, limit_steps_by_resources)
    }

It should be new_execute we are talking about execution modes that can be "validate" or "execute".
Invoke refers to a transaction type, not an execution mode.

Add a "Get Started"

It would be useful to add a "Get Started" section to the readme.
As per this blog-post, the Blockifier sequencer is used in production, but I can't deploy it easily in a test environment.

Add a `State::set_nonce_at` method

When using a transactional state you end up with state changes that include an address nonce going from value n to value n + x, where x can be a number between n and a lot.

The State trait only exposes the increment_nonce method to interact with this specific storage. This means that if I want to apply my state changes to my actual storage I will have to keep calling increment_nonce inside a loop until it reaches the correct value. This is a very bad API. A set_nonce_at method should be exposed in order to set the new value in a single write.

`ExecutableTransaction` should be implemented on every tx type

Right now ExecutableTransaction is only implemented on Transaction, AccountTransaction and L1HandlerTransaction.

It forces me to wrap my InvokeTransaction in an AccoutTransaction to execute it.

This has no reason to be the case, ExecutableTransaction should be implemented on InvokeTransaction as well as DeclareTransaction and DeployAccountTransaction.

update the roadmap

hard to know the status of the component when it seems the roadmap is quite out of date

Unlock `serde` version

Is it possible to unlock serde version? Now it's locked to 1.0.171, while the min version of serde in Scarb dependencies is 1.0.183, which makes scarb and blockifier unable to work together.

`Transaction` enum's variant name are too verbose

pub enum Transaction {
    AccountTransaction(AccountTransaction),
    L1HandlerTransaction(L1HandlerTransaction),
}

could be

pub enum Transaction {
    Account(AccountTransaction),
    L1Handler(L1HandlerTransaction),
}

without losing any information.

Remove any usage of rust floats

Floating points are non-deterministic, making them unsuitable for any blockchain usage as the same block, executed on two different computers, could lead to different states.
Moreover, they are currently only used to store ethereum gas price, which has a fixed 18 decimal precision.

Therefore all floats should be replaced by Fixed point numbers.

Ressources

https://langdev.stackexchange.com/questions/665/what-are-the-pros-and-cons-of-fixed-point-arithmetics-vs-floating-point-arithmet
https://substrate.stackexchange.com/questions/146/can-you-use-floating-point-numbers-or-math-in-the-runtime

Solution

We could use substrate FixedU128 type instead. It has a precision of exactly 18 decimal, is no_std compatible, and comes with a bunch of math methods (ceil, floor, saturating and checked mul, etc) out of the box.

Here is the doc:
https://paritytech.github.io/polkadot-sdk/master/sp_runtime/struct.FixedU128.html

I'm doing it in our fork because it causes direct problems with Substrate. Would you like for me to also do a PR on main?

Why does `ClassInfo::new` takes a reference to `ContractClass` as argument?

    pub fn new(
        contract_class: &ContractClass,
        sierra_program_length: usize,
        abi_length: usize,
    ) -> ContractClassResult<Self> {
        let (contract_class_version, condition) = match contract_class {
            ContractClass::V0(_) => (0, sierra_program_length == 0),
            ContractClass::V1(_) => (1, sierra_program_length > 0),
        };

        if condition {
            Ok(Self { contract_class: contract_class.clone(), sierra_program_length, abi_length })
        } else {
            Err(ContractClassError::ContractClassVersionSierraProgramLengthMismatch {
                contract_class_version,
                sierra_program_length,
            })
        }
    }

The method takes contract_class: &ContractClass and then clone it.
I think it should be contract_class: ContractClass because it would spare one clone. The ContractClass should be taking ownership of the ContractClass. The current implem adds an unnecessary clone if the lib a caller has a ContractClass for which he doesn't want to keep ownership after the call to ClassInfo::new.

The only reason I see to have the current implem is to spare a clone (in the case the lib caller needs to clone, which we can't know for sure) and that the conditions are not met, making ClassInfo::new return an error.

Even given this, I don't think we should arbitrage in this direction. It looks like a bad design to take a ref and later clone. If you end up needing the value ownership, take it from the get-go

Why does `StateReader::get_contract_class` has to return an `Arc` to the value?

https://github.com/starkware-libs/blockifier/blob/main/crates/blockifier/src/state/state_api.rs#L35

    /// Returns the contract class of the given class hash.
    fn get_contract_class(&mut self, class_hash: &ClassHash) -> StateResult<Arc<ContractClass>>;

Why does it have to be an Arc, it does not make sense with the storage we are using at Madara.
It looks like an optimization for the storage you already use, but that may not be generalizable to all possible storage.

Wdyt?

in response to:Impl serde::Serialize Deserialize for ContractClass #550

You're facing challenges while implementing serde serialization and deserialization for your Rust structs, primarily related to the ContractClass enum and its variants. Here's a mini explanation of the issues as i understood:

The error message indicates that the ContractClass enum isn't correctly implementing the Deserialize trait. This trait is crucial for deserializing the enum from a serialized format. While the error message mentions that some types implement Deserialize, it's not properly implemented for ContractClassV1.

[Implementing Deserialize for ContractClassV1+] To address this issue, you need to manually implement the Deserialize trait for the ContractClassV1 variant of your ContractClass enum. This allows you to handle specific deserialization logic for ContractClassV1.

Example:

use serde::Deserialize;

impl<'de> Deserialize<'de> for ContractClassV1 {
fn deserialize(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Implement the deserialization logic here.
}

To use serde for both serialization and deserialization, it's essential to implement the Serialize trait for your structs. The Serialize trait defines how your data structures are serialized into formats like JSON.

This should work:

use serde::Serialize;

#[derive(Serialize)]
pub struct StarknetConfig {
// Fields of StarknetConfig
}
For a more efficient approach to maintain state data between local chain runs, consider using a database or a file system for storage and retrieval. Rust libraries such as sled are popular for key-value storage, and the standard std::fs module can be used for file-based storage. Storing state in memory and serializing/deserializing as needed may not be optimal for long-term persistence.

If you're working on an open-source project like Katana, collaborating with the project maintainers or community is valuable. They can provide guidance on addressing these issues and may have plans to implement Deserialize for ContractClassV1.

consult the serde documentation (https://serde.rs/).
lmk if you have more questions

Return actual fee from `L1Handler`'s `execute_raw`

Right now, the default fee is returned for all successful executions, even if the calculated actual_fee differs.

Callers can get the actual fee by forcing an error (setting fee_paid_on_l1 to zero), but that is kind of hacky and relies on more implementation details than a caller should be responsible for knowing.

Can the actual fee be returned in all cases?

Overflow in fee calculation?

It appears that a Fee is a 128-bit uint, when in reality it is a field element. This can lead to overflows. I think if this TODO is addressed, the problem will be resolved.

Example

When testing starknet_estimateFee in Juno, we get a MaxFeeExceedsBalance error when executing the following request on goerli 2 (it matches this real transaction):

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "starknet_estimateFee",
  "params": {
    "request": [
      {
        "type": "INVOKE",
        "version": "0x1",
        "nonce": "0x4277",
        "max_fee": "0x99999999995330000",
        "sender_address": "0x71fe822a321b057cfd4f1cc2cbb9524818137480d9ce200840d5dae8844d61e",
        "signature": [
          "0x1475f3f2b7460b0c607c4915da74d7ee1d3b05d4a43dad6deb389fc2d68bf19",
          "0x52ca914c31d3ed1294134f0381f0f2fce1290dbda848b376cee815038c8113b"
        ],
        "calldata": [
          "0x1",
          "0x690c876e61beda61e994543af68038edac4e1cb1990ab06e52a2d27e56a1232",
          "0x1f24f689ced5802b706d7a2e28743fe45c7bfa37431c97b1c766e9622b65573",
          "0x0",
          "0x9",
          "0x9",
          "0x4",
          "0x4254432d55534443",
          "0xf31095527ba2e39a43c",
          "0x5754492d55534443",
          "0x90415eabb488ba21a",
          "0x4554482d55534443",
          "0xf2df1f0f62ebd4f22d",
          "0x4a50592d55534443",
          "0x38cdc6cb3fa9c5"
        ]
      }
    ],
    "block_id": {
      "block_number": 123386
    }
  }
}

Response:

{
    "jsonrpc": "2.0",
    "error": {
        "code": 40,
        "message": "Contract error",
        "data": "failed txn TransactionHash(StarkFelt(\"0x01d666348843226d664d7d86aa6716225eb537d2ba66b34e829e4eaeded4df39\")) reason:MaxFeeExceedsBalance { max_fee: Fee(17708874310753786
2656), balance_low: StarkFelt(\"0x0000000000000000000000000000000000000000000000008825fe6e9e898a2d\"), balance_high: StarkFelt(\"0x00000000000000000000000000000000000000000000000000000000
00000000\") }"
    },
    "id": 1
}

Why are we limiting to `MAX_STEPS_PER_TX` if `max_fee` is `0`

In the following line, we are limiting the maximum invoke step to MAX_STEPS_PER_TX { which is 4 million } regardless of whatever it is in block_context.invoke_tx_max_n_steps, I am interested in understanding why are we doing this?

Code snippet:

 min(constants::MAX_STEPS_PER_TX, block_context.invoke_tx_max_n_steps as usize)

A lot of custom implementation of sequencers on top of blockifier will be interested in doing more compute than what is possible on default starknet.

There is already an issue for this on the dojo's fork of blockifier, let me know if this needs to be fixed, I can take up this issue if it helps.

`Operation failed: 22:0 + 2, maximum offset value exceeded` error while executing a transaction

Please see lambdaclass/cairo-vm#1246 for details.

It seems that I'm unable to run this transaction with blockifier -- but it works just fine when using starknet-in-rust:

With blockifier:

2023-06-19T14:04:54.707323Z ERROR execute: pathfinder_rpc::cairo::blockifier: Transaction execution failed error=Transaction validation has failed. source=Error in the called contract (0x002c133a0c1b78b0b16cf787bfeeb6a8978ef26e9d97a993cc7ffce9d192a313):
Error at pc=0:4418:
Operation failed: 22:0 + 2, maximum offset value exceeded
Cairo traceback (most recent call last):
Unknown location (pc=0:1498)
Unknown location (pc=0:3561)
 transaction_hash=0x00AE9AE3059D3D85F2AE7F30EF703838C91BC7AC96B69B0606E05B41EB228946

vs starknet-in-rust:

2023-06-19T14:07:51.896777Z TRACE execute: pathfinder_rpc::cairo::starknet_rs: Transaction execution finished actual_fee=6237633823169988 transaction_hash=0x00AE9AE3059D3D85F2AE7F30EF703838C91BC7AC96B69B0606E05B41EB228946 block_number=82027

Dangerous `testing` feature

The crate blockifier comes with a testing feature.
Which is supposed to guard user from importing testing methods in their project in release mode. It is supposed only to be imported in [dev-dependencies].

This is dangerous, because those functions are convenient and will inevitably be used in production codebase.

A good example of this is this crate itself.
The dependency starknet-api uses the same design, and is imported as follows starknet_api = { workspace = true, features = ["testing"] } in blockifier. This makes its (very convenient) testing macro available in the core of the crate, not only in tests.
Fortunately, you only used those in the test files so far :)
Another indicator of the dangerousness of such a design is that the testing feature is actually not used anywhere in this crate.
It does not guard anything.
In blockifier/lib.rs, we expect to see the following:

#[cfg(feature = "testing")]
pub mod test_utils;

Instead, there is no guard, and the module test_utils content is exported, available for any lib user to corrupt their program. This is exactly what happened in our own Madara repository, where a contributor used those in the core of the program.

I strongly advise a change in the way test_utils are made available to lib users, because, even with the best intention, the current design will inevitably lead to abuses.

An alternative design is to provide a blockifier-test crate, which should only be imported in the [dev-dependecies]. That is the design used by serde with their serde-test crate.

A second one is to conceal the test_utils module behind a #[cfg(test)] flag, and copy-paste the methods or constants you need in the few other crate that need those.

`StateReader` method should not require `&mut self`

  fn get_fee_token_balance(
        &mut self,
        contract_address: ContractAddress,
        fee_token_address: ContractAddress,
    ) -> Result<(StarkFelt, StarkFelt), StateError> {
        let low_key = get_fee_token_var_address(contract_address);
        let high_key = next_storage_key(&low_key)?;
        let low = self.get_storage_at(fee_token_address, low_key)?;
        let high = self.get_storage_at(fee_token_address, high_key)?;

        Ok((low, high))
    }

StateReader::get_fee_token_balance should not take &mut self, it's the reader API.

Because this one has a default implem it went under my radar on my PR #1325. It has to be fixed asap

Outdated dependencies (esp. cairo-vm)

Blockifier is now quite behind on some dependencies, notably cairo-vm at v0.9.2. Is anyone working on this or planning to? I might volunteer to do so in the near future if not.

`InvokeTransactionV0` no longer Executable

Since ce14cec (yesterday), and this change:

- impl<S: State> Executable<S> for InvokeTransaction {
+ impl<S: State> Executable<S> for InvokeTransactionV1 {
    fn run_execute(
        &self,
        state: &mut S,

Only InvokeTransactionV1 can be executed, which means InvokeTransactionV0 cannot. Is it by design?

`tx_info` should be optional in `TransactionContext`

When doing a RPC call to a specific contract entry point, there is no transaction context, because there is no transaction, just a call. Nevertheless CallEntryPoint::execute method takes an EntryPointExecutionContext as an argument, which contains a TransactionContext which has a field of type TransactionContext.

https://github.com/eqlabs/pathfinder/blob/e4ed0d0f24803bda31dd62c072fa75d7ab5657b3/crates/executor/src/call.rs#L51

As you can see there, the lib users end up passing a default, empty TransactionContext. Pathfinder is doing it, we are doing it too in Madara.
This is a clear sign of a design flaw. Either the execute method should be changed to take two separated args block_context: BlockContext and tx_info: Option<TransactionInfo> or the tx_info fields of the TransactionContext should be changed to be optional.

It then becomes a bit weird to keep this type named TransactionContext, when it is also used for calls, which are not transactions. Maybe ExecutionContext would be better, or EntrypointExecutionContext to be more explicit.

HashMap + iteration == platform specific behaviour

In: https://github.com/starkware-libs/blockifier/blob/e0e8af66481682c2333da1e1a5c428aa82c13a6b/crates/blockifier/src/state/cached_state.rs

#[derive(Debug, Default, IntoIterator)]
pub struct StorageView(pub HashMap<ContractStorageKey, StarkFelt>);

/// Converts a `CachedState`'s storage mapping into a `StateDiff`'s storage mapping.
impl From<StorageView> for IndexMap<ContractAddress, IndexMap<StorageKey, StarkFelt>> {
    fn from(storage_view: StorageView) -> Self {
        let mut storage_updates = Self::new();
        for ((address, key), value) in storage_view.into_iter() {
            storage_updates
                .entry(address)
                .and_modify(|map| {
                    map.insert(key, value);
                })
                .or_insert_with(|| IndexMap::from([(key, value)]));
        }

        storage_updates
    }
}

The order of iteration over a hashmap is platform-specific. Two computers could execute this code and iterate over the key/value tuples in different order. Here this is used to build an IndexMap, a struct for which the order of insertion is extremely important.

This will lead to hard forks and slashing if executed in a decentralized blockchain context.

This specific occurrence should be fixed but I want also to draw things a step further.
To sum hashmaps are okay, as long as we don't iterate over them in a way where the order of the iteration has importance. This is really, really, really error-prone., as no hard error or warning is raised by the language. My recommendation will be to always use BtreeMap or IndexMap. It has to be benchmarked but in my opinion, even a slight slowdown is better than opening our flank to such destructive errors.
This is for every lib in our stack: starknet-api, Blockifier, cairo-rs.

`add_visited_pcs` should not be part of `State` trait

/// Marks the given set of PC values as visited for the given class hash.
// TODO(lior): Once we have a BlockResources object, move this logic there. Make sure reverted
// entry points do not affect the final set of PCs.
fn add_visited_pcs(&mut self, class_hash: ClassHash, pcs: &HashSet<usize>);

As explained in the comment this method should not be part of the State trait.
It makes it weird to implement for any user of the lib. They now have to mirror your architecture otherwise they can't correctly impl the trait.

Can you please remove it from State and put it in another trait that may, or may not be implemented by the same structure?

WASM support

Super excited about the work here! I just wanted to open an issue to flag that wasm support would be incredibly useful for https://github.com/dojoengine/dojo. It would enable us to easily perform optimistic state updates on the client.

bad api design for `DeclareTransaction` creation

#[derive(Debug, Clone, Encode, Decode, Eq, PartialEq)]
#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))]
pub struct DeclareTransaction {
    pub tx: starknet_api::transaction::DeclareTransaction,
    pub tx_hash: TransactionHash,
    // Indicates the presence of the only_query bit in the version.
    only_query: bool,
    pub class_info: ClassInfo,
}

impl DeclareTransaction {
    fn create(
        declare_tx: starknet_api::transaction::DeclareTransaction,
        tx_hash: TransactionHash,
        class_info: ClassInfo,
        only_query: bool,
    ) -> TransactionExecutionResult<Self> {
        let declare_version = declare_tx.version();
        verify_contract_class_version(&class_info.contract_class(), declare_version)?;
        Ok(Self { tx: declare_tx, tx_hash, class_info, only_query })
    }

    pub fn new(
        declare_tx: starknet_api::transaction::DeclareTransaction,
        tx_hash: TransactionHash,
        class_info: ClassInfo,
    ) -> TransactionExecutionResult<Self> {
        Self::create(declare_tx, tx_hash, class_info, false)
    }

    pub fn new_for_query(
        declare_tx: starknet_api::transaction::DeclareTransaction,
        tx_hash: TransactionHash,
        class_info: ClassInfo,
    ) -> TransactionExecutionResult<Self> {
        Self::create(declare_tx, tx_hash, class_info, true)
    }

As you can see, both the only_query field and the create method are private. The lib users cannot use DeclareTransaction {} or DeclareTransaction::create in order to instantiate a DeclareTransaction.
The have to use either new or new_for_query resulting in a code looking like this:

let tx = if only_query {
  DeclareTransaction::new_for_query(declare_tx, tx_hash, class_info)
} else {
  DeclareTransaction::new(declare_tx, tx_hash, class_info)
};

Which is super redundant. At least create should be publicbut probably the fieldonly_query` too. I see no reason to keep it out of reach of libs users, when all the others fields are publics.

Redundant types in api_core

Types like ClassHash, CompiledClassHash, Nonce, EntryPointSelector are just wrappers over StarkHash which is itself an alias for StarkFelt, which is a wrapper over [u8; 32].
They don't implement any specific method and their inner value is pub.

Accessing the underlying value force to do stuff like this

let inner_repr = contract_address.0.0.0;

Which is really ugly, inexpressive, and tedious. It tends to make us uncertain about what we are actually acceding.

For those types that don't add anything, aliasing is enough:

type ClassHash = StarkHash;

Same thing for initialization:

ContractAddress(PatriciaKey(StarkFelt(test_addr))

This a really bad code to write.

Other types like PatriciaKey implement some logic (TryInto for PatriciaKey), therefore they totally require a new type pattern. But I can't help to remark that its inner representation is public:

pub struct PatriciaKey(pub StarkHash);

anybody can change the inner value to whatever it likes, even values that are supposed to be impossible as defined by the TryFrom implem:

fn try_from(value: StarkHash) -> Result<Self, Self::Error> {
        if value < CONTRACT_ADDRESS_DOMAIN_SIZE {
            return Ok(PatriciaKey(value));
        }
        Err(StarknetApiError::OutOfRange { string: format!("[0x0, {PATRICIA_KEY_UPPER_BOUND})") })
    }

The exact same thing happened with the StarkFelt type. Some values are supposed to be not supported:

 pub fn new(bytes: [u8; 32]) -> Result<StarkFelt, StarknetApiError> {
        // msb nibble must be 0. This is not a tight bound.
        if bytes[0] < 0x10 {
            return Ok(Self(bytes));
        }
        Err(StarknetApiError::OutOfRange { string: hex_str_from_bytes::<32, true>(bytes) })
    }

but because its inner representation is public, it is possible to create StarkFelt with unsupported inner values.

The solution for those types is to make their inner representation private, only accessible through getter functions, and only settable through setter functions that perform the required tests.

Wdyt?

`AccountTransaction::handle_fee` should not take `&self` as arg

    fn handle_fee(
        &self,
        state: &mut dyn State,
        tx_context: Arc<TransactionContext>,
        actual_fee: Fee,
        charge_fee: bool,
    ) -> TransactionExecutionResult<Option<CallInfo>> {
        if !charge_fee || actual_fee == Fee(0) {
            // Fee charging is not enforced in some transaction simulations and tests.
            return Ok(None);
        }

        // Charge fee.
        let fee_transfer_call_info = Self::execute_fee_transfer(state, tx_context, actual_fee)?;

        Ok(Some(fee_transfer_call_info))
    }

&self is not used. It can be removed and implemented as a type function, like Self::execute_fee_transfer it internally calls.
Or even as a standalone function as it does not use anything associated with the type like type constants or anything else

stop using `usize` in public types

Types that are publics are part of the protocol.
Using usize in such types means that the protocol accepts different max values depending on the machine on which it is executed.

Example:

pub struct EventSizeLimit {
    pub max_data_length: usize,
    pub max_keys_length: usize,
    pub max_n_emitted_events: usize,
}

On a 32 bit machine, the max_n_emitted_events maximum value will be u32::MAX, on a 64 bit machine the max_n_emitted_events maximum value will be u64::MAX. Meaning that a block produced on a 64 bits machine, could in theory be invalid for all the u32 machines. They will overflow and end up with a different value.

The values exposed by the protocol should always be defined unequivocally, otherwise, we expose a decentralized network of machines to the possibility of a hard fork and loss of consensus.

same issue on the cairo-vm: lambdaclass/cairo-vm#1498
It should be enforced across the starknet rust ecosystem.
Ideally, in a perfect world, some SNIP should decide for each value, if we want it to be a u8, u16, u32, u64, u128, u256, felt252, or something else. But letting the hardware decide for us is never ok.

`TryFrom<&Path>` should not be used

impl TryFrom<&Path> for VersionedConstants {
type Error = VersionedConstantsError;
fn try_from(path: &Path) -> Result<Self, Self::Error> {
Ok(serde_json::from_reader(std::fs::File::open(path)?)?)
}
}

https://doc.rust-lang.org/std/convert/trait.TryFrom.html

TryFrom is for type conversion. The implementation here reads from disk and does json parsing, we are way beyond type conversion. Nothing from the resulting type can be found in the original one. By this logic, any function that takes one single argument and returns another single different type could be implemented as From/TryFrom.

Now, what if I also want to offer a way to create the type from a toml file? I can only have a single implem of TryFrom<&Path> for VersionedConstants, so I have to add it to the existing one? How do I know if I should expect a toml or a json. This is obviously a bad design.
The correct way here is to add a from_json_file method to the impl VersionedConstant block.

Update `starknet-api` to get rid of `web3` dependency

web3 introduces a lot of additional dependencies, one of which is openssl even tho starknet-api doesnt really use any web3 apis that use openssl. I am trying to cross-compile blockifier and openssl dependency is making this really hard.

a recent commit in starknet-api removes web3 dependency.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.