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 in the future, Starknet will place max steps limitation upon the
__validate__
function. -
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. Today, reverted transactions are not included in blocks, but in the future, they will be and the sequencer will be eligible to charge a fee for the work done up to the point of failure, similar to Ethereum.
Currently, the sequencer charges only for successful transactions that result from the successful completion of the __execute__
function.