Digging into Tezos with ConseilPy

ConseilPy is a Python library providing Sqlalchemy-like query syntax for Conseil Tezos blockchain indexer. You can start using ConseilPy right now

Michael
Michael   Follow

ConseilPy is a Python library providing Sqlalchemy-like query syntax for Conseil blockchain indexer. You can start using ConseilPy right now: the package can be installed via PyPi, documentation, and examples are available on Github. We want to thank Tezos Foundation for supporting us and Cryptonomic team for their cooperation and quick responses.

We are big fans of Jupyter notebook, and we also like when things are working out of the box. ConseilPy lets you start playing with Tezos alphanet in a few clicks, and in this article, we will take several interesting usage examples.

# Delegates registrations and roll price

There is not much useful information that can be extracted from the alphanet, but we start from the assumption that before bakers do something in the Mainnet they pre-try to do it in the alphanet.

Let’s check if there is a connection between the price of one roll and the number of registered delegates. In order to register as a baker, one has to make a delegation operation to himself. It’s not possible to compare fields in Conseil right now, but we can filter operations by source (note that this won’t work after Emmy update).

from conseil import conseil

Operation = conseil.tezos.alphanet.operations
Operation.query(Operation.timestamp) \
    .filter(Operation.kind == Operation.kind.delegation,
            Operation.source.startswith('tz')) \
    .order_by(Operation.timestamp) \
    .all()

Applying some pandas magic:

from conseil import conseil
import pandas as pd
import cufflinks as cf
import numpy as np
import requests
import dateutil
cf.set_config_file(offline=True, world_readable=True, theme='pearl')
Operation = conseil.tezos.alphanet.operations
query = Operation.query(Operation.timestamp) \
    .filter(Operation.kind == Operation.kind.delegation,
            Operation.source.startswith('tz')) \
    .order_by(Operation.timestamp)
df = pd.DataFrame(query.all())
df.index = df['timestamp'].astype('M8[ms]')
df['total_registrations'] = 1
df['total_registrations'] = df['total_registrations'].cumsum()
market_data = requests.get('https://graphs2.coinmarketcap.com/currencies/tezos/').json()
md = pd.DataFrame(market_data['price_btc'], columns=['ts', 'price_btc'])
md.index = md['ts'].astype('M8[ms]')
athens_ts = int(dateutil.parser.parse('2019-05-30T00:58:55Z').timestamp() * 1000)
athens_ts
result
1559177935000
md['roll_price_btc'] = np.where(md['ts'] < athens_ts, 10000, 8000) * md['price_btc']
df = pd.merge_asof(df[['total_registrations']], md[['roll_price_btc']], left_index=True, right_index=True)
df.iplot(secondary_y='roll_price_btc')

And finally, we get the chart. One can note the growth of interest after the moments of strong price drops. Also, it was a great time to open a bakery at the end of June 😃

# Smart contract insights

Let’s see what we can find out about smart contracts. It should be noted that some of the developers prefer to debug smart contracts on the local node because it’s much more convenient. Therefore, our results will not be quite correct. But it’s ok for demo purpose.

# Hello from developers

It’s always nice to come across some joke left by the programmer in the code. We’ve already mentioned ASCII-art sample found accidentally but what can we extract using ConseilPy? Luckily, we can utilize a full-text search on storages, scripts, and parameters. For instance, we can look for URLs and emails:

from conseil import conseil

Account = conseil.tezos.alphanet.accounts
Account.storage.query() \
    .filter(Account.storage.like('.com')) \
    .all()

Or run into a man crying for help:

I’m afraid he never got help

Analyzing scripts you can find a lot of interesting things too, for example, we were looking for internal originations and delegations to debug better-call.dev. Conseil does not support internal operations at the moment, so we used the following query:

from conseil import conseil

Account = conseil.tezos.alphanet.accounts
Account.query(Account.account_id) \
    .filter(Account.script.like('SET_DELEGATE')) \
    .group_by(Account.script) \
    .vector()

# Source languages

Using a fairly simple heuristics, in some cases, we can determine which high-level language was used to write the contract. For example, Liquidity compiler can be detected if field or variable annotations of special kind occur, or SmartPy can be recognized by the error message template.

Let’s see how the number of deployed contracts written in Liquidity and other languages was changing. Start with non-liquidity originations:Hello from developers

from conseil import conseil

Operation = conseil.tezos.alphanet.operations
Operation.query(Operation.timestamp.max()) \
    .filter(Operation.kind == Operation.kind.origination,
            Operation.script.notlike('_slash_'),
            Operation.script.notlike('_prim_'),
            Operation.script.notlike('_sharp_'),
            Operation.script.notlike('_Liq_')) \
    .group_by(Operation.script) \
    .order_by(Operation.timestamp.max()) \
    .vector()

You can notice that we are grouping originations by script, this is a simple approach for removing codebase duplicates. A better approach would be to use a fuzzy comparison, but a simple one would do for our research.

from conseil import conseil
import pandas as pd
import cufflinks as cf
import numpy as np
import requests
import dateutil
cf.set_config_file(offline=True, world_readable=True, theme='pearl')
Operation = conseil.tezos.alphanet.operations
non_liq_ts = Operation.query(Operation.timestamp.max().label('ts')) \
    .filter(Operation.kind == Operation.kind.origination,
            Operation.script.notlike('_slash_'),
            Operation.script.notlike('_prim_'),
            Operation.script.notlike('_sharp_'),
            Operation.script.notlike('_Liq_')) \
    .group_by(Operation.script) \
    .order_by(Operation.timestamp.max()) \
    .all()
all_ts = Operation.query(Operation.timestamp.max().label('ts')) \
    .filter(Operation.kind == Operation.kind.origination) \
    .group_by(Operation.script) \
    .order_by(Operation.timestamp.max()) \
    .all()
df_oth = pd.DataFrame(non_liq_ts)
df_oth.index = df_oth['ts'].astype('M8[ms]')
df_oth['non_liquidity'] = 1

df_all = pd.DataFrame(all_ts)
df_all.index = df_all['ts'].astype('M8[ms]')
df_all['all'] = 1
df = pd.merge(df_oth, df_all, left_index=True, right_index=True, how='outer').fillna(0)
df['liquidity'] = df['all'] - df['non_liquidity']
df_liq = df[df['liquidity'] > 0][['liquidity']].copy()
df = pd.merge_asof(df_oth.cumsum(), df_liq.cumsum(), left_index=True, right_index=True)
df[['liquidity', 'non_liquidity']].iplot(title='Cumulative number of originations')

Moments of fast growth are most likely related to the deployment of several modifications of one contract (debugging). Generally, the result is expectedly biased, but some conclusions can be drawn.

# Summary

With ConseilPy you can start exploring Tezos with a minimum effort. SQLAlchemy-like syntax and inline metadata docstrings allow writing queries very fast. Next time we will show how to use ConseilPy and PyTezos together to make even cooler things.

Follow us on Twitter and Github to stay updated.
Cheers!