Nomadic Labs
Nomadic Labs
Introducing mockup mode for tezos-client

Presenting tezos-client’s new mockup mode feature

We are pleased to announce that the tezos-client binary has a new feature aimed at contract and tool developers alike: the mockup mode.

Mockup mode allows easy prototyping of Tezos applications and smart contracts locally. By local we mean:

  • The relevant data files sit in a directory on your computer’s local filesystem.
  • These files are a lightweight emulation of the internal state of a Tezos single-node network.
  • Thus, networking communications infrastructure that a node would be wrapped in, has been stripped away.
  • Likewise, the consensus mechanisms that would be needed for a live blockchain with a network and multiple nodes, have been stripped away.
  • The state is directly accessible and modifiable, since it’s just files on your computer.
  • You don’t need (complex) setups of node-client interactions. There is no infrastructure aside from your own filesystem with a bundle of state files in a directory. It’s easy to inspect these files, modify them, and play with different configurations and protocols.

If a single sandboxed node were an apricot then mockup mode would be the apricot kernel, and we could write:

    mockup mode = kernel_of(one sandboxed Tezos node)

Our motivation in building mockup mode was to give our developers, who are building and testing Tezos smart contracts, an easy local environment offering a fast development cycle which needs only lightweight local state files, and which does not require a running blockchain. Now, we’d like to share the joy of this new tool with you.

The features described below are available on the master branch, and will be included in version 8.0.1

This post is a practical guide to mockup mode’s features (see also the documentation). Be prepared for lots of command-line snippets, which you are welcome to run for yourself!

Overview

The basic command: tezos-client

tezos-client is the main tool for advanced user interaction with the Tezos blockchain.

tezos-client can prepare transactions; evaluate, typecheck and originate contracts; and encode/decode data when interacting with nodes. It also acts as a wallet, allowing to sign arbitrary data — including, of course, transactions.

The mockup mode of tezos-client supports these operations (with slight limitations2), with the convenience that it does not need to be connected to a live Tezos node. All operations are local, in the sense above.

tezos-client in mockup mode does two things to compensate for not communicating with the live network:

  1. It allows the user to specify — or if none are specified, it invents — dummy values for required initialisation parameters which would usually be gathered from a live node. Examples include: the head of the chain; or the client’s network identifier.
  2. It simulates activation of the protocol, and runs local implementations of the RPCs (Remote Procedure Calls).

Three modes of operation

Mockup mode can run in three ways:

  1. Stateless mode.
    In this mode, tezos-client operates on its inputs and returns a value. Nothing is written to disk, and no state is preserved between calls to the client. This is the default.
  2. Stateful mode.
    In this mode, tezos-client creates or manipulates a state on disk. The switch for this is --base-dir <directory_name>; example here.
  3. Stateful asynchronous mode.
    This mode adds baking. The command-line switch for this is --base-dir <directory_name> --asynchronous; example here.

Capabilities of mockup mode

The current implementation of mockup mode can:

  • Typecheck, serialize, sign and evaluate a contract.
    These features work in stateless mode.
  • Perform transactions, originations, and contract calls — mimicking sandboxed mode but without a node.
    These features require a state.
  • Register delegates and bake blocks.
    These features require an asynchronous state.

In practice we find it simplest to just use state and remember to delete it between sessions — but your needs may vary and the tool will accomodate them.

We will now consider the capabilities in more detail.

Run a mockup client in stateless mode

Typecheck and evaluate scripts

The mockup mode can typecheck and evaluate scripts. Let’s try this on a script hardlimit.tz, which you can download from Tezos master branch or create locally as follows:

$ cat > hardlimit.tz <<EOF
parameter unit ;
storage int ;
code { # This contract stops accepting transactions after N incoming transactions
       CDR ; DUP ; PUSH int 0 ; CMPLT; IF {PUSH int -1 ; ADD} {FAIL};
       NIL operation ; PAIR} ;
