Konubinix' opinionated web of thoughts

How to Run a Local Development Environment in Tezos (Flextesa and Tzindex)?

Fleeting

how to run flextesa and tzindex in a local env to ease developing on tezos?

I will focus on ithaca because jakarta it pretty new as of today ([2022-06-24 Fri]).

running a local tezos node

Flextesa is pretty easy to run, simply following their readme.

docker run --rm --name my-sandbox -p 18731:20000 -e block_time=1 oxheadalpha/flextesa ithacabox start
docker exec my-sandbox ithacabox info 2>&1
Usable accounts:

- alice
  * edpkvGfYw3LyB1UcCahKQk4rF2tvbMUk8GFiTuMjL75uGXrpvKXhjn
  * tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb
  * unencrypted:edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq
- bob
  * edpkurPsQ8eUApnLUJ9ZPDvu98E8VNj4KtJa1aZr16Cr5ow5VHKnz4
  * tz1aSkwEot3L2kmUvcoxzjMomb9mvBNuzFK6
  * unencrypted:edsk3RFfvaFaxbHx8BMtEW1rKQcPtDML3LXjNqMNLCzC3wLC1bWbAt

Root path (logs, chain data, etc.): /tmp/mini-box (inside container).

It’s pretty comfortable to always have the same accounts full of tezzies in the box.

Also, creating bootstrap addresses is a breeze with flextesa key-of-name.

Using clk tzc, I can simply use

clk tzc node connect http://localhost:20000

And be done with it.

Actually, is looks like I need to have a node running in archive mode and with unlimited metadata size for tzIndex to work appropriately.

By looking at the ithacabox script in the docker image, I can see it runs.

start () {
    flextesa mini-net \
             --root "$root_path" --size 1 "$@" \
             --set-history-mode N000:archive \
             --number-of-b 1 \
             --balance-of-bootstrap-accounts tez:100_000_000 \
             --time-b "$time_bb" \
             --add-bootstrap-account="$alice@2_000_000_000_000" \
             --add-bootstrap-account="$bob@2_000_000_000_000" \
             --no-daemons-for=alice \
             --no-daemons-for=bob \
             --until-level 200_000_000 \
             --protocol-kind "$default_protocol"
}

The node is already run in archive move.

running a local tzIndex

That’s easy.

git clone https://github.com/blockwatch-cc/tzindex
cd tzindex
go build ./cmp/tzindex
./tzindex run --rpcurl http://localhost:18731

Or, even more simply, using docker

docker run --rm -ti -p 8000:8000 blockwatch/tzindex tzindex run --rpcurl http://172.17.0.1:18731

1

trying it out

Now, let’s check out if it works.

Run the sandbox

clk tzc sandbox flextesa start

Run the indexer2

docker run --rm -ti -p 8000:8000 blockwatch/tzindex tzindex run --rpcurl http://172.17.0.1:18731

See the alice account to play with it.

clk tzc sandbox flextesa generate-bootstrap-account-command alice
{
    "address": "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb",
    "alias": "alice",
    "public_key": "edpkvGfYw3LyB1UcCahKQk4rF2tvbMUk8GFiTuMjL75uGXrpvKXhjn",
    "secret_key": "unencrypted:edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq"
}

Add an address to play with it.

clk tzc sandbox flextesa generate-bootstrap-account-command konubinix
{
    "address": "tz1UJHuqwkEuHbomaKvzs2XT6evFesPHERKK",
    "alias": "konubinix",
    "public_key": "edpkuBLoWpFZh2dTUbgoDUDpPMPFczzYfCHr2jGeLURkBmTUNdgUsU",
    "secret_key": "unencrypted:edsk3VDZzC1Bp46Fj1oaJFUkVyVT37JtaxTHBq2Hzez4Lkpwmxba2J"
}

Then, use pytezos to play interactively with the tezos node.3

from pytezos import pytezos

alice = pytezos.using(shell="http://172.17.0.1:18731", key="edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq")
konubinix = pytezos.using(shell="http://172.17.0.1:18731", key="edsk3VDZzC1Bp46Fj1oaJFUkVyVT37JtaxTHBq2Hzez4Lkpwmxba2J")

I will use ttl=120 to mitigate a bug in pytezos.

Let’s try to give some money to konubinix.

alice.transaction(konubinix.key.public_key_hash(), 2000).send(min_confirmations=1, ttl=120)
<pytezos.operation.group.OperationGroup object at 0x7fd645cae340>

Properties
.key		tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb
.shell		['http://172.17.0.1:18731']
.block_id	head

Payload
{'branch': 'BMNSJyqFn7W3zUprw5UmfvGzmAA8B8aqmNLUPM4mHFozfZZiwXn',
 'contents': [{'amount': '2000',
               'counter': '1',
               'destination': 'tz1UJHuqwkEuHbomaKvzs2XT6evFesPHERKK',
               'fee': '416',
               'gas_limit': '1551',
               'kind': 'transaction',
               'source': 'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb',
               'storage_limit': '357'}],
 'protocol': 'PtJakart2xVj7pYXJBXrqHgd82rdkLey5ZeeGwDgPp9rhQUbSqY',
 'signature': 'sigvMvRFvTEQYfn3gPQwRdinSbYmF1YFC6GouSF5jgmbruth6sBeb99AWg1S1q4FTwwGpzKjuemx9AXEYAGfuMHRi5E2rk3t'}

