Skip to content

Commit

Permalink
Consideration min votes threshold update + Added unit test for vote (#39
Browse files Browse the repository at this point in the history
)
  • Loading branch information
leomanza authored Dec 16, 2024
1 parent b0d057f commit d6b35a7
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 8 deletions.
2 changes: 1 addition & 1 deletion server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mina-ocv"
version = "0.12.2"
version = "0.12.3"
edition = "2021"

[dependencies]
Expand Down
71 changes: 71 additions & 0 deletions server/src/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,74 @@ pub struct FetchTransactionResult {
#[diesel(sql_type = BigInt)]
pub nonce: i64,
}

pub trait ArchiveInterface {
fn fetch_chain_tip(&self) -> Result<i64>;
fn fetch_latest_slot(&self) -> Result<i64>;
fn fetch_transactions(&self, start_time: i64, end_time: i64) -> Result<Vec<FetchTransactionResult>>;
}

impl ArchiveInterface for Archive {
fn fetch_chain_tip(&self) -> Result<i64> {
self.fetch_chain_tip()
}

fn fetch_latest_slot(&self) -> Result<i64> {
self.fetch_latest_slot()
}

fn fetch_transactions(&self, start_time: i64, end_time: i64) -> Result<Vec<FetchTransactionResult>> {
self.fetch_transactions(start_time, end_time)
}
}

pub struct MockArchive;

impl ArchiveInterface for MockArchive {
fn fetch_chain_tip(&self) -> Result<i64> {
Ok(100) // Return a mock value for the chain tip
}

fn fetch_latest_slot(&self) -> Result<i64> {
Ok(200) // Return a mock value for the latest slot
}

fn fetch_transactions(&self, start_time: i64, _end_time: i64) -> Result<Vec<FetchTransactionResult>> {
Ok(vec![FetchTransactionResult {
account: "mock_account".to_string(),
hash: "mock_hash".to_string(),
memo: "mock_memo".to_string(),
height: 1,
status: BlockStatus::Pending, // Use a mock value
timestamp: start_time + 1000,
nonce: 42,
}]) // Return a mock list of transactions
}
}

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

#[test]
fn test_fetch_chain_tip() {
let archive = MockArchive;
let chain_tip = archive.fetch_chain_tip().unwrap();
assert_eq!(chain_tip, 100);
}

#[test]
fn test_fetch_latest_slot() {
let archive = MockArchive;
let latest_slot = archive.fetch_latest_slot().unwrap();
assert_eq!(latest_slot, 200);
}

