Nomadic Labs
Nomadic Labs
How to write a Tezos protocol

A Tezos node is parameterized by a software component called an economic protocol (or protocol for short). Different protocol implementations can be used to implement different types of blockchains.

This is the first post of a tutorial series on how to implement such a protocol. We will see how to write, compile, register, activate and use an extremely simple protocol. By doing so, we will also start to explore the interface between the protocol and the node (more specifically the shell component of the node). In later blog posts, we will gradually work our way up to a more realistic protocol.

In what follows, we suppose you have cloned the Tezos repository and we specifically look at revision 2d903a01 on the master branch. You should be already familiar with running a node in sandbox mode. All paths are relative to the root of this repository. All Bash commands are to be executed in sandbox mode.

Protocol Registration

A node can contain several economics protocols (they are said to be registered), but only one is activated at any given time.

We can query a node to know the registered protocols. Protocols are identified by a b58check hash. On the master branch, hashes are arbitrary values and do not depend on the actual code, but on production branches, they are hashes of the source code of the protocol.

$ tezos-admin-client list protocols

ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK
ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp
ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im

The node in this example contains three protocols. They were statically linked (embedded) to the node at compile time. genesis is the protocol activated at start-up. alpha is the main Tezos protocol. demo_noops is a simple protocol without operations (hence the name no-ops) that we will use as our main example in this article.

Protocols can also be registered dynamically at run-time via an RPC (a.k.a. protocol injection). As an example, let us inject the test protocol /src/bin_client/test/proto_test_injection available as a test case in the Tezos code base.

$ tezos-admin-client inject protocol \
  src/bin_client/test/proto_test_injection

Injected protocol PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM successfully

Under the hood, the protocol is compiled and sent to the node using the POST RPC /injection/protocol.

We can check that the protocol was successfully injected

$ tezos-admin-client list protocols

ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK
ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp
ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im
PshuejubNkeGc5nU2xwF7uGzCdujcZY7ZV3duFfffmG4z5SoMAM

Lastly the node can also fetch a protocol over the network, for example before starting the test chain or activating a new amendment. Like in the previous case, once the code is downloaded, it will be compiled and dynamically linked.

Protocol Activation

Generally, a node starts its execution with the genesis protocol. genesis provides an operation to upgrade to a new protocol. Interestingly, upgradability is a feature of the protocol, not of the shell (though the shell can also force a protocol upgrade). Protocols may or may not be upgradable. The raison d’être of genesis is upgradability, alpha is upgradable by voting, while demo_noops is not upgradable.

The client command activate protocol is a shorthand to craft the activation operation offered by genesis to upgrade to a new protocol.

$ mkdir tmp && echo { } > tmp/protocol_parameters.json
$ tezos-client activate protocol \
  ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp \
  with fitness 5 and key activator and parameters tmp/protocol_parameters.json

Injected BMHupUhxqJqT

This command injects a so-called “activation block” to the blockchain (the command returns the prefix of the hash of this block). This block is the only one using the genesis protocol. It is a block that contains only one operation: the operation that activates the next protocol (in our case, demo_noops). The next block in the blockchain will be the first block using the activated protocol. Let us detail the parameters of this command:

  • ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp is the hash of the protocol to be activated. The protocol must be registered.
  • activator is an alias for an activation secret key. In this example, the corresponding public key has been passed as a parameter to tezos-node at startup (using the --sandbox argument). The alias is known to the client because it is added by default in sandbox mode.
  • 5 is the fitness of the activation block (more details below). It can be any number.
  • protocol_parameters.json is a file that contains protocol-specific initialization parameters. There are no parameters for demo_noops, so this file contains an empty json object (i.e. { }).

We suggest the reader runs the following two commands to inspect the first two blocks of the blockchain (in particular the values of the "protocol", "hash", "predecessor", "level", and "fitness" keys).

$ tezos-client rpc get /chains/main/blocks/head
$ tezos-client rpc get /chains/main/blocks/head~1