EOF
  • Typechecking a script:
    $ tezos-client --protocol ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK \
      --mode mockup typecheck script hardlimit.tz
    
    Well typed  
    Gas remaining: 1039988.27 units remaining
    
  • Evaluating a script:
    $ tezos-client --protocol ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK \
      --mode mockup run script hardlimit.tz \
      on storage '2' and input 'Unit'
    
     storage
      1
     emitted operations
    
     big_map diff
    

Without the --protocol option, the mockup mode will choose a protocol for you.3

Query available mockup protocols

We can query the list of the Tezos protocols that mockup mode supports:

$ tezos-client list mockup protocols

As this article went to print (so to speak), this command returns three protocol identifiers (ignoring any Warnings):

ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK
PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo
PsCARTHAGazKbHtnKfLzQg3kms52kSRpgnDY982a9oYsSXRLQEb

A word on the protocol for naming protocols: The Tezos protocol IDs above are based on hashes, but the start of each ID hints at the release name of the corresponding protocol. The three items above correspond to protocols called alpha (a development version of the Tezos protocol), Delphi, and Carthage.

Getting these IDs matters because a Tezos blockchain requires a protocol, thus in particular setting up a mockup state requires us to choose a protocol. The list above tells us what’s available.

Run a mockup client with state

Giving the mockup client some state allows access more of the available functionalities. In particular, given a state we can operate on it, including:

  • transferring Tez cryptocurrency tokens (ꜩ),
  • originating (deploying) contracts,
  • importing keys, and
  • querying balances or (more generally) making RPC queries on the chain’s current state.

A useful command alias: mockup-client

A shell alias will let us call tezos-client with --mode mockup and --base-dir /tmp/mockup, and so save us keystrokes later:

$ alias mockup-client='tezos-client --mode mockup --base-dir /tmp/mockup'

Our first state

Time to make a mockup session with some state!

Making the state

$ mockup-client --protocol ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK create mockup
Created mockup client base dir in /tmp/mockup
Tezos address added: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
Tezos address added: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN
Tezos address added: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
Tezos address added: tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv
Tezos address added: tz1ddb9NMYHZi5UzPdzTZMYQQZoMub195zgv

Note that:

  • The state is stored in /tmp/mockup because of the --base-dir option in mockup-client.
  • The switch --protocol ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK means that this mockup session will use protocol alpha for all subsequent commands on this state (see next point).
  • Mockup mode does not support protocol updates4, so if we want a new protocol we need to start from a new state. Thus, for this session we are stuck with our initial choice of alpha. In the near future, mockup mode will support protocol migrations to facilitate protocol switching.

The output above confirms that:

  • A mockup state data directory /tmp/mockup has been created. Data is
    • in /tmp/mockup for non-mockup-specific elements (like accounts), and
    • in /tmp/mockup/mockup for mockup-specific data (like mempool, trashpool, and context) — see asynchronous state for details).
  • Five accounts have been added, and their addresses are listed.

The five accounts are called bootstrap1 to bootstrap5 (see command below). The reader familiar with Tezos’ sandboxed client may recognize them as the preconfigured bootstrap1 to bootstrap5 accounts which it creates.

List known addresses

We can now use standard commands, e.g., to list known addresses.

$ mockup-client list known addresses
bootstrap5: tz1ddb9NMYHZi5UzPdzTZMYQQZoMub195zgv (unencrypted sk known)
bootstrap4: tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv (unencrypted sk known)
bootstrap3: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU (unencrypted sk known)
bootstrap2: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN (unencrypted sk known)
bootstrap1: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx (unencrypted sk known)

Transfer tokens

We can execute the canonical example of a transfer from one address to another.5

$ mockup-client transfer 100 from bootstrap1 to bootstrap2
Node is bootstrapped.
Estimated gas: 1427 units (will add 100 for safety)
Estimated storage: no bytes added
Operation successfully injected in the node.
Operation hash is 'ooVjVsPgUuy4grpDBbKr5QPc667JCQ6nbMeqeTjqiRzXiCiy5e9'
NOT waiting for the operation to be included.
Use command
  tezos-client wait for ooVjVsPgUuy4grpDBbKr5QPc667JCQ6nbMeqeTjqiRzXiCiy5e9 to be included --confirmations 30 --branch BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU
