Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: CI

on:
push:
branches: ["master"]
pull_request:
branches: ["master"]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
ci:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.93
with:
components: rustfmt, clippy

- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
api/target
key: ${{ runner.os }}-cargo-${{ hashFiles('api/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-

- name: Install cargo-sort
uses: taiki-e/install-action@v2
with:
tool: cargo-sort

- name: Install machete
uses: taiki-e/install-action@v2
with:
tool: cargo-machete

- name: Check formatting
working-directory: api
run: cargo fmt --all -- --check

- name: Run clippy
working-directory: api
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Check dependency sort
working-directory: api
run: cargo sort --check

- name: Check unused dependencies
working-directory: api
run: cargo machete

- name: Run tests
working-directory: api
env:
RPC_URL: ${{ secrets.RPC_URL }}
DATABASE_URL: postgres://dummy:dummy@localhost/dummy
REDIS_URL: redis://localhost:6379
AUTH_SECRET: dummy-secret
PORT: "8080"
run: cargo test
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.5.3] - 2026-04-04
## [Unreleased]

### Added

- **CI workflow** (`.github/workflows/ci.yaml`): runs `cargo fmt`, `cargo clippy`, `cargo sort`, and `cargo machete` on every push and PR to `master`.
- **`rust-toolchain.toml`**: pins Rust toolchain to `1.93` (matching the Dockerfile), shared between local dev and CI.

### Fixed

- **Re-verification always marked `is_verified=false`**: fixed per-row `is_verified` computation when on-chain hash changes, preventing builds with matching hashes from being incorrectly unverified.
- **Duplicate phantom build record on every verification**: removed the spurious `initial_uuid` row that was inserted and immediately marked completed before the real verification build started.

### Removed

- **`use-external-pdas` feature flag**: dead code — the feature gated imports that were never used anywhere in the codebase.

## [1.5.2] - 2026-03-25

### Added
Expand Down
4 changes: 0 additions & 4 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ name = "verified_programs_api"
version = "1.5.3"
edition = "2021"

[features]
default = []
use-external-pdas = []

[dependencies]
axum = "0.6.18"
borsh = "1.5.1"
Expand Down
2 changes: 2 additions & 0 deletions api/src/db/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ mod tests {
use super::*;

#[tokio::test]
#[ignore = "requires database and Redis"]
async fn test_program_authority() {
dotenv::dotenv().ok();
let db_url = std::env::var("TEST_DATABASE_URL").unwrap();
Expand All @@ -273,6 +274,7 @@ mod tests {
}

#[tokio::test]
#[ignore = "requires database and Redis"]
async fn test_program_frozen_and_closed_status() {
dotenv::dotenv().ok();
let db_url = std::env::var("TEST_DATABASE_URL").unwrap();
Expand Down
1 change: 1 addition & 0 deletions api/src/db/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ mod tests {
use super::*;

#[tokio::test]
#[ignore = "requires database and Redis"]
async fn test_db_conn_healthcheck() {
dotenv::dotenv().ok();
let db_url = std::env::var("TEST_DATABASE_URL").unwrap();
Expand Down
1 change: 1 addition & 0 deletions api/src/db/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ mod tests {
use crate::db::models::JobStatus;

#[tokio::test]
#[ignore = "requires database and Redis"]
async fn test_job_status_update() {
dotenv::dotenv().ok();
let db_url = std::env::var("TEST_DATABASE_URL").unwrap();
Expand Down
1 change: 1 addition & 0 deletions api/src/db/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ mod tests {
use super::*;

#[tokio::test]
#[ignore = "requires database and Redis"]
async fn test_logs_crud() {
dotenv::dotenv().ok();
let db_url = std::env::var("TEST_DATABASE_URL").unwrap();
Expand Down
48 changes: 48 additions & 0 deletions api/src/db/models/unverify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,51 @@ pub fn parse_helius_transaction(
(StatusCode::BAD_REQUEST, "Invalid payload")
})
}

#[cfg(test)]
mod tests {
use super::*;

/// If this test fails, the Helius API may have changed the structure of the transaction payload.
/// Review the deserialization of `HeliusParsedTransaction` against the current Helius API response.
#[tokio::test]
async fn test_parse_helius_transaction_from_api() {
dotenv::dotenv().ok();
let rpc_url = std::env::var("RPC_URL").expect("RPC_URL must be set");
// Helius RPC URL: https://mainnet.helius-rpc.com/?api-key=KEY
// Helius API URL: https://api.helius.xyz/v0/transactions/?api-key=KEY
let url = rpc_url.replace("mainnet.helius-rpc.com/", "api.helius.xyz/v0/transactions/");

let tx_sig = "31AUfFXG6BJQjaqwBsCjjZV5ojEL4zbrJ9gKQfKHDMosPvJKQBy6dKTiZgkkjoKbG1StD11csqgWn1KU5EwQsUgX";

let client = reqwest::Client::new();
let body = serde_json::json!({ "transactions": [tx_sig] });

let response = client
.post(&url)
.json(&body)
.send()
.await
.expect("Failed to call Helius API");

assert!(
response.status().is_success(),
"Helius API returned non-success status: {}",
response.status()
);

let payload: Vec<Value> = response
.json()
.await
.expect("Failed to deserialize Helius response");

let parsed =
parse_helius_transaction(&payload).expect("Failed to parse transaction payload");

assert_eq!(parsed.signature, tx_sig);
assert!(
!parsed.instructions.is_empty(),
"Expected at least one instruction"
);
}
}
1 change: 1 addition & 0 deletions api/src/db/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ mod tests {
use chrono::Utc;

#[tokio::test]
#[ignore = "requires database and Redis"]
async fn test_build_params_operations() {
dotenv::dotenv().ok();
let db_url = std::env::var("TEST_DATABASE_URL").unwrap();
Expand Down
1 change: 1 addition & 0 deletions api/src/db/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ mod tests {
use super::*;

#[tokio::test]
#[ignore = "requires database and Redis"]
async fn test_get_verified_programs() {
dotenv::dotenv().ok();
let db_url = std::env::var("TEST_DATABASE_URL").unwrap();
Expand Down
1 change: 1 addition & 0 deletions api/src/db/redis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ mod tests {
use super::*;

#[tokio::test]
#[ignore = "requires database and Redis"]
async fn test_cache_operations() {
dotenv::dotenv().ok();
let db_url = std::env::var("TEST_DATABASE_URL").unwrap();
Expand Down
2 changes: 2 additions & 0 deletions api/src/services/onchain/program_hash_retriver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ mod tests {
use super::*;

#[tokio::test]
#[ignore = "requires solana-verify binary"]
async fn test_get_on_chain_hash() {
let program_id = "verifycLy8mB96wd9wqq3WDXQwM4oU6r42Th37Db9fC";
let result = get_on_chain_hash(program_id).await;
Expand All @@ -109,6 +110,7 @@ mod tests {
}

#[tokio::test]
#[ignore = "requires solana-verify binary"]
async fn test_get_on_chain_hash_closed_program() {
// This program has been closed - program data account no longer exists
let program_id = "2gFsaXeN9jngaKbQvZsLwxqfUrT2n4WRMraMpeL8NwZM";
Expand Down
10 changes: 0 additions & 10 deletions api/src/services/onchain/program_metadata_retriever.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,6 @@ use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use solana_sdk_ids::bpf_loader_upgradeable;

#[cfg(feature = "use-external-pdas")]
use {
solana_account_decoder::UiAccountEncoding,
solana_client::{
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
rpc_filter::{Memcmp, RpcFilterType},
},
solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel},
};

/// Program ID for the Otter Verify program
pub const OTTER_VERIFY_PROGRAMID: Pubkey =
solana_sdk::pubkey!("verifycLy8mB96wd9wqq3WDXQwM4oU6r42Th37Db9fC");
Expand Down
2 changes: 1 addition & 1 deletion api/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ mod tests {
);
assert_eq!(
validate_pubkey("12345678901234567890123456789012345678901"),
Err("Invalid public key: Invalid Base58 string".to_string())
Err("Invalid public key(12345678901234567890123456789012345678901): Invalid Base58 string".to_string())
);
}

Expand Down
3 changes: 3 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
channel = "1.93"
components = ["rustfmt", "clippy"]
Loading