Helpers
.activate_account()
.autofill()
.ballot()
.binary_payload()
.delegation()
.double_baking_evidence()
.double_endorsement_evidence()
.endorsement()
.endorsement_with_slot()
.failing_noop()
.fill()
.forge()
.hash()
.inject()
.json_payload()
.message()
.operation()
.origination()
.preapply()
.proposals()
.register_global_constant()
.result()
.reveal()
.run()
.run_operation()
.seed_nonce_revelation()
.send()
.send_async()
.sign()
.transaction()

Then, let’s reveal konubinix’s account

konubinix.reveal().send(min_confirmations=1, ttl=120)
<pytezos.operation.group.OperationGroup object at 0x7fd648cc9fd0>

Properties
.key		tz1UJHuqwkEuHbomaKvzs2XT6evFesPHERKK
.shell		['http://172.17.0.1:18731']
.block_id	head

Payload
{'branch': 'BM2Rk3cCoLteVHXnomn5TnoZSxdFf641nYopX17zEdKrRy3Yn8o',
 'contents': [{'counter': '2',
               'fee': '368',
               'gas_limit': '1000',
               'kind': 'reveal',
               'public_key': 'edpkuBLoWpFZh2dTUbgoDUDpPMPFczzYfCHr2jGeLURkBmTUNdgUsU',
               'source': 'tz1UJHuqwkEuHbomaKvzs2XT6evFesPHERKK',
               'storage_limit': '0'}],
 'protocol': 'PtJakart2xVj7pYXJBXrqHgd82rdkLey5ZeeGwDgPp9rhQUbSqY',
 'signature': 'sigeFFxANP1yCqkzhjHRJLZhvM9rm9oK6FwcNoSC3NQLz8hQXL5RVTGiNpYgpP1UFdtDDB183YKRTSahP3s6V6dSZ3rvepya'}

Helpers
.activate_account()
.autofill()
.ballot()
.binary_payload()
.delegation()
.double_baking_evidence()
.double_endorsement_evidence()
.endorsement()
.endorsement_with_slot()
.failing_noop()
.fill()
.forge()
.hash()
.inject()
.json_payload()
.message()
.operation()
.origination()
.preapply()
.proposals()
.register_global_constant()
.result()
.reveal()
.run()
.run_operation()
.seed_nonce_revelation()
.send()
.send_async()
.sign()
.transaction()

Now, let’s try to see this in querying tzindex.

import requests
operations = requests.get("http://localhost:8000/tables/op").json()
operations[0]
delegation oneDGhZacw99EEFaYDTtWfz5QEhUW3PPVFsHa7GShnLPuDn7gSd 1 0 1660316660000 0 0 applied 1 0 0 1 0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 1 0 0 1 hline hline hline hline 65536 tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb hline hline tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb BMcBzoC6XUFro4EXDocCfJ1VJwNELoUjXgYPGnEo1Kh8PWSRs7e hline hline

The documentation describes a row_id. I don’t see it in the returned results. Still, putting this first data aside, I can make sense of the returned data.


def decorate_operations(operations):
    return [['type',
             'hash',
             'height',
             'cycle',
             'time',
             'op_n',
             'op_p',
             'status',
             'is_success',
             'is_contract',
             'is_event',
             'is_internal',
             'counter',
             'gas_limit',
             'gas_used',
             'storage_limit',
             'storage_paid',
             'volume',
             'fee',
             'reward',
             'deposit',
             'burned',
             'sender_id',
             'receiver_id',
             'manager_id',
             'baker_id',
             'data',
             'parameters',
             'storage_hash',
             'big_map_diff',
             'errors',
             'days_destroyed',
             'sender',
             'receiver',
             'creator',
             'baker',
             'block',
             'entrypoint',
             ]] + operations

decorate_operations(operations[:1])
type hash height cycle time op_n op_p status is_success is_contract is_event is_internal counter gas_limit gas_used storage_limit storage_paid volume fee reward deposit burned sender_id receiver_id manager_id baker_id data parameters storage_hash big_map_diff errors days_destroyed sender receiver creator baker block entrypoint
delegation oneDGhZacw99EEFaYDTtWfz5QEhUW3PPVFsHa7GShnLPuDn7gSd 1 0 1660316660000 0 0 applied 1 0 0 1 0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 1 0 0 1 None None None None 65536 tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb None None tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb BMcBzoC6XUFro4EXDocCfJ1VJwNELoUjXgYPGnEo1Kh8PWSRs7e None None

Now, let’s try to request more precisely.

For instance, let’s see the operations initiated by alice.

