This note explains and illustrates flow of control in Tezos using the example of carrying out a simple transaction. We will go from the high-level command line call to the low-level account operations. To guide you through the codebase, we give line numbers based on release 8.2 (commit 6102c808).
Where do I start?
Our scenario. Imagine that Alice and Bob have one account each, and Alice wants to send ꜩ10 from her account to Bob’s. Alice can proceed by using a transfer operation. This transaction will trigger some additional transactions, which will be handled automatically by the system:
- Alice must pay baking fees (tokens paid to the baker who writes Alice’s main transfer onto the blockchain).
- Alice may also have to pay for key revelation to reveal the public key of her account.1
Here’s a picture:
The easiest way to execute our example is to run a node locally by using the sandboxed mode. In the terminal, we just type:
$ tezos-client transfer 10 from alice to bob
Inside the code
As far as the Tezos codebase is concerned, everything is a contract. This includes user-owned accounts (called implicit accounts in the codebase) and smart contracts (called originated accounts in the codebase). This is reflected in the variable and function names in the code cited below, so for consistency we may also call everything a “contract” — but note that in our example, the source and destination contracts are actually the user-owned (i.e., implicit) accounts of Alice and Bob.
Step 1: Dispatching the client command
-
The flow of control starts with the
commands
function, whose code is inclient_proto_context_commands.ml
(l.254). This determines the command-line syntax for the switches following an invocation of ‘tezos-client’.2 In our example, we have invoked thetransfer
sub-command (l.893). -
Control now flows to
transfer_command
(l.110), which distinguishes the type of the source contract (in the pattern-matched variablesource
l.136) — in our case it is implicit and admits a public key hash. Flow of control passes to another function,transfer
(l.167). -
transfer
is inclient_proto_context.ml
(l.73). Its job is to build the transaction operation itself, using a functionbuild_transaction_operation
. In our case, the operation is a GADTTransaction
of typemanager_operation
(discussed below). In particular, its fieldamount
contains the amount of tez to be transferred (excluding baking fees).transfer
wraps the operation in anannotated_manager_operation
by functionprepare_manager_operation
, adding fee and gas information; then it further wraps the operation into the GADTInjection.Single_manager
of typeannotated_manager_operation_list
; and finally it passes this all on to functioninject_manager_operation
, detailed below.
Manager operations are operations that compete between themselves for inclusion in a block. This is in contrast to other types of operations such as consensus or governance related operations. The competition is realized through the proposed fees, which go to the baker selecting the operations to be included in the block. There are four types of manager operations, defined by the type
manager_operation
inoperation_repr.ml
using the following four constructors:
Reveal
for the revelation of a public key, a one-time prerequisite to any signed operation, in order to be able to check the sender’s signature.Transaction
of some amount to some destination contract.Origination
of a contract using a smart-contractscript
and intially credited with the amountcredit
.Delegation
to some staking contract (designated by its public key hash).Although we usually use
tezos-client
to inject a single operation, the economic protocol use a built-in notion of a list of operations (also called “packed operation”) that can be injected as a whole, for efficiency reasons. In this case the constructorInjection.Cons_manager
, instead ofInjection.Single_manager
, would be used to build a value of typeannotated_manager_operation_list
.
Step 2: Injecting the operation
-
The function
inject_manager_operation
ininjection.ml
(l.911) proceeds, from the previous list of annotated manager operations (of typeannotated_manager_operation_list
), by creating a list of contents (of typecontents_list
) where each content (of typecontents
) is a GADTManager_operation
. This mapping is performed by the local functionbuild_contents
, and for each element by the local functioncontents_of_manager
. These functions deconstruct and reconstruct the list of annotated manager operations, providing information for the missing values (fee, gas limit, storage limit).Then, function
inject_manager_operation
proceeds according to two cases:- If Alice’s public key has not yet been revealed, then the function adds one more manager operation to reveal her public key.
- If Alice’s public key has been revealed, then the next function is directly called with this list of contents, which is the case in our example.
-
The function
inject_operation
(l.773) completes the final steps of injecting into the node. It:- computes estimated gas and storage (in function
may_patch_limits
), - checks compliance with cap fees (in function
may_patch_limits
), - performs a simulation (by calling
preapply
), and finally - injects the operation (by calling
Shell_services.Injection.operation
in l.825).
- computes estimated gas and storage (in function
Step 3: Dispatching the operation
The calls to Alpha_block_services.Helpers.Preapply.operations
in preapply
for simulation and to Shell_services.Injection.operation
for injection from Step 2 actually represent two RPC calls from the client to the node.
Such RPCs are declared in src/lib_shell_services
and implemented in src/lib_shell/
(in files with the name of the form *_directory.ml
).
To serve these two RPCs, the shell part of the node simply dispatches the operation execution to the relevant economic protocol, here Alpha
.
Step 4: Applying the operation in the economic protocol
- The main entrypoint to apply the operation which we injected in the previous section is the function
apply_operation
insrc/proto_alpha/lib_protocol/main.ml
(l.180). A variableoperation
(of type'kind operation'
) containing all information for the operation itself is passed to the next function.
The record-type
'kind operation
defined in fileoperation_repr.ml
(l.62) contains a fieldprotocol_data
of type'kind protocol_data
(l.67). It wraps together a list of contents (of typecontents_list
) and a signature of the source contract for authentication purposes.
- Next, the function
apply_operation
insrc/proto_alpha/lib_protocol/apply.ml
(l.1339) is called, which after some initialization, usesoperation.protocol_data.contents
(of typecontents_list
) to call the next function. - In the function
apply_contents_list
(l.1103), two function calls are worth mentioning:- On one side (left in the figure), the function
precheck_manager_contents_list
(l.1327), which in turn calls the functionprecheck_manager_contents
(l.928), and itself will callContract.spend
will trigger the payment of baking fees (l.833). - On the other side (right in the figure), the function
apply_manager_contents_list
(l.1331) makes successive nested calls toapply_manager_contents_list_rec
(l.987), thenapply_manager_contents
(l.836), and finallyapply_manager_operation_content
(l.518). Finally, the amount of ꜩ10 is debited from Alice when callingContract.spend
(l.552) and then credited to Bob when callingContract.credit
(l.570).
- On one side (left in the figure), the function
Summary
To show how operations are handled in Tezos, we started from the command-line level using tezos-client
to trigger a token transfer between two accounts.
This client process occurs in the shell, outside of the procotol.
The command itself is parsed and a manager operation is prepared.
This operation is further wrapped up as an annotated manager operation in a single-item list of the one operation.
Furthermore, to prepare for the injection into the economic protocol, this list of annotated manager operations is transformed into a list of contents.
A content is a wrapper for various operations, one of them is the manager operation.
Finally, the operation is injected in injection.ml
.
On the economic protocol side, the operation has been fetched, consisting of a signature and a protocol data, wrapped together with a shell header.
This value is then sent to apply.ml
which is in charge of calling the final low-level operations for funds debit/credit.
-
See this short tutorial (search for “first a revelation”). By default (to save space, and to provide a little additional security) only hashes of public keys are recorded on the blockchain. But in order for Alice to sign a transaction from her account to Bob’s, Alice has to pay a one-time cost — ꜩ0.001259, in the example linked to in the tutorial above — for the full public key of her account to be recorded on the blockchain as a special key revelation operation. ↩
-
You can fetch a list of them at the command line with
tezos-client man
.
We use here a bespoke command-line argument parser developed in Nomadic Labs called Clic. Other parts of the codebase use other parsers, includingArg
, andCmdliner
(used intezos-node
). ↩