#[test]
fn test_fetch_transactions() {
let archive = MockArchive;
let transactions = archive.fetch_transactions(1733371364000, 1733803364000).unwrap();
assert_eq!(transactions.len(), 1);
assert_eq!(transactions[0].account, "mock_account");
}
}
15 changes: 15 additions & 0 deletions server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ pub struct OcvConfig {
/// The mina network to connect to.
#[clap(long, env)]
pub network: Network,
/// The environment stage.
#[clap(long, env = "RELEASE_STAGE")]
pub release_stage: ReleaseStage,
/// The URL from which the `proposals.json` should be fetched.
#[clap(long, env = "PROPOSALS_URL")]
pub maybe_proposals_url: Option<String>,
Expand All @@ -34,6 +37,7 @@ impl OcvConfig {
caches: Caches::build(),
archive: Archive::new(&self.archive_database_url),
network: self.network,
release_stage: self.release_stage,
ledger_storage_path: PathBuf::from_str(&self.ledger_storage_path)?,
bucket_name: self.bucket_name.clone(),
proposals: self.load_proposals().await?,
Expand Down Expand Up @@ -68,3 +72,14 @@ pub enum Network {
#[display("berkeley")]
Berkeley,
}

#[derive(Clone, Copy, Parser, ValueEnum, Debug, Display, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ReleaseStage {
#[display("development")]
Development,
#[display("staging")]
Staging,
#[display("production")]
Production,
}
37 changes: 31 additions & 6 deletions server/src/ocv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ use anyhow::{Result, anyhow};
use rust_decimal::Decimal;
use serde::Serialize;

use crate::{Archive, Ledger, Network, Proposal, Vote, VoteWithWeight, Wrapper, util::Caches};
use crate::{Archive, Ledger, Network, Proposal, ReleaseStage, Vote, VoteWithWeight, Wrapper, util::Caches};

#[derive(Clone)]
pub struct Ocv {
pub caches: Caches,
pub archive: Archive,
pub network: Network,
pub release_stage: ReleaseStage,
pub ledger_storage_path: PathBuf,
pub bucket_name: String,
pub proposals: Vec<Proposal>,
Expand Down Expand Up @@ -45,6 +46,34 @@ impl Ocv {
Ok(ProposalResponse { proposal, votes })
}

/// Checks whether the positive community vote threshold has been met based
/// on the release stage.
///
/// # Arguments
///
/// * `total_positive_community_votes` - Total number of positive votes from the community.
/// * `total_negative_community_votes` - Total number of negative votes from the community.
///
/// # Returns
///
/// Returns `true` if the positive votes meet the threshold for the current release stage,
/// otherwise `false`.
///
/// # Description
///
/// - For the `Production` release stage, the minimum threshold for positive votes is 10.
/// - For other release stages, the threshold is 2.
pub fn has_met_vote_threshold(
&self,
total_positive_community_votes: usize,
_total_negative_community_votes: usize,
) -> bool {
let min_positive_votes = if self.release_stage == ReleaseStage::Production { 10 } else { 2 };
tracing::info!("min_positive_votes {}", min_positive_votes);
tracing::info!("release_stage {}", self.release_stage);
total_positive_community_votes >= min_positive_votes
}

pub async fn proposal_consideration(
&self,
id: usize,
Expand All @@ -69,7 +98,6 @@ impl Ocv {
tracing::info!("votes {}", votes.len());
votes
};

// weighted votes
let mut positive_stake_weight = Decimal::from(0);
let mut negative_stake_weight = Decimal::from(0);
Expand All @@ -86,7 +114,7 @@ impl Ocv {
}
}
// Check if enough positive votes
if total_positive_community_votes < 10 {
if !self.has_met_vote_threshold(total_positive_community_votes, total_negative_community_votes) {
return Ok(GetMinaProposalConsiderationResponse {
proposal_id: id,
total_community_votes: votes.len(),
Expand All @@ -95,7 +123,6 @@ impl Ocv {
total_stake_weight: Decimal::ZERO,
positive_stake_weight: Decimal::ZERO,
negative_stake_weight: Decimal::ZERO,
votes,
elegible: false,
vote_status: "Insufficient voters".to_string(),
});
Expand Down Expand Up @@ -153,7 +180,6 @@ impl Ocv {
total_stake_weight,
positive_stake_weight,
negative_stake_weight,
votes,
elegible: true,
vote_status: "Proposal selected for the next phase".to_string(),
})
Expand Down Expand Up @@ -258,7 +284,6 @@ pub struct GetMinaProposalConsiderationResponse {
total_stake_weight: Decimal,
positive_stake_weight: Decimal,
negative_stake_weight: Decimal,
votes: Vec<Vote>,
vote_status: String,
elegible: bool,
}
174 changes: 174 additions & 0 deletions server/src/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,4 +302,178 @@ mod tests {
Vote::new("2", "4", "E4YiC7vB4DC9JoQvaj83nBWwHC3gJh4G9EBef7xh4ti4idBAgZai7", 120, BlockStatus::Pending, 120, 2),
]
}

#[test]
fn test_decode_mep_memo() {
let mut vote = Vote::new("1", "1", "", 100, BlockStatus::Pending, 100, 1);

vote.update_memo("E4Yd67s51QN9DZVDy8JKPEoNGykMsYQ5KRiKpZHiLZTjA8dB9SnFT");
assert_eq!(vote.decode_memo().unwrap(), "BeepBoop");

vote.update_memo("E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x");
assert_eq!(vote.decode_memo().unwrap(), "YES 1");

vote.update_memo("E4YVQPTeHZGie6hAZbLSCbuj5UuhfWsCxnLbUgBPqGJgdi8XX9CNP");
assert_eq!(vote.decode_memo().unwrap(), "NO 1");
}

#[test]
fn test_match_decode_mep_memo() {
let key = "1";
let mut votes = get_test_mep_votes();

let v0_decoded = votes[0].match_decoded_mef_memo(key).unwrap();
let v1_decoded = votes[1].match_decoded_mef_memo(key).unwrap();
let v2_decoded = votes[2].match_decoded_mef_memo(key).unwrap();
let v3_decoded = votes[3].match_decoded_mef_memo(key).unwrap();
let v4_decoded = votes[4].match_decoded_mef_memo(key).unwrap();
let v5_decoded = votes[5].match_decoded_mef_memo(key).unwrap();
let v6_decoded = votes[6].match_decoded_mef_memo(key).unwrap();
let v7_decoded = votes[7].match_decoded_mef_memo(key).unwrap();
let v8_decoded = votes[8].match_decoded_mef_memo(key).unwrap();
let v9_decoded = votes[9].match_decoded_mef_memo(key).unwrap();
let v10_decoded = votes[10].match_decoded_mef_memo(key).unwrap();

assert_eq!(v0_decoded, "YES 1");
assert_eq!(v1_decoded, "YES 1");
assert_eq!(v2_decoded, "YES 1");
assert_eq!(v3_decoded, "YES 1");
assert_eq!(v4_decoded, "YES 1");
assert_eq!(v5_decoded, "YES 1");
assert_eq!(v6_decoded, "YES 1");
assert_eq!(v7_decoded, "YES 1");
assert_eq!(v8_decoded, "YES 1");
assert_eq!(v9_decoded, "YES 1");
assert_eq!(v10_decoded, "NO 1");
}

#[test]
fn test_process_mep_votes() {
let votes = get_test_mep_votes();
let binding = Wrapper(votes).process_mep(1, 130);
let processed = binding.to_vec().0;

assert_eq!(processed.len(), 11);

let a1 = processed.iter().find(|s| s.account == "1").unwrap();
let a2 = processed.iter().find(|s| s.account == "2").unwrap();

assert_eq!(a1.account, "1");
assert_eq!(a1.hash, "1");
assert_eq!(a1.memo, "YES 1");
assert_eq!(a1.height, 331718);
assert_eq!(a1.status, BlockStatus::Canonical);
assert_eq!(a1.nonce, 1);

assert_eq!(a2.account, "2");
assert_eq!(a2.hash, "2");
assert_eq!(a2.memo, "YES 1");
assert_eq!(a2.height, 341719);
assert_eq!(a2.status, BlockStatus::Pending);
assert_eq!(a2.nonce, 2);
}

fn get_test_mep_votes() -> Vec<Vote> {
vec![
Vote::new(
"1",
"1",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
331718,
BlockStatus::Canonical,
1730897878000,
1,
),
Vote::new(
"2",
"2",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
341719,
BlockStatus::Pending,
1730897878000,
2,
),
Vote::new(
"3",
"3",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
351320,
BlockStatus::Pending,
1730897878000,
3,
),
Vote::new(
"4",
"4",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
352721,
BlockStatus::Pending,
1730897878000,
4,
),
Vote::new(
"5",
"5",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
353722,
BlockStatus::Pending,
1730897878000,
5,
),
Vote::new(
"6",
"6",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
354723,
BlockStatus::Pending,
1730897878000,
6,
),
Vote::new(
"7",
"7",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
355724,
BlockStatus::Pending,
1730897878000,
7,
),
Vote::new(
"8",
"8",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
356725,
BlockStatus::Pending,
1730897878000,
8,
),
Vote::new(
"9",
"9",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
357726,
BlockStatus::Pending,
1730897878000,
9,
),
Vote::new(
"10",
"10",
"E4YXRxe1SLybDSwNoEKENNyoaj9ro9WDbiDPtJrX9Fmj5nUtVeq6x",
358727,
BlockStatus::Pending,
1730897878000,
10,
),
Vote::new(
"11",
"11",
"E4YVQPTeHZGie6hAZbLSCbuj5UuhfWsCxnLbUgBPqGJgdi8XX9CNP",
358728,
BlockStatus::Pending,
1730897878000,
11,
),
]
}
}

0 comments on commit d6b35a7

Please sign in to comment.