Operations
This chapter introduces the notion of Operations on Mavryk. These are more commonly known as Transactions on other blockchains and in Mavryk are a subset of operations. On Mavryk, transactions are transfers of tokens and smart contracts calls. A few examples of operations that are not transactions on Mavryk: Originations; attestations; proposals; ballots...
Implicit accounts and originated accounts
Let's start by talking about the two types of addresses in Mavryk [1]:
An implicit account is linked to a manager (see General definition of a mavryk smart contract), which owns the public key. The hash of the public key outputs an address. Depending on the chosen Digital Signature Algorithm's elliptic curve (see ECDSA), the latter starts with "mv1" (Ed25519 curve), "mv2" (Secp256k1 curve), "mv3" (P256 curve) [2] or "mv4" (BLS curve).
A transfer operation to the account's address creates the account itself.
Only implicit accounts can be registered as delegates and participate in the baking process.Smart contracts are also called "originated accounts" and are created with an origination operation (see the Smart Contracts chapter). They don't have a private key and public key pair. Originated accounts' addresses start with KT1.
They run their Michelson code each time they receive a transaction.
Mavryk operations
An operation is usually a message sent from one address to another.
Message representation:
type operation = {
amount: amount; // amount being sent
parameters: data list; // parameters passed to the script
counter: int; // invoice id to avoid replay attacks
destination: contract hash;
}
Example of an entrypoint call:
{
"branch": "BMXRpSqjJ9HnEeaSXj3YzM9jqB4kqDZemtJBGGqn5Sa9MepV1k7",
"contents": [
{
"kind": "transaction",
"source": "mv1AXwVEx55jf9Rc3taKbxTn6boxtPVUjJsu",
"fee": "6678",
"counter": "942780",
"gas_limit": "63669",
"storage_limit": "75",
"amount": "0",
"destination": "KT1S5hgipNSTFehZo7v81gq6fcLChbRwptqy",
"parameters": {
"entrypoint": "sellLand",
"value": {
"prim": "Pair",
"args": [
{"int": "100000000"},
{"int": "11"}
]
}
}
}
]
}
Example of a transaction between two implicit accounts:
{
"branch": "BKkwBsr6hfvj2MfaEydByALrASfCPzFBFSCs3iY7XaBLKRtwWj7",
"contents": [
{
"kind": "transaction",
"source": "mv1BiLXQNqhZuaecno7WvdVjRY2B4rCs63WU",
"fee": "444",
"counter": "855452",
"gas_limit": "1527",
"storage_limit": "0",
"amount": "100000000",
"destination": "mv1AXwVEx55jf9Rc3taKbxTn6boxtPVUjJsu"
}
]
}
Such an operation can be sent from an implicit account (if signed using the manager's private key) or programmatically by contract code execution. When the operation is received, and if the destination's contract code execution is valid, the amount is added to the destination's contract balance. This code can make use of the arguments passed on to it. It can read and write the contract's storage or post operations to other contracts.
As the example shows, there is also a counter field, whose purpose is to prevent replay attacks. An operation is only valid if the contract's counter is equal to the operation's counter. Once an operation is applied, the counter increases by one, preventing the operation from being reused.
The operation also includes the block hash ("branch" field) of a recent block that the client considers valid. If an attacker ever succeeds in forcing a long reorganization with a fork, he will be unable to include this operation, thereby making the attack detectable.
This last line of defense is named "TAPOS": a statistical detection of a Long Range Attack (see Liquid Proof of Stake chapter) based on the fraction of moving coins. This kind of system prevents long reorganizations but is inefficient with short-term double-spending.
Currently the layer 1 of Mavryk network on Atlas can process around 170 TPS (transactions per second) and has a deterministic time finality of 30 seconds (2 blocks). This speed may vary with future protocols. Finality is the time it takes for a block to be considered secure. As a comparison, Bitcoin can process 7 TPS and has a probabilistic time finality of 60 minutes (6 valid blocks).
Operation Flow
The diagram below represents the life cycle of an operation:
FIGURE 1: Life cycle of an operationNodes receive operations from clients via RPC or from a peer in the network. Note that this is how a typical Mavryk node works on protocol "Alpha".
Pre-validator
The Pre-validator[4] is an operation's validation subsystem. It decides which operation to add to the mempool (memory pool).
The pre-validation consists in 3 steps:
The Pre_filter step
Checks that there is enough provided gas (and if the sender's account has enough funds to pay)The Apply step
Checks whether the operation is in adequacy with the current state of the Mavryk chainThe Post_filter step
Decides whether to add the operation to the mempool. If yes, it broadcasts the result
If any of these steps triggers an error, the pre-validator doesn't add the operation to the mempool.
Mempool
The node maintains a memory pool to keep track of "not-invalid-for-sure
" operations.
There are two different kinds:
Known_valid
: A list of valid operations ready to go in a blockPending
: A bounded set of operations eligible to broadcast. This set contains two sub-kinds of operations:branch_refused
: an operation that could be valid in a different branch.branch_delayed
: an operation that has come too soon (i.e. there's a gap in the account's counter)
Operations in the mempool are broadcast to peers whenever the mempool is updated. A node fetches an operation from another remote peer's advertisement using the operation's hash.
A valid operation lives in the mempool until its addition to a valid block on a chain the node considers canonical (correct chain for the common history). If an operation isn't added to a block during its "time-to-live
" duration, it is removed from the mempool. As long as a transaction is in the mempool, the sender's address cannot normally issue another. However, it is possible to send multiple transactions at the same time in a batch.
Validator and attestation
The selected validator is free to select operations from the mempool, but he usually uses a filter. After a block creation, selected validators attest the block. At the next block slot, the block producer collects the attestations from the previous level and adds them to the block.
Validator
The selected validator then shares the created block with the network. Once received, each node starts the block validation by calling the Apply_block function. This function validates the block header by using the protocol parameters and verifies all the operations.
The block validation checks for errors such as too many operations, oversized operations, incorrect protocol versions, unauthorized validation passes, invalid fitness, unavailable protocols, errors while applying a transaction, and more [5].
Once a block is validated and is a candidate for the new head of the chain, it arrives to the "chain validator" which checks if the fitness score[6] of the new head is higher than the fitness score of the current one. If it is not, the block is ignored. If it is, then this block replaces the current head of the chain. The chain validator then calls the pre-validator to flush the mempool. Finally, the new head is advertised to the peers using the distributed_db's Advertise module[7]. Of course, this block only becomes part of the canonical chain if future validators decide to bake on top of it.
What have we learned so far?
In this chapter, we discovered the Mavryk operations and how they are validated.
In the next "CLI and RPC" chapter, we will learn how to issue them with a node.