# Indexing the RSK blockchain for RIF Marketplace
## Problem Definition
Find indexing solutions for the RSK blockchain
## Background
Querying data from blockchain runs into performance and bandwidth issues. Primarily, this happens because blockchains do not have an initial query language unlike other regular databases and no record indices are available for optimal queries. Blockchain’s distributed nature also becomes an obstacle in this case.
For the lagacy version of the marketplace, a solution was created in order to address this problem for the data existing in the RSK blockchain: [The RIF Marketplace Cache](https://github.com/rsksmart/rif-marketplace-cache). This piece of software appeared to bring some issues:
- Little fault tolerance: When it crashes, it gets out of sync with the blockchain data and the server would need to be restarted in order for it to prefetch all of the bockchain's data into the application's database again.
However, this problem has been addressed by various third party technologies out there that are dedicated to exclusively indexing blockchains.
## Available solutions
According to our research, the problem of indexing the blockchain is being addressed by these technologies:
- The Graph
- Covalent
- Unmarshal
- Nakji Network
### The Graph
The Graph is one of the most popular ethereum-like blockchain indexing solutions out there that claims to be compatible with Ethereum and IPFS.
The Graph serves like a cache for things that have happened in section of the blockchain (subgraph). They store incorporates 3 main concepts in order to address the indexing problem:
- **Subgraph**: This can be thought as the specification or manifest that describes What things in the blockchain are goung to be indexed
- **Schema**: A GraphQL Schema. This is the specification that indicates the objects or entities we're going to be storing for indexing.
- **Mappings**: A set of functions that run when an event is triggered in the blockchain and map the event data to object instances as specified in the schema.
By running some tests using Ganache, we're able to see how The Graph tries to fetch data using `eth_blockNumber` and `eth_getBlockByNumber`:
```eth_blockNumber
> {
> "jsonrpc": "2.0",
> "method": "eth_blockNumber",
> "params": [],
> "id": 586350
> }
< {
< "id": 586350,
< "jsonrpc": "2.0",
< "result": "0x43"
< }
eth_getBlockByNumber
> {
> "jsonrpc": "2.0",
> "method": "eth_getBlockByNumber",
> "params": [
> "0x41",
> false
> ],
> "id": 586351
> }
< {
< "id": 586351,
< "jsonrpc": "2.0",
< "result": {
< "number": "0x41",
< "hash": "0xa183434370fa90aab23c011fee987486d0593ee82b7e728c4a2ae9140412db3d",
< "parentHash": "0xa7e582a9e30436c7b35f2d5bc652ad34664418e53eafc0d4384e7c7ff18045b8",
< "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
< "nonce": "0x0000000000000000",
< "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
< "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
< "transactionsRoot": "0x87eb767339b19b099aac8cfa953f624654176fd19294e7df7136c5891606173f",
< "stateRoot": "0x8c8bfff286ae3dd093dd47e4df6d26a2511cf0c955961bf73af87654b2566707",
< "receiptsRoot": "0xa0d08385ec3b182ec808c662f1ed531b84a515389a00ac2854b0a493522b1b87",
< "miner": "0x0000000000000000000000000000000000000000",
< "difficulty": "0x0",
< "totalDifficulty": "0x0",
< "extraData": "0x",
< "size": "0x3e8",
< "gasLimit": "0x989680",
< "gasUsed": "0xaaa2",
< "timestamp": "0x6127b526",
< "transactions": [
< "0x1f6e1210fede21d395b1b9b9e02290453ed9db29d41a39ea0dbd47e570994ec9"
< ],
< "uncles": []
< }
< }
```
One of RSK's main motivation is to keep as compatible as possible with Ethereum's developer tools, including the JSON-RPC standard endpoints. Diving into [RSK's JSON-RPC Specification](https://developers.rsk.co/rsk/node/architecture/json-rpc/), these and most of the methods seem to be aligned with [Ethereum's JSON-RPC Specification](https://eth.wiki/json-rpc/API).
The Hypothesis is that if both blockchains respond to the same interface, then an external service like The Graph shouldn't care about their details but their interface only, in order words, The Graph should be able to work with RSK, since it has the same JSON-RPC interface.
In order to test this hypothesis, we will design a simple experiment: Substitute Ganache with a local instance of RSK. It's important to note that there's some background on this, it was tested by another team and it seems that it does work but it's undocumented. For this reason, we will attempt it ourselves in Apps & Services and have everything documented here in case we end up using this solution.
What we need for the experiment:
1. Configure a local RSK blockchain node (regtest)
2. Feed some data into the local blockchain (deploy a smart contract)
3. Configure a local graph node connected to the local RSK blockchain
4. Deploy a subgraph that queries the smart contract interaction's data
#### Configure a Local RSK blockchain node
Provide a basic configuration that allows us to deploy a smart contract and interact with it, as well as exposing this node for a local graph node to index its data. We also want logging functionalities in order to verify what's going on.
We will be using RSKj latest release by the time of writing this document: https://github.com/rsksmart/rskj/releases/tag/IRIS-3.0.1
1. Node configuration (regtest.conf)
```
rpc {
providers {
web {
cors = "*"
http {
enabled = true
bind_address = 0.0.0.0 # This appears to be insecure, but didn't work otherwise
hosts = ["192.168.65.2", "localhost","host.docker.internal"] # The graph runs inside a docker container, hence this hosts whitelist
port = 4444
linger_time = -1 # Not sure what this is
}
ws { # We're only exposing http (JSON-RPC)
enabled = false
}
}
}
}
miner {
server {
enabled = true
}
client {
autoMine = true # Not sure about this
}
}
wallet { # Not sure about these
enabled = true
accounts = [{
privateKey = "6b89a036f50ea2237b8be3800d72c71ac2c8413a8463e47403da50f064d50aab"
},{
privateKey = "d8e70f46bcdd6c0437779bad4b927cb9160490620e7c69d9c26dbf7ddbf69701"
}]
}
```
2. Logging configuration (logback.xml)
```xml
<configuration name="configuration">
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="execute" level="INFO"/>
<logger name="blockvalidator" level="INFO"/>
<logger name="blocksyncservice" level="TRACE"/>
<logger name="blockexecutor" level="INFO"/>
<logger name="general" level="DEBUG"/>
<logger name="gaspricetracker" level="ERROR"/>
<logger name="web3" level="INFO"/>
<logger name="repository" level="ERROR"/>
<logger name="VM" level="ERROR"/>
<logger name="blockqueue" level="ERROR"/>
<logger name="io.netty" level="ERROR"/>
<logger name="block" level="ERROR"/>
<logger name="minerserver" level="INFO"/>
<logger name="txbuilderex" level="ERROR"/>
<logger name="pendingstate" level="INFO"/>
<logger name="hsqldb.db" level="ERROR"/>
<logger name="TCK-Test" level="ERROR"/>
<logger name="db" level="ERROR"/>
<logger name="net" level="ERROR"/>
<logger name="start" level="ERROR"/>
<logger name="cli" level="ERROR"/>
<logger name="txs" level="ERROR"/>
<logger name="gas" level="ERROR"/>
<logger name="main" level="ERROR"/>
<logger name="trie" level="ERROR"/>
<logger name="org.hibernate" level="ERROR"/>
<logger name="peermonitor" level="ERROR"/>
<logger name="bridge" level="ERROR"/>
<logger name="org.springframework" level="ERROR"/>
<logger name="rlp" level="ERROR"/>
<logger name="messagehandler" level="ERROR"/>
<logger name="syncprocessor" level="TRACE"/>
<logger name="sync" level="ERROR"/>
<logger name="BtcToRskClient" level="ERROR"/>
<logger name="ui" level="ERROR"/>
<logger name="java.nio" level="ERROR"/>
<logger name="org.eclipse.jetty" level="ERROR"/>
<logger name="wire" level="ERROR"/>
<logger name="BridgeSupport" level="ERROR"/>
<logger name="jsonrpc" level="ERROR"/>
<logger name="wallet" level="ERROR"/>
<logger name="blockchain" level="INFO"/>
<logger name="blockprocessor" level="ERROR"/>
<logger name="state" level="INFO"/>
<logger name="messageProcess" level="INFO"/>
<root level="DEBUG">
<appender-ref ref="stdout"/>
<appender-ref ref="FILE-AUDIT"/>
</root>
</configuration>
```
3. Command for running the node
```shell
$ java -Drsk.conf.file=./regtest.conf -Dlogback.configurationFile=./logback.xml -cp rskj-core-3.0.1-IRIS-all.jar co.rsk.Start --regtest
```
#### Feed some data into the local blockchain (deploy a smart contract)
For this, we have selected [the ballot smart contract example](https://docs.soliditylang.org/en/v0.4.24/solidity-by-example.html) shown in Ethereum's documentation.
1. Deploy Smart Contract
A simple ballot for voting proposals where the contract owner (the chairman) has the capability of giving other wallets the right to vote, and voters can vote for the proposal of their choice.
The repository lies [here](https://github.com/jchayan/ethereum-ballot).
Modifying the `delegate` and `vote` methods to dispatch events. The idea is to tell The Graph to index these events.
```solidity=
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Ballot {
event Voted(address indexed voterId, uint weight, uint proposal);
event Delegated(address indexed voterId, address indexed delegateId);
function delegate(address to) public {
...
emit Delegated(msg.sender, to);
}
function vote(uint proposal) public {
...
emit Voted(msg.sender, voter.weight, voter.vote);
}
...
}
```
- Events
- Delegated(indexed address, indexed address);
- Voted(indexed address, uint256, uint256);
The contract code can be seen [here](https://github.com/jchayan/ethereum-ballot/blob/main/contracts/Ballot.sol).
In order to deploy it, we run:
```shell
$ npx hardhat run scripts/deploy.js --network regtest
```
The script's output is configured to give us the contract address and starting block, which we will need later:
```
jorgechayan @ ethereum-ballot$ npx hardhat run scripts/deploy.js --network regtest
Compiling 2 files with 0.8.4
Compilation finished successfully
Ballot deployed to: 0x77045E71a7A2c50903d88e564cD72fab11e82051 on block number: 1
```
2. Contract Interaction
A small script has been written in order to simulate voting for 2 proposals:
```=
The contract is deployed by the first signer (eth_accounts[0])
The chairman gives signers 1..5 the right to vote
Signers 1 and 2 vote for the first proposal
Signers 3, 4 and 5 vote for the second proposal
We query the the winner (the second proposal, for it has 3 votes)
Signers 6, 7 and 8 delegate their vote to Signer 0 (the chairman)
The chairman votes for the first proposal with all the weight of the delegated votes.
We query the winner (the first proposal, for it has 6 votes)
```
- Emitted events
- Every call to `vote` has emitted a `Voted` event
- Every call to `delegate` has emitted a `Delegated` event
The script code can be seen [here](https://github.com/jchayan/ethereum-ballot/blob/main/scripts/demo.js).
In order to run it:
```shell
$ node scripts/demo.js --address <contract address>
```
#### Configure a local graph node connected to the local RSK blockchain
Following [The Graph's documentation](https://thegraph.com/docs/developer/quick-start):
1. Running a local graph node
For this, we need to:
* Clone [the graph node repository](https://github.com/graphprotocol/graph-node/)
* Configure the graph node docker-compose specification to connect to RSK (the basic settings are provided in file `graph-node/docker/docker-compose.yml`)
```yaml=
version: '3'
services:
graph-node:
image: graphprotocol/graph-node
ports:
- '8000:8000'
- '8001:8001'
- '8020:8020'
- '8030:8030'
- '8040:8040'
depends_on:
- ipfs
- postgres
environment:
postgres_host: postgres
postgres_user: graph-node
postgres_pass: let-me-in
postgres_db: graph-node
ipfs: 'ipfs:5001'
ethereum: 'mainnet:http://192.168.65.2:4444' # Local IP address + node port - This had to be specified this way due to issues with MacOS and host.docker.internal
GRAPH_LOG: info
extra_hosts:
- "host.docker.internal:host-gateway" # Make host.docker.internal work
ipfs:
image: ipfs/go-ipfs:v0.4.23
ports:
- '5001:5001'
volumes:
- ./data/ipfs:/data/ipfs
postgres:
image: postgres
ports:
- '5432:5432'
command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"]
environment:
POSTGRES_USER: graph-node
POSTGRES_PASSWORD: let-me-in
POSTGRES_DB: graph-node
volumes:
- ./data/postgres:/var/lib/postgresql/data
```
* Run the graph node:
```shell
$ docker-compose up
```
We should be able to check that the graph node is effectively pulling data from the RSK node:
```
graph-node_1 | Sep 30 00:07:41.392 INFO Downloading latest blocks from Ethereum. This may take a few minutes..., provider: mainnet-rpc-0, component: BlockIngestor
graph-node_1 | Sep 30 00:08:06.676 INFO Syncing 14 blocks from Ethereum., code: BlockIngestionStatus, blocks_needed: 14, blocks_behind: 14, latest_block_head: 15, current_block_head: 1, provider: mainnet-rpc-0, component: BlockIngestor
```
On the RSKj node's end, we can see the activity:
```
10:00:32.209 [nioEventLoopGroup-3-31] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Request: {"jsonrpc":"2.0","method":"eth_blockNumber"}
10:00:32.209 [nioEventLoopGroup-3-31] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Invoking method: eth_blockNumber with args []
10:00:32.211 [nioEventLoopGroup-3-31] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Invoked method: eth_blockNumber, result 0x0
10:00:33.098 [nioEventLoopGroup-3-32] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Request: {"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false],"id":279}
10:00:33.098 [nioEventLoopGroup-3-32] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Invoking method: eth_getBlockByNumber with args ["latest", false]
10:00:33.098 [nioEventLoopGroup-3-32] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Invoked method: eth_getBlockByNumber, result org.ethereum.rpc.dto.BlockResultDTO@2b464544
10:00:33.098 [nioEventLoopGroup-3-32] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Response: {"jsonrpc":"2.0","id":279,"result":{"number":"0x0","hash":"0xf75066863a1e84a90cac5f8de8ab3a284a2540953117cae38ab50497a13b4f0c","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x0d1d2ffa3e93f5fd3a2a82f69be8dd1ea48075bbc20585a8c6cdcf8c28953b65","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","miner":"0x3333333333333333333333333333333333333333","difficulty":"0x1","totalDifficulty":"0x1","extraData":"0x686f727365","size":"0x1de","gasLimit":"0x989680","gasUsed":"0x0","timestamp":"0x0","transactions":[],"uncles":[],"minimumGasPrice":"0x0","bitcoinMergedMiningHeader":"0x00","bitcoinMergedMiningCoinbaseTransaction":"0x00","bitcoinMergedMiningMerkleProof":"0x00","hashForMergedMining":"0xcedfd7a8ccef3b1c9820933971cbd4338ff13993291c437bfcbd4505da401ecf","paidFees":"0x0","cumulativeDifficulty":"0x1"}}
10:00:34.107 [nioEventLoopGroup-3-1] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Request: {"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false],"id":280}
10:00:34.107 [nioEventLoopGroup-3-1] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Invoking method: eth_getBlockByNumber with args ["latest", false]
10:00:34.107 [nioEventLoopGroup-3-1] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Invoked method: eth_getBlockByNumber, result org.ethereum.rpc.dto.BlockResultDTO@c7d617f9
10:00:34.107 [nioEventLoopGroup-3-1] DEBUG c.g.jsonrpc4j.JsonRpcBasicServer - Response: {"jsonrpc":"2.0","id":280,"result":{"number":"0x0","hash":"0xf75066863a1e84a90cac5f8de8ab3a284a2540953117cae38ab50497a13b4f0c","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x0d1d2ffa3e93f5fd3a2a82f69be8dd1ea48075bbc20585a8c6cdcf8c28953b65","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","miner":"0x3333333333333333333333333333333333333333","difficulty":"0x1","totalDifficulty":"0x1","extraData":"0x686f727365","size":"0x1de","gasLimit":"0x989680","gasUsed":"0x0","timestamp":"0x0","transactions":[],"uncles":[],"minimumGasPrice":"0x0","bitcoinMergedMiningHeader":"0x00","bitcoinMergedMiningCoinbaseTransaction":"0x00","bitcoinMergedMiningMerkleProof":"0x00","hashForMergedMining":"0xcedfd7a8ccef3b1c9820933971cbd4338ff13993291c437bfcbd4505da401ecf","paidFees":"0x0","cumulativeDifficulty":"0x1"}}
```
#### Deploy a subgraph that queries the smart contract interaction's data
1. Create the subgraph specification
This will be done in the same repository where the Ballot smart contract lies
* Subgraph Specification (subgraph.yaml)
```yaml=
specVersion: 0.0.2
description: Ethereum Voting System
repository: https://github.com/jchayan/ethereum-ballot
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Ballot
network: mainnet
source:
address: '0x77045E71a7A2c50903d88e564cD72fab11e82051' # Deployed contract address
abi: Ballot
startBlock: 1 # We can specify The Graph from which block to start indexing (in this case, from the block the smart contract was deployed)
mapping: # Mapping specification
kind: ethereum/events # What are we mapping?
apiVersion: 0.0.5
language: wasm/assemblyscript
entities:
- Voter
abis: # ABI specification
- name: Ballot
file: ./artifacts/contracts/Ballot.sol/Ballot.json
eventHandlers: # Event-Handler specification (mapping.ts)
- event: Voted(indexed address,uint256,uint256)
handler: handleVoted
- event: Delegated(indexed address,indexed address)
handler: handleDelegated
file: ./src/mapping.ts # mapping file path
```
* GraphQL schema specification (schema.graphql)
We're going to create 2 entities: Vote and Delegation, these entities will be created from the Voted and Delegated events respectivelly (more details on this in the mappings)
```graphql=
type Vote @entity {
id: ID!
weight: BigInt!
voterAddress: Bytes!
votedProposal: BigInt!
}
type Delegation @entity {
id: ID!
voterAddress: Bytes!
delegateAddress: Bytes!
}
```
* Mapping specification (src/mapping.ts)
```typescript=
import { Voted, Delegated } from '../generated/Ballot/Ballot'
import { Vote, Delegation } from '../generated/schema'
export function handleVoted(event: Voted): void {
let params = event.params;
let vote = new Vote(params.voterId.toHex());
vote.weight = params.weight;
vote.voterAddress = params.voterId;
vote.votedProposal = params.proposal;
vote.save();
}
export function handleDelegated(event: Delegated): void {
let params = event.params;
let delegation = new Delegation(params.voterId.toHex());
delegation.voterAddress = params.voterId;
delegation.delegateAddress = params.delegateId;
delegation.save();
}
```
2. Create the subgraph:
```shell
$ graph create ballot --node http://127.0.0.1:8020
```
In the graph node logs we should see:
```
graph-node_1 | Sep 30 01:40:38.150 INFO Received subgraph_create request, params: SubgraphCreateParams { name: SubgraphName("ballot") }, component: JsonRpcServer
```
3. Deploy subgraph to the local graph node
``` shell
graph deploy ballot --ipfs http://localhost:5001 --node http://127.0.0.1:8020
```
In the graph node logs we should see:
```
graph-node_1 | Sep 30 01:43:27.934 INFO Received subgraph_deploy request, params: SubgraphDeployParams { name: SubgraphName("ballot"), ipfs_hash: DeploymentHash("Qmbu8tsuLVrAQRkNUAS7reLKK8aPHZ7JCXEqXUFetS5tzv"), node_id: None }, component: JsonRpcServer
graph-node_1 | Sep 30 01:43:27.935 INFO Resolve manifest, link: Qmbu8tsuLVrAQRkNUAS7reLKK8aPHZ7JCXEqXUFetS5tzv, sgd: 0, subgraph_id: Qmbu8tsuLVrAQRkNUAS7reLKK8aPHZ7JCXEqXUFetS5tzv, component: SubgraphRegistrar
graph-node_1 | Sep 30 01:43:27.945 INFO Resolve schema, link: /ipfs/QmTsb336krYKUBBZSdsU5esvsaWE5R49Rmb8KiBaGVPskA, sgd: 0, subgraph_id: Qmbu8tsuLVrAQRkNUAS7reLKK8aPHZ7JCXEqXUFetS5tzv, component: SubgraphRegistrar
```
#### Results
With all of this setup, we're ready to test what our graph node has indexed from the RSK blockchain. For this, we're going to query the subgraph's GraphQL endpoint:
1. Votes
```shell
$ curl 'http://localhost:8000/subgraphs/name/ballot' -d '{"query":"{\n votes {\n voterAddress\n votedProposal\n weight\n }\n}","variables":null,"operationName":null}' 2> /dev/null | jsonpp
```
```json
{
"data": {
"votes": [
{
"votedProposal": "0",
"voterAddress": "0x0a3aa774752ec2042c46548456c094a76c7f3a79",
"weight": "1"
},
{
"votedProposal": "1",
"voterAddress": "0x39b12c05e8503356e3a7df0b7b33efa4c054c409",
"weight": "1"
},
{
"votedProposal": "0",
"voterAddress": "0x7986b3df570230288501eea3d890bd66948c9b79",
"weight": "1"
},
{
"votedProposal": "1",
"voterAddress": "0xc354d97642faa06781b76ffb6786f72cd7746c97",
"weight": "1"
},
{
"votedProposal": "0",
"voterAddress": "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826",
"weight": "1"
},
{
"votedProposal": "1",
"voterAddress": "0xcf7cdbbb5f7ba79d3ffe74a0bba13fc0295f6036",
"weight": "1"
}
]
}
}
```
2. Delegations
```shell
$ curl 'http://localhost:8000/subgraphs/name/ballot' -d '{"query":"{\n delegations {\n voterAddress\n delegateAddress\n }\n}","variables":null,"operationName":null}' 2> /dev/null | jsonpp
```
```json
{
"data": {
"delegations": [
{
"delegateAddress": "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826",
"voterAddress": "0x7857288e171c6159c5576d1bd9ac40c0c48a771c"
},
{
"delegateAddress": "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826",
"voterAddress": "0xa4dea4d5c954f5fd9e87f0e9752911e83a3d18b3"
},
{
"delegateAddress": "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826",
"voterAddress": "0xdebe71e1de41fc77c44df4b6db940026e31b0e71"
}
]
}
}
```
Conclusions to be added here...