Protocol Structure and Compilation

Currently, the embedded protocols live in the tezos repository besides the rest of the code. They follow the naming convention proto_*. The code of proto_demo_noops is organized as shown below:

$ ls -R src/proto_demo_noops

src/proto_demo_noops/lib_protocol:
TEZOS_PROTOCOL main.ml main.mli dune.inc tezos-protocol-demo-noops.opam dune
tezos-embedded-protocol-demo-noops.opam

The protocol code resides in the lib_protocol directory. A protocol must define a TEZOS_PROTOCOL json file that contains the hash of the protocol and the list of OCaml modules.

$ cat src/proto_demo_noops/lib_protocol/TEZOS_PROTOCOL

{
    "hash": "ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp",
    "modules": ["Main"]
}

Besides the TEZOS_PROTOCOL and main.ml[i] files, the other files in lib_protocol are used for compiling the protocol, checking that it respects the restrictions explained below, and for linking it with the other components of the node.

Currently, protocols are compiled differently depending on whether they are embedded or injected. Injected protocols are compiled by the Tezos compiler embedded in tezos-node. Embedded protocols are compiled as OPAM libraries using the standard toolchain. We leave the practical details for a future post.

Protocol Interface

The economic protocol is a sandboxed component restricted in the following two ways.

  • It can only access modules defined by the protocol environment.

  • It must define a module Main which implements the interface Updater.PROTOCOL from src/lib_protocol_environment/sigs/v1/updater.mli. The shell interacts with the protocol through this interface.

In addition, just like any other node component, the protocol can define RPC services to interact with a client. We will address RPCs in a later post but there is no difficulty here apart from getting accustomed with the Tezos RPC library.

Environment

The environment of the protocol is a fixed set of OCaml modules (their signatures are declared in src/lib_protocol_environment/sigs/v1/), consisting in a carefully chosen subset of the OCaml standard library, plus specialized utility modules. This form of sandboxing the protocol ensures that the protocol code does not use unsafe functions. The Tezos documentation explains in more detail the restrictions the environment imposes.

Any datatype used by the protocol is defined in this environment (in particular, all modules or types mentioned below). The following is the list of Tezos-specific modules defined by the environment that we will be mentioning throughout this blog post: Block_header, Context, Operation, and Updater.

Updater.PROTOCOL

At a very high-level, a protocol must:

  1. implement protocol-specific types, such as the type of operations or protocol-specific block header data (in addition to the shell generic header),

  2. define under which conditions a block is a valid extension of the current blockchain, and define an ordering on blocks to arbitrate between concurrent extensions.

For instance, in a bitcoin-like protocol, the supported operations are transactions, and the block header data contains a proof of work (PoW) stamp. A block is valid if its operations are supported by enough funds and the PoW stamp is correct.

For the second point, at a conceptual level, the protocol defines the function apply: context -> block -> (context * fitness) option which is called whenever the node processes a block. context represents the protocol state and fitness is an integer used to compare blocks. The context is therefore protocol-specific, it may contain, for instance, a list of accounts and their balance. It must contain enough information to determine the validity of a new block. The fitness defines a total ordering between blocks (and therefore between chains). The option type is used here to represent block validity: the function returns None when the block is not valid, while if it is valid, it returns the block’s fitness and the updated protocol state, obtained after applying (the operations contained in) the block.

The signature PROTOCOL in module Updater captures these general ideas (explained in more detail in the Tezos white paper), but is slightly more complex, mostly for efficiency reasons. In this first article, we will cover only some aspects of the interface and we will cover it more fully in later posts.

Concretely, a context (represented by the type Context.t) is a disk-based immutable key-value store, namely, a map from string list to MBytes.t. Such a loosely structured datatype should accommodate most protocols. A fitness (represented by the type Fitness.t) is a list of byte arrays. A total order on blocks is obtained by comparing their fitness first by length and then lexicographically.

