Skip to main content

Toward Multidimensional Solana Fees

· 13 min read

Note: In this post, we assume familiarity with some Solana-specific terms. If you're unfamiliar, check out, Solana Fees, Part 1: Solana's Fee Mechanism for a helpful glossary.

Introduction: why aren't my transactions included?

A Solana transaction's journey from user submission to block inclusion can be arduous. Even once the transaction reaches the current leader, it must compete with other transactions for limited blockspace. Including transactions in a purely first-come-first-served fashion encourages spam and may block high-value transactions from ordinary users. To solve this problem we need a fee mechanism.

Priority fees to the rescue?

Priority fees solve this problem, right? Unfortunately, not for most users.

Imagine this scenario. You want to go to your town's 100-seat movie theater but the theater has an abnormal ticketing system: they don't publish a ticket price. Instead, you have to tell them how much you're willing to pay. (Let's say it's $50.) If demand is high and at least 100 other people submitted a higher price, tough luck. If demand is low, you're in! The catch: you're paying $50 even if the theater is empty. But the ticketing experience doesn't end there. This system has another oddity: you have to tell the theater your willingness to pay before you leave your home. Obviously, getting a theater ticket in this system is a potentially fraught experience for the average individual. Wealthy, sophisticated individuals, on the other hand, can accurately estimate the ticket price. They can install cameras next to the movie theater to monitor traffic in real-time. They can use this traffic and collected historical data to estimate demand on a given day. And they can even buy a helicopter to travel to the theater faster after telling the theater their willingness to pay. While it is possible to use this ticketing system effectively, the experience is horrible for most people who want to go to the theater. When the theater is crowded, many of these people may not bother trying to go at all.

Similarly the "true" priority fee needed for a Solana transaction is difficult to estimate. It fluctuates a lot. During periods of high congestion, wallets using recent averages will likely over-estimate the required fee, causing users to overpay for transaction inclusion. (And even with a high priority fee, a transaction still may not be included due to the nature of multi-threaded continuous block production.) In theory, priority fees can optimally allocate blockspace. In practice, they utterly fail. Fortunately there are ways to solve these issues, ultimately resulting in cheaper transactions with a better chance of inclusion for most users. Before diving into the solution, let's examine some properties of the resource we're trying to allocate: blockspace.

Solana priority fees

Blockspace: fixed supply, wildly fluctuating demand

Transactions on Solana are independently verified and executed by a decentralized network of validators. Because these validators have limited computational resources, Solana limits the total computational resources, measured in compute units (CUs) that can be used per block. When demand for blockspace spikes beyond the limited supply, this fixed resource must be allocated among competing transactions.

In some ways, the market can handle blockspace allocation through pricing; transactions with higher utility to the submitter are willing to pay a higher price. However, this mechanism doesn't work when users don't know what the price for blockspace is! As discussed above, a priority fee alone leads to hard-to-estimate transaction prices for most users and has poor transaction inclusion guarantees. Other blockchains have tackled this issue by implementing a transaction fee mechanism (for example, EIP-1559 in Ethereum, and multidimensional fees in Avalanche and Penumbra). These fee mechanisms implement an easy-to-estimate base fee that provides a predictable "entry fee" for the network; this base fee approximates the true price for transaction inclusion.

Review: Solana fees today

Solana currently implements two fee mechanisms: a base fee and a priority fee. We can think of the base fee as an "entry fee" for using the network and of the priority fee as an extra tip to incentivize validators to include one transaction over another. The Solana base fee is a fixed 5000 lamports per signature (usually one per transaction). As a result, users submitting simple transfers pay the same base fee as users playing compute-heavy on-chain games and as searchers attempting to capture complicated MEV opportunities. Thus, the current base fee charged does not accurately capture a transaction's resource usage (and "externality," in economics speak), resulting in potentially poorly-used blockspace.

In our theater example, a similarly fixed base fee would charge a moviegoer buying 1 seat and one buying all 100 seats the same price.

Why a base fee mechanism?

