Skip to content

Tutorial: Querying for Tezos alphanet data using the ConseilJS v2 API

anonymoussprocket edited this page Apr 7, 2019 · 16 revisions

In this walkthrough, we will ConseilJS to query for some Tezos alphanet blockchain data.

You will need to either run your own Conseil instance or ask Cryptonomic for access to their dev instance going against the Tezos alphanet.

URL and apiKey variables

When querying Conseil through ConseilJS, we will provide our functions with url and apiKey parameters which should look something like this:

"url": "https://myconseilnode:8080.com",
"apiKey": "myapikey"

Metadata Queries

Let us first use the metadata queries to ensure we can get the data we want. To do so, we should first import the required namespace from ConseilJS to our project.

import { ConseilMetadataClient } from 'conseiljs'

Querying for platforms

Let’s start by confirming Tezos data is indeed available from the current Conseil instance:

import { ConseilMetadataClient } from 'conseiljs'
const { getPlatforms } = ConseilMetadataClient;
    
const url = "https://myconseilnode.com";
const apiKey = "myapikey"
    
async function foo(){
  const platforms = await getPlatforms(url, apiKey);
  return platforms;
}

The returned platforms variable will look like:

[
    {
        "name": "tezos",
        "displayName": "Tezos"
    }
]

Great, Tezos is indeed supported!

Querying for networks

Now let’s enhance our code to confirm which Tezos networks this Conseil instance offers data for:

import { ConseilMetadataClient } from 'conseiljs'
const { getPlatforms, getNetworks } = ConseilMetadataClient;
    
const url = "https://myconseilnode.com"
const apiKey = "myapikey"
    
async function foo(){
  const platforms = await getPlatforms(url, apiKey);
  const networks = await getNetworks(url, apiKey, platforms[0].name)
  return networks;
}

The returned networks variable will look like:

[
    {
        "name": "alphanet",
        "displayName": "Alphanet",
        "platform": "tezos",
        "network": "alphanet"
    }
]

Alphanet is also supported by this instance! Some other Conseil instances might support multiple networks.

Querying for available entities

Now let’s enhance our code to understand what kinds of data we can query from our Conseil instance.

import { ConseilMetadataClient } from 'conseiljs'
const { getPlatforms, 
        getNetworks, 
        getEntities } = ConseilMetadataClient;
            
const url = "https://myconseilnode.com"
const apiKey = "myapikey"
    
async function foo(){
  const platforms = await getPlatforms(url, apiKey);
  const networks = await getNetworks(url, apiKey, platforms.name)
  const entities = await getEntities(url, apiKey, networks[0].platform, networks[0].network)
  return entities;
}

The returned entities variable will look like:

[
    {
        "name": "blocks",
        "displayName": "Blocks",
        "count": 137532,
        "network": "alphanet"
    },
    {
        "name": "accounts",
        "displayName": "Accounts",
        "count": 5501,
        "network": "alphanet"
    },
    {
        "name": "operation_groups",
        "displayName": "Operation groups",
        "count": 966144,
        "network": "alphanet"
    },
    {
        "name": "operations",
        "displayName": "Operations",
        "count": 978238,
        "network": "alphanet"
    },
    {
        "name": "fees",
        "displayName": "Fees",
        "count": 259,
        "network": "alphanet"
    }
]

So this means we can query for information about blocks, accounts, operation groups, operations, and fees.

Getting attributes for entities

The entities above effectively correspond to tables in the Conseil database. Let's now investigate which columns from these tables are available.

import { ConseilMetadataClient } from 'conseiljs'
const { getPlatforms, 
        getNetworks, 
        getEntities,
        getAttributes } = ConseilMetadataClient;
            
const url = "https://myconseilnode.com"
const apiKey = "myapikey"
    
async function foo(){
  const platforms = await getPlatforms(url, apiKey);
  const networks = await getNetworks(url, apiKey, platforms[0].name)
  const entities = await getEntities(url, apiKey, platforms[0].name, networks[0].network)
  const attributesForOperations = await getAttributes(url, apiKey, platforms[0].name, networks[0].network, entities[3].name)
  return attributesForOperations;
}

The returned attributesForOperations variable will look like:

