In this piece, we focus on the lifecycle of a Solana transaction. We examine how the Solana runtime processes transactions, with an emphasis on transaction execution and ordering. We also explore the differences in transaction execution between Solana and Ethereum.
The topics discussed assume some familiarity with blockchain mechanisms. The goal of this piece is to set the stage for discussing MEV on Solana.
Structure of a Solana Transaction
A Solana transaction consists of three parts:
- One or more instructions, specifying what code the transaction should run on-chain. For example, "Transfer 5 SOL from Account A to Account B".
- An array of accounts (discrete pieces of state) required by the transaction, with read and write flags. For example, "Account A [writable], Account B [writable]".
- One or more signatures required by the transaction. For example, "Signature from Account A owner".
Up-front state specification is a pain for developers, but this separation of code and state is necessary for Solana’s parallel execution model (similar to EIP-2930).
You can read more details about how transactions are structured here, but that’s not necessary to understand the rest of the post.
Lifecycle of a Solana Transaction
Users initiate transactions through their wallet. A transaction could be a token transfer, a trade on a DeFi protocol, an NFT mint, or any other operation available on the Solana blockchain. What happens between clicking "Approve Transaction" and the UI reporting that the transaction has been confirmed?
Initiating the Transaction
Once a user signs the transaction in their wallet, the wallet sends the transaction to a Solana RPC server. RPC servers can be run by any validator. Upon receiving the transaction, the RPC server checks the leader schedule (determined once per epoch, about 2 days long) and forwards the transaction to the current leader as well as the next two leaders. The leader is in charge of producing a block for the current slot, and is assigned four consecutive slots. Slots usually last around 400 milliseconds.
Transaction Execution and Ordering
Once the signed transaction reaches the current leader, the leader validates the transaction's signature and performs other pre-processing steps before scheduling the transaction for execution.
Most validators today use the scheduler implementation provided in the Solana client built by Solana Labs. However, validators can run different block building algorithms if they prefer.
The default scheduler implementation is multi-threaded, with each thread maintaining a queue of transactions waiting for execution. Transactions are randomly assigned to a single thread’s queue. Each queue is ordered by priority fee (denominated in fee paid per compute unit requested) and time.
Note that there is no global ordering of transactions queued for execution; there is just a local ordering in each thread’s queue.
Recall that a transaction includes a specification of which state needs to be read-locked and write-locked for execution. When it comes time for a thread to execute a transaction, it first attempts to acquire the necessary account locks, then executes the transaction. Transactions for which the process cannot acquire the necessary lock are re-queued to try again later.
In the above diagram, each box represents a single transaction. Each transaction is labeled with the accounts it locks. Execution thread 1 locks accounts [a,b,c], [d], fails to lock [c,j], and [f,g]. Execution thread 2 locks accounts [w], [x,y,z], fails to lock [c], and [v]. The remaining transactions are re-scheduled for future execution.
This is one way Solana achieves higher performance than competing chains. When multiple transactions don’t need to touch the same state, they can be executed in parallel which improves the throughput of the chain. However, this imposes a cost on developers as any piece of state that may be required by a transaction must be specified up front.
With this default scheduler implementation, transactions are ordered in the block by a rough combination of FIFO and priority fees. But there is inherent non-determinism in transaction ordering because transactions are assigned to execution threads somewhat randomly—even without network jitter, different sends can land in different positions in the multi-threaded scheduler. This scheduler jitter adds variance to the position a transaction lands in a block, so it can be beneficial to spam to land an urgent transaction.
This also implies that if a searcher is fastest to a particular trade, they may be able to get their transaction executed before the relevant state (market account) gets hot, so they don’t need to use priority fees for inclusion.
One can imagine alternate scheduler designs with different characteristics and different payouts to validators. This is just the block packing problem, except execution is a significant bottleneck because of Solana’s high supported throughput, so blocks are built progressively as transactions stream in.
Transaction Propagation and Status Updates
Once a transaction is executed by the leader, it is immediately recorded to the validator’s copy of the ledger and propagated to the rest of the network. After a block has achieved the necessary votes from consensus, the transaction is considered "confirmed." Finally, a block is considered "finalized" when 31+ confirmed blocks have been built upon it. These stages are returned through the RPC back to the front-end, allowing the user to see the status of their transaction. We will explore Solana’s block propagation and consensus mechanisms in future posts.
Differences between Solana and Ethereum
There are many differences in the transaction lifecycles of Solana and Ethereum.
- One major difference is that Solana has no public mempool. Instead of pending transactions being part of a distributed mempool built by peer-to-peer gossip, they are forwarded directly to the current leader and next few leaders.
- Solana’s default validator implementation also features continuous block production. Transactions continuously stream into the validator for execution, then block production, and finally transaction propagation. On Ethereum, pending transactions are held up by the validator or block builder before full blocks are built in 12-second intervals. Continuous block production implies that priority fees do not guarantee position within a block. This means latency is more important for competitive trades, relative to discrete auctions for state (featured in
mev-booston Ethereum and Jito auctions on Solana).
- Solana transactions require a fixed network fee per-signature (usually one signature per transaction) of 0.000005 SOL, roughly $0.0001 at the time of publication. An optional priority fee, denominated in fee paid per compute unit requested (upper bound on compute units used in execution), can be included as well for higher priority within the Solana scheduler as described above. Solana’s block size limit is fixed in compute units used, similar to how Ethereum blocks have a gas target. Half of the network fees are burned and half go to the leader. We will explore the implications of this fee model and potential alternatives in a future post.
- Out-of-protocol blockspace auctions (Jito) have recently launched on Solana, and have a smaller market share (25%) compared to
mev-booston Ethereum (85%). This illustrates some of the fundamental differences between the two ecosystems in terms of block production and the handling of MEV.
In this piece, we examined the lifecycle of a Solana transaction, from a user-submitted wallet transaction to the transaction’s confirmation by the Solana network. We looked at how Solana executes and orders transactions, and the relevant differences between Solana and Ethereum. We also touched upon some of the downstream implications Solana’s design has for MEV searchers. In the next post, we will discuss the current and future states of MEV on Solana.
To collaborate with us, please reach out to firstname.lastname@example.org.