A Tezos block is composed of a block header (of type Block_header.t) and a list of operations. A block header has two parts, a protocol-independent shell header (described here) and a protocol-specific header, which is a byte array (with type MBytes.t). Similarly, operations (of type Operation.t) have a protocol independent shell header, and a protocol-specific header. For instance, Block_header.t is defined as follows.

  type t = {
    shell: shell_header ;
    protocol_data: MBytes.t ;
  }

As part of implementing the PROTOCOL signature, the protocol must in particular provide concrete types for the protocol-specific block header (type block_header_data) and operations (type operation_data). These types are private to the protocol. The only functions exported to the shell are encoders/decoders. This allows the shell to serialize these types, either in binary format or in json. Typically, the binary format is used for P2P communications, and json is used for human-readable RPCs. Here is an excerpt from the PROTOCOL signature where these types are declared:

  (** The version specific type of blocks. *)
  type block_header_data

  (** Encoding for version specific part of block headers.  *)
  val block_header_data_encoding: block_header_data Data_encoding.t

  (** A fully parsed block header. *)
  type block_header = {
    shell: Block_header.shell_header ;
    protocol_data: block_header_data ;
  }

Note the analogy between Block_header.t (the shell’s view of the block header) and block_header (the protocol’s view of the block header).

Several functions declared in the PROTOCOL signature realize together the apply functionality: begin_application, begin_partial_application, begin_construction, apply_operation, and finalize_block. A typical apply is represented by a call to begin_(application|construction), followed by a sequence of calls to apply_operation, one for each operation in the block, and finally a call to finalize_block. These functions use values with types validation_result and validation_state. Defined by the PROTOCOL signature, the type validation_result represents the result of a block application, and it is a record type that contains most notably a context and a fitness. validation_state is a protocol-defined datatype used as intermediary state between applications of operations. To understand the usage of these two types, it may be useful to consider the following simplification of the types of the five functions mentioned:

begin_application: Context.t -> block_header -> validation_state
begin_partial_application: Context.t -> block_header -> validation_state
begin_construction: Context.t -> ?protocol_data: block_header_data -> validation_state
apply_operation: validation_state -> operation ->
validation_state finalize_block: validation_state -> validation_result

We briefly describe the role of these five functions:

  • begin_application is used when validating a block received from the network.

  • begin_partial_application is used when the shell receives a block more than one level ahead of the current head (this happens, for instance, when synchronizing a node). This function should run quickly, as its main role is to reject invalid blocks from the chain as early as possible.

  • begin_construction is used by the shell when instructed to build a block and for validating operations as they are gossiped on the network. This two cases are distinguished by the optional protocol_data argument: when only validating operations the argument is missing, as there is no block header. In both of these cases, the operations are not (yet) part of a block which is why the function does not expect a shell block header.

  • apply_operation is called after begin_application or begin_construction, and before finalize_block, for each operation in the block or in the mempool, respectively. Its role is to validate the operation and to update the (intermediary) state accordingly.

  • finalize_block represents the last step in a block validation sequence. It produces the context that will be used as input for the validation of the block’s successor candidates.

Another important function in the PROTOCOL interface is init, which is called when the protocol is activated. It takes as parameters a context and the shell header of the last block of the previous protocol. The context is the context corresponding to this last block, which includes the protocol parameters given at activation time. It returns a validation_result, which contains a context that is prepared for the new protocol. Note that the new context may change the key-value structure of the store compared with the previous protocol. init is therefore responsible for making the migration of the context from the previous protocol to the current protocol.

Finally, let us emphasize that the protocol is a stateless component. Rather than maintaining a mutable state, it implements pure functions that that take a state as a parameter and return a new state. The shell is responsible to store this state between function calls.

Protocol demo_noops

The demo_noops protocol is very simple:

  • It has no operations (hence no-ops).
  • It does not update its state, context is never modified.
  • The fitness of a block is the block’s level (i.e. its height in the blockchain).

