Skip to content

Contract Indexing via Cirrus

The STRATO Cirrus API stores contract state data in a read-only SQL table. It allows multiple contract instances of the same name to be accessible in the same place. This allows you to think of a contract more as a definition of a SQL table or data model, where every state variable is a corresponding column in the table. When a new contract is created, that represents a new row in the contract’s table, and updates to the rows are controlled by the smart contract’s embedded logic, all verified on the blockchain. So when an app creates 5 new assets, 5 new contracts are created, and 5 new rows are added to the corresponding SQL contract table (referred to as the contract’s “Cirrus table”). When one of these contracts' state is updated, the corresponding contract’s row is also updated.

Cirrus also stores historical records of every data update on contracts, so that each state that the contract has had over time is preserved. These are typically called history tables or audit logs. This table is named “history@<ContractTableName>”.

Events emitted from a contract are stored in separate event tables, allowing for lightweight data recording without the need to create new contracts. This table is named “<ContractTableName>.<EventName>”.

All of these tables use the contract's name as the base for the table name. Then depending on the identity of the contract creator, and type of table, further qualifications are added to the names in order to distinguish between them.

Table Naming

Tables are prefixed by the organization name of the user instantiating the contract/code collection. This allows users from two different organizations to create contracts of the same name and have that data stored in separate tables. So a “Car” contract created by a user in “Acme” org would have the following Cirrus table name: “Acme-Car”. If a contract is instantiated from a code pointer, it will be prefixed by organization of the original code collection creator (See Code Collections.)

Contracts created by other contracts/instantiated by code pointers will have the creating contract’s name prefixed as well. So a “Widget” contract created by a “Factory” contract would have the following Cirrus table name: “Factory-Widget”. Similarly if a code collection is uploaded with a "Dapp" and an "Asset" contract in it, and when uploaded, the "Dapp" contract is instantiated, and then an Asset contract is created in any way, the Cirrus table name would be: "Dapp-Asset".

These naming rules apply to all tables created for a contract (primary, history, events), and all work with each other. So a contract created by another contract, by a user in the "Acme" organization, would have the name of "Acme-Factory-Widget".

These rules create the full name of the contract's table name. History and event tables have their respective additions on top of this table name - history@Acme-Factory-Widget and Acme-Factory-Widget.MyEvent respectively.

See the STRATO developer documentation on Cirrus for more information on using it query your application’s data.

Private Shard Indexing

Contracts that are on Private Shards are still indexed in Cirrus, however only on the nodes of the Shard’s members. This allows the STRATO Mercata network to manage assets of the same type (same contract type) across multiple shards (Asset Shards) while storing them in the same Cirrus table. However, the Asset instance (table row) will only be present on the nodes that have access to the asset.

When members gain access to a shard, they will sync the entire history of the contracts on that shard, and continue receiving new transactions on that shard. A common misconception is that nodes will only sync private shard data at the point in time from which they received it - this is not true. Nodes receive the entire data of a shard from the shard's inception. As an example, if a private asset shard has some data field set to 5, and then it is set to 10, and then a new member receives access to the asset shard, that new member will be able to see the change of the initial value of 5 set to 10.

When a member loses access to a shard, they will cease syncing transactions after the transaction that removed them is committed. The data for that shard will stay on the node up until the point of that last transaction. Therefore the node retains all the data kept on that shard even after it has been removed. Data is never deleted from a node*.

If a Node is initially a member of a shard, removed, and then re-added at a later point in time, they will receive all the transactions on that shard for the interim period when they were not a member - the data is not hidden from them since the node needs all the transactions in the shard’s history to get to its current state.

Info

*If there is a private shard that only has a single node as a member (either the organization only has one node, or it was never shared with other organizations), and the node is reset, wiped, or otherwise taken out of commission, the private shard data is lost. However private shard data can never be manually deleted from the network. For this reason, we encourage all application developers to ensure their shards are shared with multiple parties or nodes within the organization.

History Tables

History tables create a historical record of every data update that happens in each transaction/for each contract. Any time a transaction causes an update to a contract’s state, a new row is created in the contract’s history table, with the values of the new state. The row with the previous state of the contract is never modified. This provides an easy way to view the state of contract on the blockchain over time. Unlike a contract’s primary Cirrus table, there are multiple rows for every contract instance (unique contract address), since on the history table, a contract instance must have its data represented in a historical view.

As an example, let’s show the resulting state of a Car’s history table when there are 2 separate Car contracts (addresses deadbeef and b0bacafe) and one car has 2 updates, and another has 3 updates:

address timestamp transaction_sender data
deadbeef 01:01:01 12345 5
b0bacafe 02:02:02 12345 500
deadbeef 03:03:03 abcdef 10
b0bacafe 04:04:04 12345 250
b0bacafe 05:05:05 abcdef 350

Event Tables

Event tables contain the Solidity event's arguments as values. The column names are the declared argument names. Every event table also has a "id" and "address" column. The id is a serial integer auto-generated upon row insertion. The address column contains the address and shard ID of the contract that emitted the event. Since events are indexed in Cirrus just like full contracts, they can act as lightweight data records for one-time actions (hence the name "event").

Since members are managed on Private Shards via Solidity events, you can easily track the historical log of what members were added or removed and in what order.

Note all event arguments are coerced to their string representation when inserted into an event table.

Example Event

contract Car {
    event Drive(string location, uint speed, uint timestamp);

    constructor() {...}

    function driveCar(string _location, uint _speed) {
        emit Drive(_location, _speed, block.timestamp);
    }
}

Car.Drive Table

id address location speed timestamp
1 deadbeef "Grocery Store" "25" "123456"
2 b0bacafe "Road Trip" "60" "789000"

Application Usage

Applications typically use Cirrus to get large amounts of contract data to display in UI tables, or retrieve data from multiple related contract’s when displaying an individual asset that might be represented by multiple contracts, identified by some common unique identifier.

Note

Joining across tables with identical values is not currently supported for contract’s on private shards, thus querying across multiple tables based on IDs must be done manually on the application side.

See the Managing Assets Page or the Cirrus Documentation for more information on this topic.