and/or an external block explorer to make sure that it has been included.
This sequence of operations was run:
  Manager signed operations:
    From: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
    Fee to the baker: ꜩ0.000404
    Expected counter: 1
    Gas limit: 1527
    Storage limit: 0 bytes
    Balance updates:
      tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx ................ -ꜩ0.000404
      fees(the baker who will include this operation,0) ... +ꜩ0.000404
    Transaction:
      Amount: ꜩ100
      From: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
      To: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN
      This transaction was successfully applied
      Consumed gas: 1427
      Balance updates:
        tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx ... -ꜩ100
        tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN ... +ꜩ100

Let’s check that the transfer has been registered:

  1. First, we check that the sender bootstrap1 has indeed paid the transfer amount, plus fees:
    $ mockup-client get balance for bootstrap1
    
    3999899.999596 ꜩ
    
  2. Second, we check that the receiver bootstrap2 indeed has an extra 100 ꜩ in its balance:
    $ mockup-client get balance for bootstrap2
    
        4000100 ꜩ
    

Something more advanced: interacting with contracts

We developed mockup mode as a safe environment to develop and test Michelson smart contracts.

To interact with a contract, we must first originate (deploy) it. Let’s add a dummy contract to our mockup state:

$ mockup-client originate contract dummy transferring 100 from bootstrap1 running \
  'parameter unit; storage unit; code { CAR; NIL operation; PAIR}' --burn-cap 10
Node is bootstrapped.
Estimated gas: 1589.562 units (will add 100 + 36 for safety)
Estimated storage: 295 bytes added (will add 20 for safety)
Operation successfully injected in the node.
Operation hash is 'oor3iMLau7g9K78pWTrAETx5KwrE7jTWYR7euh2MC6pqReV8aX7'
NOT waiting for the operation to be included.
Use command
  tezos-client wait for oor3iMLau7g9K78pWTrAETx5KwrE7jTWYR7euh2MC6pqReV8aX7 to be included --confirmations 30 --branch BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU
and/or an external block explorer to make sure that it has been included.
This sequence of operations was run:
  Manager signed operations:
    From: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
    Fee to the baker: 0.000441
    Expected counter: 2
    Gas limit: 1726
    Storage limit: 315 bytes
    Balance updates:
      tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx ................ -0.000441
      fees(the baker who will include this operation,0) ... +0.000441
    Origination:
      From: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
      Credit: 100
      Script:
        { parameter unit ; storage unit ; code { CAR ; NIL operation ; PAIR } }
        Initial storage: Unit
        No delegate for this contract
        This origination was successfully applied
        Originated contracts:
          KT1QgvWVQHXDu6ryqQ1t3GN3UciToFbLhu7j
        Storage size: 38 bytes
        Paid storage size diff: 38 bytes
        Consumed gas: 1589.562
        Balance updates:
          tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx ... -0.0095
          tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx ... -0.06425
          tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx ... -100
          KT1QgvWVQHXDu6ryqQ1t3GN3UciToFbLhu7j ... +100

New contract KT1QgvWVQHXDu6ryqQ1t3GN3UciToFbLhu7j originated.
Contract memorized as dummy. 

We now check some things.

  • The contract account dummy is now listed as known along with all bootstrap accounts:
    $ mockup-client list known contracts
    
    dummy: KT1QgvWVQHXDu6ryqQ1t3GN3UciToFbLhu7j
    bootstrap5: tz1ddb9NMYHZi5UzPdzTZMYQQZoMub195zgv
    bootstrap4: tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv
    bootstrap3: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
    bootstrap2: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN
    bootstrap1: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
    
  • Contract dummy also has the expected amount of ꜩ in its balance:
    $ mockup-client get balance for dummy
    
    100 ꜩ
    

Let’s inspect our freshly-originated dummy contract and display part of its state through its storage:

$ mockup-client get contract storage for dummy
Unit

The contract’s storage can also be accessed in JSON format through the usual RPC mechanism:

