How gas works on the Libra blockchain

January 8, 2020
Libra Engineering Team

Note : The Libra Core software and the Move language are still under development; the information and the terminology used in this document are subject to change.

This post starts a series on gas on the Libra blockchain. The goal of this series is to take you from "I have no idea what gas means" to understanding what gas is and, at a high level, how it works. As the first post in this series, our goal here is to introduce the subject and provide an overview of the design and design goals. In subsequent posts we'll dig in and explore each area in more technical detail.

What is gas?

Put simply:

Gas is a way for the Move virtual machine to track and account for the abstract representation of computational resources consumed during execution.

In this way, gas is central to one of the most basic and crucial properties we need in Move:

The computational resources consumed by Move programs running on the blockchain are bounded.

Gas is a way to ensure that all programs terminate; it also provides the ability to charge a transaction fee based in part on the resources consumed during the execution of the transaction.

What does gas look like for a developer?

The transaction a client submits for execution contains a specified max_gas_amount and gas_pricemax_gas_amount is the maximum amount of gas that can be used to execute the transaction, and therefore it bounds the amount of computational resources that can be consumed by the transaction. gas_price is a way to move from the abstract units of resource consumption that are used in the virtual machine (VM) — gas units — into Libra. Consequently, the transaction submitter is guaranteed to be charged at most gas_price * max_gas_amount (the "gas liability") for the execution of the transaction.

Similarities with other blockchains

Fees for executing transactions were originally pioneered by Bitcoin, and the idea of gas and gas-based execution fees were introduced by Ethereum. The design of gas on the Libra blockchain has benefited greatly from their designs, and in many ways the gas design for the Libra blockchain is similar to Ethereum's.

Core design principles

The design of gas in Move is motivated by three central principles.

First, Move is Turing complete. Because of this, determining if a given Move program terminates cannot be decided statically. However, by ensuring that (1) every bytecode instruction has a non-zero cost, and (2) the amount of gas that any program can be started with is bounded, we get this termination property for programs almost free of cost.

Second, we want to both discourage DDoS attacks and encourage judicious use of the network. This means that the computed resource consumption should reflect the real-world resource usage as closely as possible. It is important to note that resource consumption needs to be carefully defined; defining the relationships between different dimensions of resources is crucial.

Lastly, the resource usage of a program needs to be agreed upon in consensus. Taking this to its eventual conclusion means that the method of accounting for resource consumption needs to be deterministic. This in particular rules out other means of tracking resource usage, such as cycle counters or any type of timing-based methods, since these are not guaranteed to be deterministic across nodes. In particular, the method for tracking resource usage needs to be abstract.

Diving into the details

In this section, we'll provide a high-level technical overview of gas in the VM.

Different types of resources

For the VM to execute a transaction, the gas system needs to track the primary resources that are used by both the network and the VM. These fall into three resource "dimensions": (1) the computational cost of executing the transaction itself; (2) the network cost of sending the transaction over the network; and finally (3) the storage cost of storing the data created and read during the transaction on the blockchain. The first two of these resources (compute and network) are ephemeral, whereas storage is long lived; once data is allocated, that data persists until it is deleted. In the case of accounts, the data lives indefinitely.

Each of these resource dimensions can fluctuate independently of the other. However, we also have only one gas price. This means the gas usage contributed for each resource dimension needs to be correct, because the gas price only acts as a multiplier to the total gas usage, not usage by dimension. Thus, the essential property that we design for is that the gas usage of a transaction needs to be as highly correlated with the real-world cost associated with executing the transaction as possible.

That's all on this area for now, but stay tuned for an upcoming post where we'll dive into the details of this part of the gas system and how to determine the ratios between different resource dimensions in the Libra blockchain. Now, let's take a look at how gas is implemented in the VM.

Gas and transaction flow

When a transaction is executed by the VM, it is responsible for computing the resource usage of the transaction, then multiplying this by the gas price submitted with the transaction to arrive at the fee for execution. Different aspects of resource usage are charged for at different times in the transaction flow. The basics of the transaction flow and the gas-related logic are detailed in the following diagram:

Note in the diagram that both the prologue and epilogue sections are marked in red. This is because both of these sections of the transaction flow need to be unmetered:

  • In the prologue, it's not known if the submitting account has enough Libra to cover its gas liability, or if the transaction submitter even has authority over the submitting account. Due to this lack of knowledge, when the prologue is executed, it needs to be unmetered; deducting gas for transactions that fail the prologue could allow unauthorized deductions from accounts.
  • The epilogue is in part responsible for debiting the execution fee from the sending account and distributing it.[1] Because of this, the epilogue must run even if the transaction execution has run out of gas. Likewise, we don't want it to run out of gas while debiting the transaction sender's account as this would cause additional computation to be performed without any transaction fee being charged.

Taken together, this non-metering of both the prologue and epilogue requires the MIN_TXN_FEE to be enough to cover the average cost of running both. This is fairly straightforward to determine since metering must be deterministic and since both of these are relatively simple pieces of code that don't perform any complex operations.

After the prologue has run and we've checked in part that the account can cover its gas liability, the rest of the transaction flow starts with the "gas tank" full at max_gas_amount. The MIN_TXN_FEE is charged, after which the gas tank is then deducted (or "charged") for each instruction executed by the VM. This per-instruction deduction continues until either (1) the execution of the transaction has completed — in which case, the epilogue is run and the execution fee deducted — or (2) the "gas tank" becomes empty, in which case an OutOfGas error is raised. In the former, the fee is distributed and the result of the transaction is persisted. The latter causes the execution of the transaction to stop when the error is raised, following which the total gas liability of the transaction is deducted and distributed. No other remnants of the execution are persisted other than the deduction in this case.[2]

Wrapping things up

In this post, we offered a high-level overview of the design space around gas in the Libra blockchain. In future posts, we'll explore each aspect of gas in further technical detail.

Endnotes
[1] This will be discussed more in the upcoming posts on the distribution of transaction fees and the economics of gas and transaction fees.
[2] Along with the deduction of the gas liability, the sequence number of the account is also incremented.