How to Run a Local Development Environment in Tezos (Flextesa and Tzindex)?
Fleetinghow 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 ( ).
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
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.
Permalink
-
I use here the address to discuss with localhost from a docker container ↩︎
-
this can be run whenever you want, it will parse all the block when run ↩︎
-
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. ↩︎