We now go through the types and functions which do not have a trivial definition. First, we simply choose to have a string as the block header. Therefore we define in main.ml:

  type block_header_data = string

  let block_header_data_encoding =
    Data_encoding.(obj1 (req "block_header_data" string))

For the encoding of the (protocol-specific) block header we rely on the data_encoding library, explained here.

As there are no operations, the type of an operation header is just unit. Similarly, as we do not use the other helper datatypes like block_header_metadata and `operation_receipt, we simply set these types to unit.

Next, we need to define a validation_state. We define it as record datatype that contains a context and a fitness, because these need to be passed to the validation_result returned by finalize_block.

  type validation_state = {
    context : Context.t ;
    fitness : Fitness.t ;
  }

Concerning the fitness, we assume that the protocol is instantiated from genesis. Note that this may not be the case in general. demo_noops could very well be instantiated from a previous protocol with a totally different format for the fitness. The protocol should be able to adjust to different fitness models. Here, however, we use the same fitness model as genesis (and alpha), where the fitness has the form xx:xxxxxxxxxxxxxxxx. That is, the fitness is a list of two byte arrays, the first one (xx, of length 1), representing the protocol version, and the second one encoding an int64 number (thus of length 8). Recall that there is only one block using the genesis protocol. For this block, the fitness’ first element is 00 and its second element encodes the integer given as the fitness parameter when activating the next protocol. In demo_noops, the first element is 01 and the second element represents the level.

The helper functions needed to implement the fitness are as follows:

  let version_number = "\001"

  let int64_to_bytes i =
    let b = MBytes.create 8 in
    MBytes.set_int64 b 0 i;
    b

  let fitness_from_level level =
    [ MBytes.of_string version_number ;
      int64_to_bytes level ]

The fitness of a new block is actually set in begin_construction, which has the following very simple implementation:

  let begin_construction
      ~chain_id:_
      ~predecessor_context:context
      ~predecessor_timestamp:_
      ~predecessor_level
      ~predecessor_fitness:_
      ~predecessor:_
      ~timestamp:_
      ?protocol_data:_ ()
    =
    let fitness = fitness_from_level Int64.(succ (of_int32 predecessor_level)) in
    ... (* output a log message *)
    return { context ; fitness }

The implementation of the other main functions is trivial: begin_application just builds the validation state from the predecessor context and the fitness from (the shell part of) the block header. begin_partial_application behaves like begin_application. apply_operation returns an error (however, it is never called), and finalize_block builds a validation result from the validation state by copying the context and the fitness, and setting default values for the other fields. Most functions also record a log message which allows one to see when these functions are called during the node’s execution. They also show how the fitness is updated.

Finally, this protocol does not define any RPC.

let rpc_services = RPC_directory.empty

Baking a block

We can build a rudimentary baker simply using the RPCs provided by the node. The RPC to inject a block is /injection/block. However, we need to provide an hexadecimal binary encoding of the block header. To obtain it we use the following RPC: /chains/main/blocks/head/helpers/forge_block_header. This RPC expects as argument a json representation of the block header. The json representation of the shell header (the protocol-independent part of the header) can be obtained with the following RPC: /chains/main/blocks/head/helpers/forge_block_header.

We will thus use the following RPCs to bake a block:

  1. /chains/main/blocks/head/helpers/preapply/block
  2. /chains/main/blocks/head/helpers/forge_block_header
  3. /injection/block

We call the first RPC with the protocol hash, the protocol block header, and the (empty) list of operations. The RPC service calls begin_construction and finalize_block of the demo_noops protocol and returns the built (but not injected) block in json format. Notice the json representation of the protocol block header data "block_header_data": "hello world") is the one we defined in our implementation of demo_noops.

$ tezos-client -p ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im \
  rpc post /chains/main/blocks/head/helpers/preapply/block with \
  '{"protocol_data":
      {"protocol": "ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp",
       "block_header_data": "hello world"},
       "operations": []}'

{ "shell_header":
    { "level": 2, "proto": 1,
      "predecessor": "BLCJ5s7SGvMzmJd7Y7jbpuNiTN5c8yz9L4Q2GtBpLaJTAHKMEoz",
      "timestamp": "2019-06-21T15:35:37Z", "validation_pass": 0,
      "operations_hash":
        "LLoZS2LW3rEi7KYU4ouBQtorua37aWWCtpDmv1n2x3xoKi6sVXLWp",
      "fitness": [ "01", "0000000000000002" ],
      "context": "CoV3MLpgMM91DbHGuqGz7uwgmMYjnh7EQSsqt1CxPqvxQpU9pczA" },
  "operations": [] }

tezos-client can use protocol-specific extensions. By default, tezos-client tries to use the extension corresponding to the node’s protocol. In our case no such extension has been given, therefore we need to specify an extension using the -p XXX option, where XXX is a protocol hash.

Now we use the second RPC to obtain the binary encoding of the protocol block header:

$ tezos-client -p ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im \
    rpc post /chains/main/blocks/head/helpers/forge_block_header \
    with '{"level": 2, "proto": 1,
           "predecessor": "BLCJ5s7SGvMzmJd7Y7jbpuNiTN5c8yz9L4Q2GtBpLaJTAHKMEoz",
           "timestamp": "2019-06-21T15:35:37Z", "validation_pass": 0,
           "operations_hash": "LLoZS2LW3rEi7KYU4ouBQtorua37aWWCtpDmv1n2x3xoKi6sVXLWp",
           "fitness": ["01", "0000000000000002"],
           "context": "CoV3MLpgMM91DbHGuqGz7uwgmMYjnh7EQSsqt1CxPqvxQpU9pczA",
           "protocol_data": "0000000b68656c6c6f20776f726c64"}'

{ "block": "0000000201b478f20b61340c9e8290d7b45edf057fd180891d0e0b290abc..." }

Notice the last field protocol_data. It must contain the binary-encoded block header data. Remember that we specified this encoding in the protocol with

let block_header_data_encoding =
  Data_encoding.(obj1 (req "block_header_data" string))

We can compute the binary encoding on the client side, for instance using the Data_encoding library, or by writing the encoder in a different language using the public specification of the Data_encoding library.

For this example, "0000000b68656c6c6f20776f726c64" is the binary encoding of "hello world".

Finally, the last RPC injects the block. After the block is validated by the protocol (the RPC service calls begin_application and finalize_block), the RPC returns its hash.

$ tezos-client -p ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im \
  rpc post injection/block with \
   '{"data": "0000000201b478f20b61340c9e8290d7b45edf057fd180891d0e0b290abc...",
     "operations": []}'

"BM6qcDPhm57sXHv1js25qcy9WESah1C3qcpKn9y8bRZzpf8s7g8"

We can look at the newly created block:

$ tezos-client -p ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im \
  rpc get /chains/main/blocks/head/

{ "protocol": "ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp",
  "chain_id": "NetXdQprcVkpaWU",
  "hash": "BMKeY5PDbm3acKDPUt7XnARkFBj5JoUDfMbBqYTYvWrGFHPt89a",
  "header":
    { "level": 2, "proto": 1,
      "predecessor": "BLCJ5s7SGvMzmJd7Y7jbpuNiTN5c8yz9L4Q2GtBpLaJTAHKMEoz",
      "timestamp": "2019-06-21T15:35:37Z", "validation_pass": 0,
      "operations_hash":
        "LLoZS2LW3rEi7KYU4ouBQtorua37aWWCtpDmv1n2x3xoKi6sVXLWp",
      "fitness": [ "01", "0000000000000002" ],
      "context": "CoV3MLpgMM91DbHGuqGz7uwgmMYjnh7EQSsqt1CxPqvxQpU9pczA",
      "block_header_data": "hello world" },
  "metadata":
    { "protocol": "ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp",
      "next_protocol": "ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp",
      "test_chain_status": { "status": "not_running" },
      "max_operations_ttl": 0, "max_operation_data_length": 0,
      "max_block_header_length": 100, "max_operation_list_length": [] },
  "operations": [] }

For completeness, we show below the node’s trace, that is, the output obtained when executing the following command:

$ ./src/bin_node/tezos-sandboxed-node.sh 1 --connections 1

After the node’s initialization, we see the following:

Generating a new identity... (level: 0.00)
Stored the new identity (idrCKw61WKnEHc2kuqd7BZvV1V39CN) into '/tmp/tezos-node.cJVCgcW0/identity.json'.
Jun 21 17:34:22 - node.main: Starting the Tezos node...
Jun 21 17:34:22 - node.main: No local peer discovery.
Jun 21 17:34:22 - node.main: Peer's global id: idrCKw61WKnEHc2kuqd7BZvV1V39CN
Jun 21 17:34:22 - main: shell-node initialization: bootstrapping
Jun 21 17:34:22 - main: shell-node initialization: p2p_maintain_started
Jun 21 17:34:22 - validator.block: Worker started
Jun 21 17:34:22 - validation_process.sequential: Initialized
Jun 21 17:34:22 - node.validator: activate chain NetXdQprcVkpaWU
Jun 21 17:34:22 - validator.chain_1: Worker started for NetXdQprcVkpa
Jun 21 17:34:22 - prevalidator.NetXdQprcVkpa.ProtoGenesis_1: Worker started for NetXdQprcVkpa.ProtoGenesis
Jun 21 17:34:22 - node.main: Starting a RPC server listening on ::ffff:127.0.0.1:18731.
Jun 21 17:34:22 - node.main: The Tezos node is now running!

After the protocol activation command, we further see the following output:

Jun 21 17:34:53 - demo-noops: init: fitness = 00::0000000000000005
Jun 21 17:34:53 - demo-noops: init: fitness = 00::0000000000000005
Jun 21 17:34:54 - validator.block: Block BLCJ5s7SGvMzmJd7Y7jbpuNiTN5c8yz9L4Q2GtBpLaJTAHKMEoz successfully validated
Jun 21 17:34:54 - validator.block: Pushed: 2019-06-21T15:34:53-00:00, Treated: 2019-06-21T15:34:53-00:00, Completed: 2019-06-21T15:34:54-00:00
Jun 21 17:34:54 - prevalidator.NetXdQprcVkpa.ProtoDemoNoo_1: Worker started for NetXdQprcVkpa.ProtoDemoNoo
Jun 21 17:34:54 - demo-noops: begin_construction (mempool): pred_fitness = 00::0000000000000005  constructed fitness = 01::0000000000000002
Jun 21 17:34:54 - prevalidator.NetXdQprcVkpa.ProtoGenesis_1: Worker terminated [NetXdQprcVkpa.ProtoGenesis]
Jun 21 17:34:54 - validator.chain_1: Update current head to BLCJ5s7SGvMzmJd7Y7jbpuNiTN5c8yz9L4Q2GtBpLaJTAHKMEoz (fitness 00::0000000000000005), same branch
Jun 21 17:34:54 - validator.chain_1: Pushed: 2019-06-21T15:34:54-00:00, Treated: 2019-06-21T15:34:54-00:00, Completed: 2019-06-21T15:34:54-00:00

After the first RPC, we obtain:

Jun 21 17:35:37 - demo-noops: begin_construction (block): pred_fitness = 00::0000000000000005  constructed fitness = 01::0000000000000002
Jun 21 17:35:37 - demo-noops: finalize_block: fitness = 01::0000000000000002

There is no output corresponding to the second RPC. After the third and last RPC, we see the following output:

Jun 21 17:38:35 - demo-noops: begin_application: pred_fitness = 00::0000000000000005  block_fitness = 01::0000000000000002
Jun 21 17:38:35 - demo-noops: finalize_block: fitness = 01::0000000000000002
Jun 21 17:38:35 - validator.block: Block BM6qcDPhm57sXHv1js25qcy9WESah1C3qcpKn9y8bRZzpf8s7g8 successfully validated
Jun 21 17:38:35 - validator.block: Pushed: 2019-06-21T15:38:35-00:00, Treated: 2019-06-21T15:38:35-00:00, Completed: 2019-06-21T15:38:35-00:00
Jun 21 17:38:35 - demo-noops: finalize_block: fitness = 01::0000000000000002
Jun 21 17:38:35 - demo-noops: begin_construction (mempool): pred_fitness = 01::0000000000000002  constructed fitness = 01::0000000000000003
Jun 21 17:38:35 - prevalidator.NetXdQprcVkpa.ProtoDemoNoo_1: switching to new head BM6qcDPhm57sXHv1js25qcy9WESah1C3qcpKn9y8bRZzpf8s7g8
Jun 21 17:38:35 - prevalidator.NetXdQprcVkpa.ProtoDemoNoo_1: Pushed: 2019-06-21T15:38:35-00:00, Treated: 2019-06-21T15:38:35-00:00, Completed: 2019-06-21T15:38:35-00:00
Jun 21 17:38:35 - validator.chain_1: Update current head to BM6qcDPhm57sXHv1js25qcy9WESah1C3qcpKn9y8bRZzpf8s7g8 (fitness 01::0000000000000002), same branch
Jun 21 17:38:35 - validator.chain_1: Pushed: 2019-06-21T15:38:35-00:00, Treated: 2019-06-21T15:38:35-00:00, Completed: 2019-06-21T15:38:35-00:00

Wrapping up with Python

Two python scripts summarize what we have covered in this post: . tests_python/tests/test_injection.py . tests_python/examples/proto_demo_noops.py

This first one shows how to inject a new protocol in a node. The second script launches a node in sandbox, activates the demo_noops protocol, and bakes a block. See here for more details about how to run Python scripts, including installation instructions.

Excerpt from tests_python/examples/proto_demo_noops.py:

with Sandbox(paths.TEZOS_HOME,
                constants.IDENTITIES,
                constants.GENESIS_PK,
                log_dir='tmp') as sandbox:
    # launch a sandbox node
    sandbox.add_node(0)
    client = sandbox.client(0)

    protocols = client.list_protocols()
    assert PROTO_DEMO in protocols

    parameters = {}
    client.activate_protocol_json(PROTO_DEMO, parameters, key='activator',
                                    fitness='1')

    head = client.rpc('get', '/chains/main/blocks/head/', params=PARAMS)
    # current protocol is still genesis and level == 1
    assert head['header']['level'] == 1
    assert head['protocol'] == PROTO_GENESIS

    time.sleep(1)

    # bake a block for new protocol, using fours RPCs:
    # - helpers/preapply/block builds the block
    # - helpers/forge_block_header encodes the whole block header
    # - /injection/block injects it
    message = "hello world"

    data = {"protocol_data":
            {"protocol": PROTO_DEMO, "block_header_data": message},
            "operations": []}
    block = client.rpc(
        'post',
        '/chains/main/blocks/head/helpers/preapply/block',
        data=data,
        params=PARAMS)

    protocol_data = {'block_header_data': message}
    encoded = forge_block_header_data(protocol_data)

    shell_header = block['shell_header']
    shell_header['protocol_data'] = encoded
    encoded = client.rpc(
        'post',
        '/chains/main/blocks/head/helpers/forge_block_header',
        data=shell_header,
        params=PARAMS)

    inject = {'data': encoded['block'], 'operations': []}
    client.rpc('post', '/injection/block', data=inject, params=PARAMS)

    head = client.rpc('get', '/chains/main/blocks/head/', params=PARAMS)
    assert head['header']['level'] == 2

Conclusion

We saw how to write, compile, register, activate and use a simple protocol. In the next blog post, we’ll make this protocol more realistic by adding operations and block validation. We’ll also improve the client interface by defining RPCs in the protocol, as well as extending the tezos-client command-line interface with protocol-specific commands.


Emmy+: an improved consensus algorithm

Underlying the Tezos network is a consensus algorithm, which, for the ease of reference, we will call Emmy. Consensus ensures that participants agree on the same blockchain and on its state. The ideas behind Emmy are described in the white paper and the specifics to its implementation can be found in the documentation. We recall that Emmy is a PoS based consensus with a use of endorsements to speed-up confirmation times and reduce...

Read More
Release of Mainnet May

Today we are proud to announce a new release of the Tezos node and client. The main features present in this release are: node support for snapshots (boot and synchronize a node in minutes), node support for new history modes (not everyone needs to be an archive), client integration for a multi-signature contract. Remember that besides major features there are always a myriad of smaller improvements, have a look at the changelog. We encourage bakers to switch to history mode “full” using a snapshot before the activation of...

Read More
Athens on the testchain

The quorum for the second voting phase has been reached, with a supermajority of Yays! This means that the voting procedure can proceed to its third stage: the test chain for Athens is going to be launched soon, approximately Friday April 12th. To accompany that, we just released a new version of the Tezos node and its baking daemons. This release includes many improvements, most notably a new --enable-testchain option. Users who want to participate in the test chain should upgrade...

Read More
Meanwhile at Nomadic Labs #2

The last few weeks have been pretty intense at Nomadic with the preparation of the first community voted upgrade! The team has been working hard on some special future improvements to Tezos… Improvements to the consensus layer Tezos has the unique ability to amend itself which allows us to propose state of the art research to the network at any time. This is not a far fetched ideal and will happen sooner rather than later in a series of new efforts...

Read More
Athens: Second Voting Period

As we approach the completion of the first active voting period, the proposal period, we would like to describe what’s to come during the second voting period. The first voting period saw the Athens protocols injection and bakers upvoted Athens A and/or Athens B, with Athens A, the most popular proposal, promoted for the second voting phase: the test voting period. In this new period, participants are asked to vote on whether to advance Athens A to the next stage: the...

Read More
Athens: Proposals Injected!

Today marks an important milestone for Tezos. We just triggered the beginning of the first on-chain vote for self amendment. This process could end in the successful migration from current protocol alpha to Athens in about three months, if the participants decide so. As advertised in the last meanwhile at Nomadic and as detailed in our previous post, we injected the hashes of two proposals. Both include the same enhancements except for one: the decreased amount in the number of tokens needed to...

Read More
Athens: Our Proposals for the First Voted Amendment

This blog post is a preview of Athens: our protocol proposal for the first voted upgrade of Tezos. As announced in the last meanwhile at Nomadic, we shall propose two upgrades: one lowers the roll size to 8,000 tez, the other leaves it unchanged at 10,000 tez. Both alternatives will include an increase of the gas limit. The hashes of both versions will be proposed on mainnet later this week, now that a new proposal period has begun. Later this week, we will publish a...

Read More
Amendments at Work in Tezos

We are now on the verge of submitting a protocol upgrade to a vote, and it seems like a good opportunity to explain in details the way in which Tezos node handles amendment in practice. Brace yourselves, this article is quite technical, as are all articles in our in-depth category. Still, as we did in the previous one on snapshots, we’ll try to explain the stakes and announcements and give a brief summary in a short foreword understandable even by non-programmers. The original whitepaper...

Read More
Introducing Snapshots and History Modes for the Tezos Node

In this article, we introduce two new features for the Tezos node: snapshots and history modes. A snapshot is a file that contains everything necessary to restore the state of a node at a given block. A node restored via a snapshot can synchronise and help other nodes synchronise in the existing network. The only difference is that you cannot query the chain context (balances, baking rights, etc.) before the restoration point, but you can still get the full chain history. In conjunction, we also...

Read More

Receive Updates

ATOM

Contacts