$ mockup-client rpc get /chains/main/blocks/head/context/contracts/KT1QgvWVQHXDu6ryqQ1t3GN3UciToFbLhu7j/storage
{ "prim": "Unit" }

We can of course send some money to dummy and verify it has been added to the contract’s balance:

$ mockup-client transfer 100 from bootstrap3 to dummy
Node is bootstrapped.
Estimated gas: 2237.715 units (will add 100 for safety)
Estimated storage: no bytes added
Operation successfully injected in the node.
Operation hash is 'ooAe9HRnc1veUPTVPBtMEpfUi5isgYm4MzeD13MN8Nxfdgfa8AZ'
NOT waiting for the operation to be included.
Use command
  tezos-client wait for ooAe9HRnc1veUPTVPBtMEpfUi5isgYm4MzeD13MN8Nxfdgfa8AZ to be included --confirmations 30 --branch BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU
and/or an external block explorer to make sure that it has been included.
This sequence of operations was run:
  Manager signed operations:
    From: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
    Fee to the baker: ꜩ0.000485
    Expected counter: 1
    Gas limit: 2338
    Storage limit: 0 bytes
    Balance updates:
      tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU ................ -ꜩ0.000485
      fees(the baker who will include this operation,0) ... +ꜩ0.000485
    Transaction:
      Amount: ꜩ100
      From: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
      To: KT1QgvWVQHXDu6ryqQ1t3GN3UciToFbLhu7j
      This transaction was successfully applied
      Updated storage: Unit
      Storage size: 38 bytes
      Consumed gas: 2237.715
      Balance updates:
        tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU ... -ꜩ100
        KT1QgvWVQHXDu6ryqQ1t3GN3UciToFbLhu7j ... +ꜩ100
$ mockup-client get balance for bootstrap3
3999899.999515 
````