[
    {
        "name": "operation_id",
        "displayName": "Operation id",
        "dataType": "Int",
        "cardinality": 978326,
        "keyType": "UniqueKey",
        "entity": "operations"
    },
    {
        "name": "operation_group_hash",
        "displayName": "Operation group hash",
        "dataType": "String",
        "cardinality": 966232,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "kind",
        "displayName": "Kind",
        "dataType": "String",
        "cardinality": 8,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "level",
        "displayName": "Level",
        "dataType": "Int",
        "cardinality": 137398,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "delegate",
        "displayName": "Delegate",
        "dataType": "String",
        "cardinality": 66,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "slots",
        "displayName": "Slots",
        "dataType": "String",
        "cardinality": 355113,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "nonce",
        "displayName": "Nonce",
        "dataType": "String",
        "cardinality": 4268,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "pkh",
        "displayName": "Pkh",
        "dataType": "String",
        "cardinality": 674,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "secret",
        "displayName": "Secret",
        "dataType": "String",
        "cardinality": 674,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "source",
        "displayName": "Source",
        "dataType": "String",
        "cardinality": 1166,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "fee",
        "displayName": "Fee",
        "dataType": "Decimal",
        "cardinality": 430,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "counter",
        "displayName": "Counter",
        "dataType": "Decimal",
        "cardinality": 10921,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "gas_limit",
        "displayName": "Gas limit",
        "dataType": "Decimal",
        "cardinality": 384,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "storage_limit",
        "displayName": "Storage limit",
        "dataType": "Decimal",
        "cardinality": 103,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "public_key",
        "displayName": "Public key",
        "dataType": "String",
        "cardinality": 1097,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "amount",
        "displayName": "Amount",
        "dataType": "Decimal",
        "cardinality": 6574,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "destination",
        "displayName": "Destination",
        "dataType": "String",
        "cardinality": 4297,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "parameters",
        "displayName": "Parameters",
        "dataType": "String",
        "cardinality": 518,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "manager_pubkey",
        "displayName": "Manager pubkey",
        "dataType": "String",
        "cardinality": 202,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "balance",
        "displayName": "Balance",
        "dataType": "Decimal",
        "cardinality": 757,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "spendable",
        "displayName": "Spendable",
        "dataType": "Boolean",
        "cardinality": 1,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "delegatable",
        "displayName": "Delegatable",
        "dataType": "Boolean",
        "cardinality": 1,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "script",
        "displayName": "Script",
        "dataType": "String",
        "cardinality": 154,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "status",
        "displayName": "Status",
        "dataType": "String",
        "cardinality": 4,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "block_hash",
        "displayName": "Block hash",
        "dataType": "String",
        "cardinality": 137391,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "block_level",
        "displayName": "Block level",
        "dataType": "Int",
        "cardinality": 137392,
        "keyType": "NonKey",
        "entity": "operations"
    },
    {
        "name": "timestamp",
        "displayName": "Timestamp",
        "dataType": "DateTime",
        "cardinality": 137392,
        "keyType": "NonKey",
        "entity": "operations"
    }
]

Wow, that's a lot of attributes to explore! Observe that the returned data lists for each attribute things like its cardinality, i.e. how many unique values the attribute has, and data type.

Querying for attributes for blocks and accounts is left as an exercise for the reader!

Getting values for attributes

Let's now figure out how many different kinds of operations Conseil has in its database.

import { ConseilMetadataClient } from 'conseiljs'
const { getPlatforms, 
        getNetworks, 
        getEntities,
        getAttributes,
        getAttributeValues } = ConseilMetadataClient;
            
const url = "https://myconseilnode.com"
const apiKey = "myapikey"
    
async function foo(){
  const platforms = await getPlatforms(url, apiKey);
  const networks = await getNetworks(url, apiKey, platforms[0].name);
  const entities = await getEntities(url, apiKey, platforms[0].name, networks[0].network);
  const attributesForOperations = await getAttributes(url, apiKey, platforms[0].name, networks[0].network, entities[3].name);
  const valuesForOperationKind = await getAttributeValues(url, apiKey, platforms[0].name, networks[0].network, entities[3].name, attributesForOperations[3].name);
  return valuesForOperationKind;
}

The returned valuesForOperationKind variable will look like:

[
    "seed_nonce_revelation",
    "delegation",
    "transaction",
    "activate_account",
    "origination",
    "reveal",
    "double_baking_evidence",
    "endorsement"
]

Of course, Tezos supports more operation kinds but these are just the ones Conseil came across in alphanet at the point at which it was queried.

Now that we know how to use metadata queries to understand the available data, we can move on to querying for data.

Data Queries

Now that we are armed with all the metadata we need, we can actually start pulling the data we want.