Base fees should instead be a function of the resources used. This consumption is currently measured in compute units (CUs) but could also include other resources such as account access. We want the user to have a more predictable cost of participation, given by a dynamic base fee. These fees are difficult to set correctly. (There's a lot of academic research, including our own, trying to tackle this!) However, anyone who has suffered from failed transactions due to network congestion knows that fees are also important to get right if we want a network to grow.

Ultimately, many users who want to submit transactions aren't sure what to pay for priority fees. Overestimate these fees, and they pay way too much. Underestimate these fees, and the transaction won't be included. For base fees, on the other hand, users pay exactly the current published fee. We'd like the base fee to accurately capture the true cost of inclusion for transactions. Here, we assume all transactions reach the scheduler but compete for limited blockspace, including compute and account access. During periods of high demand, transactions cannot all be included. In this post, we outline steps towards a dynamic base fee mechanism that can help unclog the network and provide more predictable transaction inclusion.

Aside: Priority fees and MEV

Priority fees may alternatively be viewed as payments for particular opportunities which are limited by something other than resource consumption. For example, if two searchers simultaneously submit a transactions to liquidate a particular loan, only one of these transactions can be executed successfully; only the first transaction in the block successfully liquidates the loan. Priority fees, therefore, often pay for transaction ordering rather than inclusion alone. This usage of priority fees to capture MEV opportunities is somewhat orthogonal to this post—see MEV on Solana for more discussion. In this post, we concentrate on base fees and transaction inclusion.

Optimal blocks

Before discussing how to set base fees, we'll consider a ficticious mechanism for block inclusion: an omniscient network designer chooses transactions for each block that maximize welfare of the users (the total utility or 'happiness' from a transaction being included), minus the resource consumption cost of the network, subject to transaction constraints (e.g., smart contract constraints, compute limits, etc.). Of course, user welfare is unknown and unmeasurable by the network designer in practice, and the network designer does not build the blocks. However, we can use this problem as a mental benchmark for an "optimal" block built out of the transactions that reach the scheduler. Our goal is to design a fee mechanism that gets close to this benchmark.

Although this ficticious block building mechanism is intractable, it turns out that we can come up with an equivalent mechanism that is implementable in practice. This equivalent mechanism seeks to find the resource prices which minimize the gap between the welfare of the network and that of its users. Correctly setting the resource prices aligns incentives such that the resource costs to the network are exactly balanced by the utility gained by the users and validators, even though the network has no idea what this utility is and the users never explicitly specify it. These prices then lead to blocks which are "optimal" on average. Thus, we can focus only on setting these prices. (For more technical details, check out this paper.)

Abstract

Toward a mechanism

How then do we set prices to incentivize optimal blocks, as discussed above? A first approach might be to charge a fixed amount per compute unit used by a transaction. Unfortunately, this approach won't work. If demand is low, then this fixed price per CU will cause users to not submit transactions, and blocks may be close to empty. If demand is high, then this fixed price may be too low and will, as a result, do nothing to ease network congestion or approximate the true cost of transaction inclusion (our original goal!). Thus, we need to have some way to increase and decrease the base fee based on network demand, and also some way to estimate this demand.

Dynamic base fees

The next step is to add a controller that adjusts the fee per compute unit based on past usage. For example, if there is a usage target per block (i.e., a "steady state" ideal usage for the chain), we can increase or decrease the base fee based on utilization or the deviation from this target. Many mechanisms, including ones such as EIP-1559, implement this idea. We may be tempted to dynamically update the base fee within a single block using real-time demand, or to encourage each leader to choose their own fee updating rules. However, these changes would reduce base fee predictability, defeating the original purpose of this fee mechanism. The choice of mechanism ultimately determines the user experience.

Fortunately, Solana's fast block times permit aggressive algorithms for setting the base fee. For example, we can quickly increase the price during periods of high demand (e.g., double the price for each full block), and then more slowly lower the price as demand subsides. The price will still drop reasonably fast due to Solana's short block times. Intuitively, when a specific resource is "maxed out," the network is significantly undercharging and getting less information about what the optimal price should be. Similar types of algorithms are used in practice in TCP congestion control, the data-link layer of wireless communications, and marketplace optimization.

Multidimensional fees: local fee markets

The previous discussion considered one joint resource shared by all transactions: (global) compute units. However, state access on Solana also significantly impacts transaction resource usage. If many transactions access the same piece of state, they cannot be executed in parallel and, therefore, reduce network throughput. Naturally, we would like these transactions to pay a higher base fee, motivating per-account (local) fee markets. (The question of who gets these fees is beyond the scope of this post; more on that in the future.)

As a concrete example, let's consider a popular NFT drop. Without per-account fee markets, this drop would increase the base fee significantly for all other transactions. With per-account fees, the cost of submitting transactions to claim an NFT (and access that part of state) can increase while fees for other transactions (such as topping off collateral on a loan) remain largely unchanged. This type of per-account, local fee market design is being considered in a draft Solana IMprovement Document.

These multidimensional fees can also account for correlations between contracts. For example, if two cNFT exchanges trade many of the same collections, their contracts require access to many of the same accounts. If trading volume increases dramatically for a particular collection on one of these exchanges, then the base fee will increase for that collection on the other, as the fee for the underlying account has increased for both. In this way, "local" per-account fees properly correlate in a "global" way, and multidimensional fees prevent gaming the system by releasing multiple contracts for the same application.

On the other hand, if the state of the application itself is parallelized, fees will be lower. This property is desired; parallelization increases throughput as it allows different transactions accessing the same application to be put on different threads when the underlying accounts do not overlap (see Lifecycle of a Solana Transaction).

The price controller for these local, per-account fee markets may be different than that used for compute units. Perhaps for the accounts we charge no fee at all until the state is significantly contested, at which point the fee ramps up rapidly. Analysis of past periods of congestion can help inform these design decisions.

Alternative mechanisms

The options discussed above are not the only ways to set fees. Most base fee mechanisms follow roughly the same iterative procedure: at each block...

  1. look at the resource consumption of the most recent blocks (a result of the included transactions);
  2. compute a function of this consumption (for example, deviation from a target);
  3. and then update the resource prices using the output of step 2 and a simple rule.

In the examples discussed above, we used different choices for the resources in step 1 and the update mechanism in steps 2 and 3. A method for turning resource utilization preferences into a concrete fee update rule is discussed in this paper.

Does this multidimensional stuff work?

While our academic work is mostly theoretical, simple toy examples show that pricing resources, like accounts, separately can increase network efficiency and make the network more robust to DoS attacks or changes in the types of transactions submitted. In this section, we highlight the difference between a single-dimensional and a simple multi-dimensional fee mechanism (see section 4 of our paper for details).

Steady-state behavior

We first simulate the steady state behavior of a multi-dimensional fee mechanism with transactions that require roughly equal amounts of resource 1 and resource 2. Under steady state behavior, uniform pricing causes higher deviation from resource usage targets (left). Less efficient usage also leads to lower throughput (right).

Steady state behavior

Steady state behavior

Distribution shifts

We also test mechanism behavior under a distribution shift: we add 150 transactions in block 10 that require a significant amount of resource 2. This scenario may appear in an NFT mint, for example. Multidimensional pricing leads to significantly higher throughput when the distribution shifts (left) by adjusting resource prices adjust accordingly (right).

Distribution shift

Distribution shift

Looking at individual resource usage, we see that multidimensional pricing (left) facilitates short burst capacity, whereas uniform pricing (right) does not.

Throughput

Throughput

Conclusion

Blockchains have finite computational resources. When user demand is high, this requires allocating these finite resources among competing user transactions in a predictable way. This allocation should be done implicitly via a dynamic transaction base fee.

We propose a theoretically-grounded mechanism to set this fee, which can not only increase network throughput during periods of high demand but also improve the user experience by decoupling unrelated transactions and providing a predictable cost of transaction inclusion. For more details on the mechanism, check out Tarun's talk at Breakpoint and our paper!

Thank you to Lucas Bruder and Jarry Xiao for helpful comments on drafts of this post!