```shell
$ mockup-client get balance for dummy
200 ꜩ

The examples so far have used mockup mode’s default settings. Some use cases need a custom setup, so mockup mode lets us configure some initial parameters, as we discuss next.

Tune mockup parameters

For simplicity, mockup mode — like sandboxed mode — uses default values for wallet and protocol parameters. These default settings can be inspected and overridden to suit your needs.

The default configuration can be inspected as follows (recall that mockup-client is a command alias for tezos-client plus some parameters):

$ mockup-client config show
Default value of --bootstrap-accounts:
[ { "name": "bootstrap1",
    "sk_uri":
      "unencrypted:edsk3gUfUPyBSfrS9CCgmCiQsTCHGkviBDusMxDJstFtojtc1zcpsh",
    "amount": "3999799925405" },
  { "name": "bootstrap2",
    "sk_uri":
      "unencrypted:edsk39qAm1fiMjgmPkw1EgQYkMzkJezLNewd7PLNHTkr6w9XA2zdfo",
    "amount": "4000100000000" },
  { "name": "bootstrap3",
    "sk_uri":
      "unencrypted:edsk4ArLQgBTLWG5FJmnGnT689VKoqhXwmDPBuGx3z4cvwU9MmrPZZ",
    "amount": "3999899999515" },
  { "name": "bootstrap4",
    "sk_uri":
      "unencrypted:edsk2uqQB9AY4FvioK2YMdfmyMrer5R8mGFyuaLLFfSRo8EoyNdht3",
    "amount": "4000000000000" },
  { "name": "bootstrap5",
    "sk_uri":
      "unencrypted:edsk4QLrcijEffxV31gGdN2HU7UpyJjA8drFoNcmnB28n89YjPNRFm",
    "amount": "4000000000000" } ]
Default value of --protocol-constants:
{ "hard_gas_limit_per_operation": "1040000",
  "hard_gas_limit_per_block": "10400000",
  "hard_storage_limit_per_operation": "60000", "cost_per_byte": "250",
  "chain_id": "NetXynUjJNZm7wi",
  "initial_timestamp": "1970-01-01T00:00:00Z" }

We can tune these values with dedicated mockup mode creation switches. Generating the JSON data by hand is a pain (and dangerously error-prone) so we suggest to generate files corresponding to default values, and edit them.

To generate the JSON files related to protocol constants and bootstrap accounts configuration, just type:

$ mockup-client config init
Written default --bootstrap-accounts file: /tmp/mockup/bootstrap-accounts.json
Written default --protocol-constants file: /tmp/mockup/protocol-constants.json

We can now edit the files bootstrap-accounts.json and protocol-constants.json to later create a tuned mockup state.

For example, we can change the chain_id field of protocol-constants.json. We will compute a new chain identifier, which will replace the initial NetXynUjJNZm7wi value.

tezos-client compute chain id from seed my-chain-id
NetXKQNvsbETtvZ

Let’s create a new protocol constants configuration file, using jq to manipulate the JSON data.

$ cat /tmp/mockup/protocol-constants.json | \
  jq '.chain_id = "NetXKQNvsbETtvZ"' > tuned_up_protocol_constants.json

Assuming you have not renamed the files, you can create a new mockup setup by feeding the JSON configuration to the create mockup command with the following command-line invocation.

$ mv /tmp/mockup /tmp/mockup.old && \
     mockup-client --protocol ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK \
     create mockup \
     --protocol-constants tuned_up_protocol_constants.json \
     --bootstrap-accounts /tmp/mockup.old/bootstrap-accounts.json
Created mockup client base dir in /tmp/mockup
mockup client uses protocol overrides:
hard_gas_limit_per_operation: 1040000
hard_gas_limit_per_block: 10400000
hard_storage_limit_per_operation: 60000
cost_per_byte: 0.00025

mockup client uses custom bootstrap accounts:
name:bootstrap1
sk_uri:unencrypted:edsk3gUfUPyBSfrS9CCgmCiQsTCHGkviBDusMxDJstFtojtc1zcpsh
amount:3999799.925405;
name:bootstrap2
sk_uri:unencrypted:edsk39qAm1fiMjgmPkw1EgQYkMzkJezLNewd7PLNHTkr6w9XA2zdfo
amount:4000100;
name:bootstrap3
sk_uri:unencrypted:edsk4ArLQgBTLWG5FJmnGnT689VKoqhXwmDPBuGx3z4cvwU9MmrPZZ
amount:3999899.999515;
name:bootstrap4
sk_uri:unencrypted:edsk2uqQB9AY4FvioK2YMdfmyMrer5R8mGFyuaLLFfSRo8EoyNdht3
amount:4000000;
name:bootstrap5
sk_uri:unencrypted:edsk4QLrcijEffxV31gGdN2HU7UpyJjA8drFoNcmnB28n89YjPNRFm
amount:4000000
Tezos address added: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
Tezos address added: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN
Tezos address added: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
Tezos address added: tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv
Tezos address added: tz1ddb9NMYHZi5UzPdzTZMYQQZoMub195z

We can check that the chain id in our environment setup matches the chain id we obtained from the command line.

$ cat /tmp/mockup/mockup/context.json | jq .chain_id
"NetXKQNvsbETtvZ"

Context state

In addition to the two bootstrap-accounts.json and protocol-constants.json configuration files, stateful mockup mode stores state data in a single context.json file.

context.json is located under the mockup subdirectory of the base directory. In our running example, its absolute file name is /tmp/mockup/mockup/context.json. It contains in particular information about the current block hash or the shell header:

$ cat /tmp/mockup/mockup/context.json | \
  jq '.context | { block_hash: .block_hash, shell_header: .shell_header}'
{
  "block_hash": "BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU",
  "shell_header": {
    "level": 0,
    "proto": 0,
    "predecessor": "BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU",
    "timestamp": "1970-01-01T00:00:00Z",
    "validation_pass": 0,
    "operations_hash": "LLoZKi1iMzbeJrfrGWPFYmkLebcsha6vGskQ4rAXt2uMwQtBfRcjL",
    "fitness": [
      "01",
      "0000000000000000"
    ],
    "context": "CoUeJrcPBj3T3iJL3PY4jZHnmZa5rRZ87VQPdSBNBcwZRMWJGh9j"
  }
}

The directory where the context state resides, /tmp/mockup in our examples, is where mockup mode keeps all its data. In particular, it is key to supporting asynchronous operations in stateful mockup mode.

Running a mockup client with asynchronous state

In Tezos, extending the blockchain is a three-step process:

  1. An operation is emitted across the network of nodes.
  2. It gets validated, aggregated with other operations, and included in (baked in to) a block by a baker.
  3. The (cryptographic hash of the) block gets included in the next block.

See this paper for details (search for “In order to append transactions to the ledger, all blockchains follow a similar generic algorithm …”).

Thus, mockup mode offers a stateful asynchronous mode which simulates a two-step inclusion of operations in the Tezos chain which corresponds to steps 2 and 3 above.6

We must add two new files in the mockup subdirectory, to store:

  • operations waiting to be baked in (mempool.json) and
  • operations rejected (trashpool.json).

How to activate

To activate asynchronous stateful mockup mode, we reuse the initial command line invocation for state creation, with an --asynchronous flag:

$ rm -Rf /tmp/mockup && mockup-client create mockup --asynchronous
Created mockup client base dir in /tmp/mockup
creating mempool file at /tmp/mockup/mockup/mempool.json
Tezos address added: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
Tezos address added: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN
Tezos address added: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
Tezos address added: tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv
Tezos address added: tz1ddb9NMYHZi5UzPdzTZMYQQZoMub195zgv

This commands creates a fresh mockup directory, as for stateful mode, but adds another file to represent the mempool (mempool.json), which is initially empty.

Baking in asynchronous stateful mockup mode

Let’s add some operations to mempool.json by issuing two transfers.

$ mockup-client transfer 1 from bootstrap1 to bootstrap2
$ mockup-client transfer 2 from bootstrap2 to bootstrap3

These commands use the same syntax as for immediate stateful mockup mode; the fact that we are operating in asynchronous mode is auto-detected. Both transfer operations are now in the mempool, as we can verify:

$ cat /tmp/mockup/mockup/mempool.json
[ { "shell_header":
      { "branch": "BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU" },
    "protocol_data":
      { "contents":
          [ { "kind": "transaction",
              "source": "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx", "fee": "403",
              "counter": "1", "gas_limit": "1527", "storage_limit": "0",
              "amount": "1000000",
              "destination": "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" } ],
        "signature":
          "sigTXV77JT5t3xaAUnCXs4RhvwJscFaqpZvHp4Wm8tQoENKXyFz3hLyqbQkibPoo4JNeXiGHJRdeMTAK79ZJJMDTvxZGF75H" } },
  { "shell_header":
      { "branch": "BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU" },
    "protocol_data":
      { "contents":
          [ { "kind": "transaction",
              "source": "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN", "fee": "403",
              "counter": "1", "gas_limit": "1527", "storage_limit": "0",
              "amount": "2000000",
              "destination": "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" } ],
        "signature":
          "sigNDKvCy4JDjSSXNG3CBwwhkFw2S8DeVaUXamYbVddsyRwZhF7rf1RgCZUZBy8UdsYrWFUDaj9b8xH3w5Ryg3SoBfGmKJBR" } } ]

The mempool is just a JSON array listing the valid operations that are waiting to be baked in. We see our two pending transfers above, e.g. transfer 1 is first and will earn whoever bakes it into the chain 403 microtez (0.000403 ꜩ); we will return to this fee later.

We can check that our transfers have not yet been included in the chain (baked), so that the balances of bootstrap1 and bootstrap2 are unchanged. Both still have the initial value of 4000000 ꜩ:

$ mockup-client get balance for bootstrap1
4000000 ꜩ
$ mockup-client get balance for bootstrap2
4000000 ꜩ

For the balances to change, the transfers must be validated and baked in the chain.

$ mockup-client bake for bootstrap1 --minimal-timestamp
Nov  9 10:23:05.436 - alpha.baking.forge: found 2 valid operations (0 refused) for timestamp 1970-01-01T00:00:02.000-00:00 (fitness 01::0000000000000001)
Injected block BLzVCvEvPRsc

Note the --minimal-timestamp flag above. This will compute the baked block’s timestamp from its predecessor’s, instead of taking the current machine time.

In a local simulated environment, this also ensures the baking action will succeed, since the (computed) time between timestamps is guaranteed greater than the chain’s minimal time interval between blocks. Without the --minimal-timestamp flag, baking might fail because it is too close to the last baking.

The mempool is now empty since all operations were valid for the baking operation.7

The trashpool

The trashpool.json file which we will see below is a design feature unique to mockup mode. It does not appear in normal or sandboxed mode. First, some motivation:

The fee for transfer 1 above was a default fee which in a real system would have been paid to the baker baking the transaction into the real chain. But suppose our transfer is urgent and we want to offer an extra fee to encourage it to be baked quickly. mockup-mode allows us to offer an additional incentive to our (mock) bakers:

$ mockup-client transfer 1 from bootstrap1 to bootstrap2 --fee 1  
$ mockup-client transfer 2 from bootstrap2 to bootstrap3 --fee 0.5  

Thus we have asked mockup-client to carry out two transactions: transfer 1 with a fee of 1 ꜩ, and transfer 2 with a fee of 0.5ꜩ. We then execute a selective baking operation:

$ mockup-client bake for bootstrap1 --minimal-timestamp --minimal-fees 0.6
Nov  9 10:23:06.047 - alpha.baking.forge: found 1 valid operations (1 refused) for timestamp 1970-01-01T00:00:04.000-00:00 (fitness 01::0000000000000002)
Nov  9 10:23:06.182 - mockup.local_services: Appending 1 operation to trashpool
Injected block BLE4cu7Usm8J

transfer 1 and transfer 2 are both valid transactions with respect to the emitters’ balances, but our mock baker

  • accepts transfer 1 and
  • rejects transfer 2 because of the command line option --minimal-fees 0.6.

mempool.json is empty after the baking operation, and the rejected transaction 2 has been pushed into
a trashpool.json file, for debugging:

$ cat /tmp/mockup/mockup/mempool.json
[]
$ cat /tmp/mockup/mockup/trashpool.json
[ { "shell_header":
      { "branch": "BLzVCvEvPRscvX9jre4t9oLWcSMa5o5LfnhPtSGsiL45qUSLRcB" },
    "protocol_data":
      { "contents":
          [ { "kind": "transaction",
              "source": "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN",
              "fee": "500000", "counter": "2", "gas_limit": "1527", 
              "storage_limit": "0", "amount": "2000000",
              "destination": "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" } ],
        "signature":
          "sigpzoMG3ySUrk3JvVhjWgzK86NXbUdvD4Zad84GnC3e75ZxoAtMJAUe2tPa4UtFcr1cqmruruewYC4r9nWfKmVZcLSTaPZp" } } ]

The trashpool behaves like (and indeed is) a debug log: once an operation is recorded in the trashpool, this log entry stays there forever and cannot leave. This is because mockup mode has only one head block and one chain, and we do not store information about the past beyond the head block and the block preceding it. This is consistent with an idea of mockup mode as an environment for a single user to test state transformations on a single machine.

Differences from sandboxed mode

Mockup mode is not the only way to safely and locally test a Tezos blockchain: we can also run a Tezos blockchain sandboxed on a local machine. So in particular we can use sandboxed mode to create a blockchain consisting of a single Tezos node, and then interact with that using tezos-client.

This is a more heavyweight solution, because a sandboxed blockchain is still a blockchain, albeit one that is isolated (sandboxed) from the wider internet. A sandboxed blockchain is just as complex as an unsandboxed chain: it comes with chain history,8 chain branching, consensus, and so forth. These are all good things, but they are not needed for every testing scenario.

Sandboxed mode is also somewhat less friendly to debugging, e.g. it has no trashpool.

So, while it is valid to run a sandboxed blockchain and examine its behavior — and this has been done in practice — it requires some effort.

In mockup mode, in contrast,

  • there is no live node,
  • there is no chain branching (there is only ever one live block: the head), and
  • there is almost no history (we just store two blocks).

But,

  • Mockup mode is lightweight, fast, and convenient, and
  • there is no networking overhead, and no rounds of consensus with the one available node to decide which of the one available blocks will be added to the one available chain.
  • Mockup mode gives direct command-line access to some critical helper functions of a full Tezos implementation, allowing us to run and test those functions acting directly on a locally stored state.

Thus workflow is simple. The initial state, once created, is directly accessible and transformable — no need to open a terminal, run a node, log into clients, communicate with the node, let the node communicate (with itself) to reach consensus (with itself), and so forth.

Conclusions

We have presented a general overview of mockup mode in its three modes of operation:

  • Stateless mode gives us access to some basic but important functions.
  • Stateful mode gives us a state, but no baking. Every operation is immediately acted on by either being registered in the state, or rejected. There is only one live block.
  • Stateful asynchronous adds baking. There are a mempool and trashpool, and baking operations from the mempool either to act on the local state or to get dropped into the trashpool. There is still only one live block.

If you want to go beyond this then you can either:

  • set up a sandboxed Tezos network,
  • set up your own live Tezos network, or
  • connect to one that somebody else has already set up (e.g. the Tezos Mainnet).

Thus mockup mode fills in a complete menu of options for experimenting with Tezos.

We created this new version of tezos-client, with its mockup mode, to help our developers to quickly and efficiently develop and test smart contracts in a Tezos environment. They found it useful and it has improved our internal development cycle.9 We are happy to share this tool with the Tezos community, and we hope you will like it and find it useful too.

Mockup mode is being actively developed and will evolve best if it can benefit from your feedback. If you have a suggestion, please do not hesitate to create an issue on the tezos issue tracker.


  1. Technical note: The current version 7.x releases have a preliminary version, with a slightly different user experience. Mockup mode has existed for Tezos protocols starting with Carthage (numbers relates to shell updates, and names to protocol updates). Edo requires a shell containing environment V1. Delphi and Carthage are both usable on 7.x releases, and shells are backward-compatible. 

  2. This post covers the higher-level functionality. You can fetch a precise list of implemented functionalities with tezos-client --mode mockup rpc list

  3. Technical documentation describes this as: mockup mode defaults to an unspecified protocol

  4. A Tezos blockchain has a self-amendment mechanism allowing to modify the protocol, subject to community votes. These self-amendments are called protocol updates

  5. Note that valid transfers in stateful mode are immediate, without any networking or consensus mechanisms. In contrast, a sandboxed Tezos blockchain maintains an (emulated) network and, most expensively, it adheres to the full consensus and baking mechanisms of a proper blockchain. 

  6. In mockup mode, step 1 is irrelevant. This isn’t because there is no network — we might still simulate one locally. It’s because there’s no node. 

  7. Note the word ‘forge’ in the code above. Wait, we can explain: it’s a term of art! Forging or minting is standard terminology in Proof-of-Stake systems (like Tezos) for the operation of creating a block, and corresponds to ‘mining’ in Proof-of-Work systems (like Bitcoin). Thus, the etymology and meaning of a phrase like “forgers forge forged transactions” is … entirely respectable. 

  8. With the current Delphi protocol (announcement, changelog) it takes sixty blocks for a transaction to be removed from the mempool (this is governed by the max_operations_ttl parameter). 

  9. For instance, we used mockup mode to test the bugfix for comb pairs in the Delphi protocol.
    We also used mockup mode to develop the four smart contracts (ticket_builder_fungible.tz, ticket_builder_non_fungible.tz, ticket_wallet_fungible.tz and ticket_wallet_non_fungible.tz) which are examples of using the new tickets feature of Edo in a specific example implementation. Here, mockup mode tightened our developers’ development cycle in Michelson Emacs mode, which now uses mockup mode as a default engine to derive type information.
    Before commit 199a1e82 Emacs integration used sandboxed mode. This required defining a tezos-client alias for inside the Emacs mode, and launching a node, all before starting up Emacs. With mockup mode we just create a state and operate on it directly, for immediate feedback.