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
- Run a mockup client in stateless mode
- Run a mockup client with state
- Tune mockup parameters
- Running a mockup client with asynchronous state
- Conclusions
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:
- 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.
- 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:
- 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. - 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. - 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 inmockup-client
. - The switch
--protocol ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK
means that this mockup session will use protocolalpha
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).
- in
- 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:
-
First, we check that the sender
bootstrap1
has indeed paid the transfer amount, plus fees:$ mockup-client get balance for bootstrap1
3999899.999596 ꜩ
-
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:
- An operation is emitted across the network of nodes.
- It gets validated, aggregated with other operations, and included in (baked in to) a block by a baker.
- 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.
-
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 withCarthage
(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. ↩ -
This post covers the higher-level functionality. You can fetch a precise list of implemented functionalities with
tezos-client --mode mockup rpc list
. ↩ -
Technical documentation describes this as: mockup mode defaults to an unspecified protocol. ↩
-
A Tezos blockchain has a self-amendment mechanism allowing to modify the protocol, subject to community votes. These self-amendments are called protocol updates. ↩
-
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. ↩
-
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. ↩
-
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. ↩
-
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). ↩ -
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
andticket_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 atezos-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. ↩