decorate_operations(requests.get(f"http://localhost:8000/tables/op?sender.eq={alice.key.public_key_hash()}").json())
type hash height cycle time op_n op_p status is_success is_contract is_event is_internal counter gas_limit gas_used storage_limit storage_paid volume fee reward deposit burned sender_id receiver_id manager_id baker_id data parameters storage_hash big_map_diff errors days_destroyed sender receiver creator baker block entrypoint
delegation oneDGhZacw99EEFaYDTtWfz5QEhUW3PPVFsHa7GShnLPuDn7gSd 1 0 1660316660000 0 0 applied 1 0 0 1 0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 1 0 0 1 None None None None 65536 tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb None None tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb BMcBzoC6XUFro4EXDocCfJ1VJwNELoUjXgYPGnEo1Kh8PWSRs7e None None
activation oneDGhZacw99EEFaYDTtWfz5QEhUW3PPVFsHa7GShnLPuDn7gSd 1 0 1660316660000 1 1 applied 1 0 0 1 0 1 0 0 0 0 2000000.0 0.0 0.0 0.0 0.0 1 0 0 0 None None None None 65537 tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb None None None BMcBzoC6XUFro4EXDocCfJ1VJwNELoUjXgYPGnEo1Kh8PWSRs7e None None
transaction ooyokRus7dMeMUBeaPxVXqxC5vxBNXchxmqr8fR2dnMAHcix2u8 97 12 1660316972000 3 0 applied 1 0 0 0 0 1 1551 1450 357 0 0.002 0.000416 0.0 0.0 0.06425 1 8 0 0 None None None None 6356995 tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb tz1UJHuqwkEuHbomaKvzs2XT6evFesPHERKK None None BMTyuSAWxZGSzLYNG5798daEuRTgTsSiGWx4xAYRVj4adHdc9jP None None
transaction opC1XApjT9M5kfrpcGsXSkRxevLtfH7oacxVFZ2yV18E4WV216N 357 44 1660317752000 3 0 applied 1 0 0 0 0 2 1551 1450 357 0 0.002 0.000416 0.0 0.0 0.06425 1 9 0 0 None None None None 23396355 tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb tz1NT871u7xQx8kDHXc7NGJ2r5SjtiRZpc7i None None BLxyTmZ97E4PCVkzKM7VySk7NUDrEXhFkPrvNnLkf9V1TozVNVh None None

And in particular those towards konubinix.

decorate_operations(requests.get(f"http://localhost:8000/tables/op?sender.eq={alice.key.public_key_hash()}&receiver={konubinix.key.public_key_hash()}").json())
type hash height cycle time op_n op_p status is_success is_contract is_event is_internal counter gas_limit gas_used storage_limit storage_paid volume fee reward deposit burned sender_id receiver_id manager_id baker_id data parameters storage_hash big_map_diff errors days_destroyed sender receiver creator baker block entrypoint
transaction ooyokRus7dMeMUBeaPxVXqxC5vxBNXchxmqr8fR2dnMAHcix2u8 97 12 1660316972000 3 0 applied 1 0 0 0 0 1 1551 1450 357 0 0.002 0.000416 0.0 0.0 0.06425 1 8 0 0 None None None None 6356995 tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb tz1UJHuqwkEuHbomaKvzs2XT6evFesPHERKK None None BMTyuSAWxZGSzLYNG5798daEuRTgTsSiGWx4xAYRVj4adHdc9jP None None

I don’t see where the amount is supposed to be. But let’s move forward.

The transactions initiated by konubinix.

decorate_operations(requests.get(f"http://localhost:8000/tables/op?sender.eq={konubinix.key.public_key_hash()}").json())
type hash height cycle time op_n op_p status is_success is_contract is_event is_internal counter gas_limit gas_used storage_limit storage_paid volume fee reward deposit burned sender_id receiver_id manager_id baker_id data parameters storage_hash big_map_diff errors days_destroyed sender receiver creator baker block entrypoint
reveal opLJamDitCX8NYUdERoPyS7ChKJRKTYFY5TTWJAFFiai5FogB2Q 108 13 1660317005000 3 0 applied 1 0 0 0 0 2 1000 1000 0 0 0.0 0.000368 0.0 0.0 0.0 8 0 0 0 edpkuBLoWpFZh2dTUbgoDUDpPMPFczzYfCHr2jGeLURkBmTUNdgUsU None None None 7077891 tz1UJHuqwkEuHbomaKvzs2XT6evFesPHERKK None None None BLnHuRo61UAAhZyPN2NTNFy1cuA7ES3FgBnjTHAhxMr1dt7NZEN None None

We can see the reveal.

Now, let’s take a look at the balances.

requests.get(f"http://localhost:8000/explorer/account/{alice.key.public_key_hash()}").json()['spendable_balance']
2199999.866668
requests.get(f"http://localhost:8000/explorer/account/{konubinix.key.public_key_hash()}").json()['spendable_balance']
0.001632

Yet again, I’m not familiar enough with tezos to understand this, but it looks like everything is working appropriately.


  1. I use here the address to discuss with localhost from a docker container ↩︎

  2. this can be run whenever you want, it will parse all the block when run ↩︎

  3. I would have loved querying the boltdb database, but it appears that there are no good boltdb command line tool out there. I was too lazy to build my request directly in golang. ↩︎