Validate and execute

As previously stated, Starknet’s account structure is not completely arbitrary, but must include the following two functions, which account transactions call:

  • __validate__

  • __execute__

Separating the __validate__ and __execute__ functions guarantees payment to sequencers for work completed and protects them from Denial of Service (DoS) attacks.

The validate function

The __validate__ function ensures that any transaction submitted was indeed initiated by the account owner and therefore will not take up unjustified resources during the execution process.

Without this mechanism, a forged transaction can result in the sequencer stealing the user’s funds. In short, __validate__ ensures that the sequencer may only include transactions that were approved by the account owner.

The arbitrary logic allowed in the __validate__ function gives the account’s designer the ability to determine what it means for a transaction to be valid. This gives rise to the usage of different signature schemes and other exotic accounts.

Invalid transactions

When the __validate__ function fails, no fee will be taken from the account in question.

Validate limitations

There are some limitations set on the __validate__ function. The purpose of these limitations is twofold:

  • We want to avoid the sequencer having to do a lot of work only to discover that the validation failed and the sequencer is then not eligible to charge a fee (if this was possible, the sequencer would be completely exposed to DOS attacks). Validation, while now abstract and in control of the account owner rather than the protocol, should still be a simple operation. This is why a maximum steps limitation on the __validate__ function is currently in place on the Starknet network. For more information, see Current limits.

  • Even if the validation is simple, we could still face the following attack:

    • An attacker fills the mempool with transactions that are valid at time T.

    • A sequencer may start executing them, thinking that at the time he will produce his block, they will still be valid.

    • However, shortly after, at time T', the attacker sends one transaction that somehow invalidates all the previous ones and makes sure it’s included before the sequencer gets to publish his block (the attacker may do this by offering higher fees for this one transaction).

As a concrete example, think of many __validate__ functions checking that the value of a storage slot is 1 and the attacker’s transaction later sets it to 0. To handle this issue, we add some further limitations.

Currently, Starknet enforces that __validate__ does not call external contracts.

The property that we achieve with the above restrictions is that a single storage update may only invalidate transactions from a single account (this is the best we can do, an account can always invalidate its own past transactions by changing its keys). Thus, the price (in fees) required to invalidate transactions in the mempool is linear in the number of unique accounts that we want to invalidate.

The execute function

The purpose of the __execute__ function is to abstract away the remaining actions performed by a transaction.

In Ethereum, a transaction is necessarily a call to a specific function in a smart contract. With the __execute__ abstraction, the account designer controls the flow of the transaction. For example, multicalls can be natively supported in your account, saving the need to send multiple transactions (in practice, this is even harder to manage without multicalls due to nonces).

Reverted transactions

A transaction is considered reverted when the __execute__ function failed. A reverted transaction is included in a block and the sequencer is eligible to charge a fee for the work done up to the point of failure, similar to Ethereum.