Fetching delegations in a block range

Let's try to find all delegations between blocks 10,000 and 12,000 and report both the delegators and the bakers they are delegating to. To perform this, we will be using executeEntityQuery function of ConseilDataClient namespace, and various functions from ConseilQueryBuilder namespace.

executeEntityQuery takes 5 parameters;

  1. serverInfo: a ConseilServerInfo object, which has 2 fields; url and apiKey
  2. platform: The platform we will be querying; in our case it will be tezos
  3. network: Which network of the above mentioned platform to query; in our case it will be alphanet
  4. entity: Which entity we will be querying; in our case it will be operations
  5. query: The query object which we will be building using the ConseilQueryBuilder functions.

In this example, we will be using all 5 ConseilQueryBuilder functions. addFields, queryBuilder, addPredicate, addOrdering, and *setLimit To better understand how these functions work and what sorts of parameters they receive, please refer to our API Documentation.

import { ConseilDataClient, ConseilQueryBuilder } from 'conseiljs'
const { executeEntityQuery } = ConseilDataClient;
const { blankQuery, addFields, addPredicate, addOrdering, setLimit } = ConseilQueryBuilder;

const conseilServer = {
  url: "https://myconseilnode.com/tezos/alphanet",
  apiKey: "myapikey"
};
const platform = "tezos";
const network = "alphanet";
const entity = "operations";

let query = blankQuery();
query = addFields(query, ['operation_group_hash','block_level','kind','source','delegate'])
query = addPredicate(query, 'kind', 'eq', ['delegation'], false);
query = addPredicate(query, 'block_level', 'between', [10000,12000],false);
query = addOrdering(query, 'balance', 'desc');
query = setLimit(query, 1000);

async function foo(){
  const queryResults = await executeEntityQuery(conseilServer, platform, network, entity, query);
  return queryResults;
}

The queryResults variable will return:

[
    {
        "source": "tz1SsvTqeMmR9FW1D81dw9AaZPPwxSKFGuja",
        "block_level": 10232,
        "operation_group_hash": "onjnC72xRyE1Z2mydkGPRJkmiawFi9Y78JVKQQo4chd7oJLcFrz",
        "delegate": "tz1SsvTqeMmR9FW1D81dw9AaZPPwxSKFGuja",
        "kind": "delegation"
    },
    {
        "source": "tz1db53osfzRqqgQeLtBt4kcFcQoXJwPJJ5G",
        "block_level": 11143,
        "operation_group_hash": "op4GYeewLZt9kvv3PYDWJph9cAZTVirrp6uJXVbxBg7JWnULg34",
        "delegate": "tz1db53osfzRqqgQeLtBt4kcFcQoXJwPJJ5G",
        "kind": "delegation"
    },
    {
        "source": "KT1Bq7LEmYJyjghJAMMxt3V9FqXy9pfXAqrq",
        "block_level": 11826,
        "operation_group_hash": "opPs7KAVZHdieascPba2KwK5EkmHvX7ZyjGkrVWYepFLUQRg4qV",
        "delegate": "tz1LdZ6S8ScNMgaCLqrekDvbBWhLqtUebk23",
        "kind": "delegation"
    },
    {
        "source": "tz1Q9oZm21A3jhHjNUtrnVYEy8PbUeBf2wUV",
        "block_level": 11029,
        "operation_group_hash": "oodzAkkQyYeUHN9SpLf4dnGwcehxsY9UKGTXVoekc4ogX5R9a9Q",
        "delegate": "tz1Q9oZm21A3jhHjNUtrnVYEy8PbUeBf2wUV",
        "kind": "delegation"
    },
    {
        "source": "tz1bxe2zpe15B2TKajfT67Zp5oey6ptb5bsv",
        "block_level": 11236,
        "operation_group_hash": "onrpv2ana8A3X5T97hi2HGESd4frNfpuHuXL2mNAxwuhwZP25jH",
        "delegate": "tz1bxe2zpe15B2TKajfT67Zp5oey6ptb5bsv",
        "kind": "delegation"
    }
]

In the above query we

  • asked for five specific columns
  • applied two conditions
  • set a specific sort order
  • limited the returned results to 1,000 records.

As it turns out, there were five delegation operations between blocks 10,000 and 12,000.

Conclusion

Thanks for reading this tutorial so far. If you encounter any issues, feel free to join our Riot group for developers and get help. As people read this tutorial and provide feedback, we will add more example queries so make sure to come back here to learn more. Happy querying!