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_URLshould 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:
| Endpoint | Method | Description |
|---|---|---|
/governance-tracker/governance-tracker/proposals | GET | Fetch all governance proposals |
/governance-tracker/votes/<proposal_id> | GET | Fetch all votes associated with a given proposal |
/governance-tracker/resolutions/<proposal_id> | GET | Fetch all resolution steps for a given proposal |
/governance-tracker/history/<voter_address> | GET | Get the full voting history of a specific voter |
/governance-tracker/stats/<voter_address> | GET | Retrieve 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.