Governance Tracker

Overview

governance_tracker is a Rust-based application designed to track on-chain governance proposals, votes, and resolutions. It connects to a blockchain node, stores governance events into a persistent Postgres database using sqlx, and exposes useful HTTP endpoints to retrieve data via a REST API.

Features

  • Tracks proposals, votes, and resolutions.
  • Records voter-specific history and statistics.
  • Stores data in a Postgres database.
  • Exposes a RESTful API.

Installation & Setup

1. Clone the Repository

git clone https://github.com/Entropy-Foundation/supra-toolbox.git
cd trackers/governance_tracker

2. Prepare SQLx Environment

Ensure the sqlx-cli is installed:

cargo install sqlx-cli --no-default-features --features postgres

Prepare the sqlx environment:

cargo sqlx prepare

Migrate sql to PostgresDB:

DATABASE_URL="postgres://<username>:<password>@localhost/<database_name>" cargo sqlx migrate run
DATABASE_URL="postgres://<username>:<password>@localhost/<database_name>" cargo sqlx prepare -- --lib

Note: The DATABASE_URL should pointing towards PostgresDB. Please create user on PostgresDB and create a database. After that provide username, password and databasename, modify url accordingly.

3. Build the Project

DATABASE_URL="postgres://<username>:<password>@localhost/<database_name>" cargo build --release

Usage

Run the application with historical sync enabled:

DATABASE_URL="postgres://<username>:<password>@localhost/<database_name>" cargo run --release -- --rpc-url <RPC_URL> --sync

This command will fetch historical governance events and start the REST API server.

Run the application with archiveDB sync data:

DATABASE_URL="postgres://<username>:<password>@localhost/<database_name>" cargo run --release -- --rpc-url <RPC_URL> --archive-db-path <ARCHIVE_DB_PATH>

Run the application from some particular start block:

cargo run --release -- --rpc-url <RPC_URL> --start-block <START_BLOCK>

For running the tests use:

    DATABASE_URL="postgres://<username>:<password>@localhost/<database_name>" cargo test

API Endpoints

Once the application is running, it will start a local HTTP server on port 3030. Here are the available endpoints:

EndpointMethodDescription
/governance-tracker/governance-tracker/proposalsGETFetch all governance proposals
/governance-tracker/votes/<proposal_id>GETFetch all votes associated with a given proposal
/governance-tracker/resolutions/<proposal_id>GETFetch all resolution steps for a given proposal
/governance-tracker/history/<voter_address>GETGet the full voting history of a specific voter
/governance-tracker/stats/<voter_address>GETRetrieve statistics for a specific voter

Example usage

curl http://127.0.0.1:3030/governance-tracker/proposals
curl http://127.0.0.1:3030/governance-tracker/votes/123
curl http://127.0.0.1:3030/governance-tracker/resolutions/123
curl http://127.0.0.1:3030/governance-tracker/history/0xabc...
curl http://127.0.0.1:3030/governance-tracker/stats/0xabc...

Data Model

The governance tracker captures and stores the following on-chain governance events in its Postgres database:

1. Proposals

Represents information related to creation of proposal with a unique id:

#![allow(unused)]
fn main() {
pub struct Proposal {
    pub id: i64,                    // Unique proposal identifier
    pub proposer: String,            // Account address of proposer
    pub creation_block: i64,         // Block number of proposal creation
    pub timestamp: i64,              // Unix timestamp of creation
    pub execution_hash: String,      // Hash of execution logic
    pub metadata: serde_json::Value, // JSON metadata (title, description, etc.)
    pub yes_votes: i64,              // Current affirmative votes
    pub no_votes: i64,               // Current negative votes
    pub min_vote_threshold: i64,     // Minimum votes required for resolution
    pub steps_resolved: i64          // Completed resolution steps
}
}

2. Votes

Represents the votes casted corresponding to a proposal_id:

#![allow(unused)]
fn main() {
pub struct Vote {
    pub proposal_id: i64,
    pub voter: String,        // Account address of the voter
    pub block_height: i64,    // Block number where vote was recorded
    pub timestamp: i64,       // Unix timestamp of the vote
    pub vote_choice: bool,    // True = support, False = reject
}
}

3. Resolutions

Represents the information regarding resolution of a proposal

#![allow(unused)]
fn main() {
pub struct Resolution {
    pub proposal_id: i64,
    pub yes_votes: i64,         // Final yes votes at resolution
    pub no_votes: i64,          // Final no votes at resolution
    pub resolved_early: bool,   // Whether resolved before deadline
    pub resolution_block: i64,  // Block number of resolution
    pub timestamp: i64,         // Unix timestamp of resolution
    pub tx_hash: String,        // Transaction hash that triggered resolution
}
}

4. Voter Stats

A structure representing the aggregate statistics of a particular voter

#![allow(unused)]
fn main() {
pub struct VoterStats {
    pub voter: String,
    pub total_proposals: usize,       // Number of proposals created by the voter
    pub total_votes: usize,           // Total votes cast
    pub yes_votes: usize,             // Total affirmative votes
    pub no_votes: usize,              // Total negative votes
    pub first_vote_timestamp: Option<i64>,  // Timestamp of first participation
    pub last_vote_timestamp: Option<i64>,   // Timestamp of most recent participation
}
}

4. Voter History

A collection of all the votes corresponding to a certain voter, each vote containing the following information:

#![allow(unused)]
fn main() {
pub struct DbVoterHistory {
    pub proposal_id: i64,
    pub block_height: i64,    // Block number where vote was recorded
    pub timestamp: i64,       // Unix timestamp of the vote
    pub vote_choice: bool,    // True = support, False = reject
}
}

Execution Workflow

Run the application with historical sync enabled:

cargo run --release -- --sync

The program operates in two distinct modes depending on synchronization status with the blockchain:

1. Sync Mode (sync())

#![allow(unused)]
fn main() {
pub async fn sync(&mut self, start_block: u64) -> Result<u64>
}

Sync mode is used when local database state is significantly behind the chain head.

2. Run Mode (run())

#![allow(unused)]
fn main() {
pub async fn run(&mut self, start_block: u64, wait_time_in_sec: u64) -> Result<()>
}

Run mode is used when we are near the chain head and require live event polling. The event provider crate is used in the run mode to provide live events, efficiently handling the delays required for live polling.

3. Sync from ArchiveDB Mode (sync())

#![allow(unused)]
fn main() {
pub async fn syn_from_db(&mut self, archive_db_reader: &ArchiveDBReader, start_block: u64, end_block: u64,) -> Result<u64>
}

Sync fromm ArchvieDB mode is used when we have available store data of blockchain till some particular block. It will fetch that data first and then it will start normal service.

Dependencies

  • axum – for building HTTP APIs.
  • sqlx – for asynchronous Postgres support.
  • tokio – for async runtime.
  • serde – for JSON serialization.