Accounts
Overview
The Zilliqa blockchain supports two types of accounts:
- Non-contract - for balance transfers
- Contract - for both balance transfers and smart contract execution
While non-contract accounts simply need to record base data like balance and nonce, contract accounts also have to store smart contract code, as well as immutable and mutable states.
The diagram here shows that account data is organized according to those common
to all accounts (AccountBase
) and those that only contract accounts would have
(Account
).
Note
We can distinguish a contract from non-contract account by checking if
m_codeHash
is NULL. We can also choose to serialize only the base fields or
both the base and the contract-related fields.
Contract Account States
A transaction that requests a contract deployment triggers the generation of a new contract account and the setting up of that account's immutable and mutable states. Then, transactions that invoke transition calls on that contract account trigger the updating of the mutable states.
Immutable states are more commonly referred to as the init data, and
examples of this include _scilla_version
, _library
, and _extlibs
.
Init data is supplied to the node within the transaction body (please refer to
the
CreateTransaction
API documentation), although the node will also eventually add more fields into
init data (such as _creation_block
and _this_address
) during transaction
processing.
Mutable states, on the other hand, are the variables that are manipulated by the Scilla interpreter as it executes a transition on the contract. The Zilliqa node provides the Scilla interpreter access to these states through the Scilla IPC server.
Account Data Storage
Storage of accounts in the Zilliqa blockchain can be quite complex to understand due to the fact that account data is spread out across several leveldb databases:
leveldb Name | Managed By | Key | Value |
---|---|---|---|
state | AccountStoreTrie | Address | AccountBase |
contractCode | ContractStorage | Address | Code |
contractInitState2 | ContractStorage | Address | Tx data field + _creation_block + _this_address |
contractStateData2 | ContractStorage | Address.vname Address._depth.vname Address._type.vname Address.vname.index1.index2… |
State value |
contractTrie | ContractStorage | Hash of contractStateData2 key | State value |
stateRoot | BlockStorage | LATEST_EPOCH_STATES_UPDATED STATEROOT |
Epoch number Trie root value |
stateDelta | BlockStorage | Tx block number | List of Account |
State Tries
The state and contractTrie databases are implementations of Ethereum's Merkle Patricia Trie data structure.
A trie is a key-value data structure with a root hash and hashes along each key-value pair. The root hash is updated every time the structure is updated (e.g., by adding another key-value pair).
Please see this Medium article for how the trie structure evolves (specifically, where the root node is) as you add transactions one-by-one.
The trie provides the ability to prove that a specific key-value pair exists within the structure without the requesting party needing to know the rest of the structure. All the requesting party needs is the hash of the key-value pair at the particular time that the structure is being evaluated. For example, the contractTrie database supports requests from the GetStateProof API, which requests for proof of a particular state for an account at a specific Tx block.
The contractTrie database was implemented in order to support bridging between the Zilliqa and Ethereum blockchains. While the contract state trie is useful for the blockchain bridging aspect, it is not vital to the normal operation of the Zilliqa protocol, i.e., it has no impact on the normal transaction processing, consensus execution, and account state updating. Therefore, it does not need to be discussed further at present.
The state database, on the other hand, is used to store the AccountBase
portion of the account data.
State Trie Library
Zilliqa uses Ethereum's GenericTrieDB
library for the trie structure. The
GenericTrieDB
class contains a TraceableDB
member, which manages the loading
from or saving to disk (i.e., to the levelDB database) of the trie structure.
The basic usage will involve:
- Calling
init()
to reset the root to NULL - Calling
insert()
to add nodes to the trie - Calling
at()
to access any of the added nodes - Calling
db()
to access the underlyingTraceableDB
member, and then either- Calling
commit()
to save the changes to disk - Calling
rollback()
to discard changes
- Calling
This basic usage applies to the state database. In this context, a node
basically refers to the Address
(key) and the AccountBase
(value) pair.
For the contractTrie database, the usage is slightly more complex due to the
fact that separate tries are maintained for a maximum of
NUM_DS_EPOCHS_STATE_HISTORY
DS epochs. As mentioned in the previous section,
we won't be going deeper into contractTrie for now.
AccountStore Class Hierarchy
The diagram below highlights the Zilliqa architecture around account and account state storage.
There are essentially three account store objects in a Zilliqa node:
AccountStore
holds the accounts that have been committed to disk (i.e., the blockchain).AccountStoreTemp
holds the account data that resides in transient memory and that has yet to be validated by the nodes during consensus.AccountStoreAtomic
also holds account data in transient memory, but only for the current transaction being executed. More precisely, it is used for contract call transactions, where multiple accounts may be updated (e.g., a chain call). After the transaction is completely processed, the contents are moved to theAccountStoreTemp
object.
The rest of the account store hierarchy is composed of abstract classes:
AccountStoreBase
contains the map of accounts in transient memory. It also provides anUpdateAccount()
function for use on non-contract (i.e., payment) transactions.AccountStoreSC
contains most of the smart contract-related functionality, including anUpdateAccount()
function for use on contract (deployment and call) transactions.AccountStoreTrie
contains trie management functions and theGenericTrieDB
instance for the base account data and its storage in the state leveldb database.
Apart from the state leveldb database, the other account-related databases
listed in the previous section are managed by the ContractStorage
and
BlockStorage
classes.
Finally, the diagram also shows that aside from the account store classes, the
ScillaIPCServer
also interacts with ContractStorage
whenever the Scilla
Interpreter requests for state access during the execution of a contract.
State Deltas
State deltas are the differences between what's stored in disk (i.e., the blockchain) and what's changed after transaction execution for a particular account (whether non-contract or otherwise).
Transaction processing by a node does not immediately result in committing any such differences to disk. As with any distributed ledger system, all nodes in the network must first reach consensus before they update their storage in a uniform manner. Until the consensus is completed (i.e., the proposed Tx Block is accepted by all nodes), state deltas must first be managed by a node separately within its transient memory.
It should also be noted that multiple transactions could be called on the same contract (for example, several users withdrawing rewards from the Staking contract), and thus the complete set of state deltas cannot be known immediately after one transaction, but rather only after all transactions for the current Tx epoch have been processed.
Another consideration unique to Zilliqa's architecture is that shards manage their own set of transactions that are different from those of other shards. This means nodes belonging to different shards will be holding different state deltas in their transient memory during the consensus period. Therefore, in order for all nodes to arrive at the same final states, these state deltas will eventually have to be shared across shards.
As can be inferred from the previous section, state deltas are generated by
getting the differences for an account between AccountStore
and
AccountStoreTemp
.