diff --git a/packages/wasm-sdk/src/queries/protocol.rs b/packages/wasm-sdk/src/queries/protocol.rs index 8a7a0b60fba..f6cfaf59017 100644 --- a/packages/wasm-sdk/src/queries/protocol.rs +++ b/packages/wasm-sdk/src/queries/protocol.rs @@ -16,55 +16,105 @@ use wasm_dpp2::{ProTxHashLikeNullableJs, ProTxHashWasm}; pub struct ProtocolVersionUpgradeStateWasm { current_protocol_version: u32, next_protocol_version: Option, - activation_height: Option, - vote_count: Option, - #[serde(rename = "thresholdReached")] - is_threshold_reached: bool, + vote_count: Option, } impl ProtocolVersionUpgradeStateWasm { fn new( current_protocol_version: u32, next_protocol_version: Option, - activation_height: Option, - vote_count: Option, - is_threshold_reached: bool, + vote_count: Option, ) -> Self { Self { current_protocol_version, next_protocol_version, - activation_height, vote_count, - is_threshold_reached, } } } #[wasm_bindgen(js_class = ProtocolVersionUpgradeState)] impl ProtocolVersionUpgradeStateWasm { + /// Protocol version the chain was running when this response was produced. #[wasm_bindgen(getter = "currentProtocolVersion")] pub fn current_protocol_version(&self) -> u32 { self.current_protocol_version } + /// Candidate upgrade version: the version above the current one with the + /// most evonode votes, if any votes exist. #[wasm_bindgen(getter = "nextProtocolVersion")] pub fn next_protocol_version(&self) -> Option { self.next_protocol_version } - #[wasm_bindgen(getter = "activationHeight")] - pub fn activation_height(&self) -> Option { - self.activation_height - } - + /// Number of evonode votes cast for `nextProtocolVersion`. #[wasm_bindgen(getter = "voteCount")] - pub fn vote_count(&self) -> Option { + pub fn vote_count(&self) -> Option { self.vote_count } +} + +/// Pick the candidate upgrade version from the vote counts: among versions +/// newer than `current_version`, the one with the most votes; equal vote +/// counts resolve to the highest version. +fn next_version_upgrade( + upgrades: &drive_proof_verifier::types::ProtocolVersionUpgrades, + current_version: u32, +) -> (Option, Option) { + upgrades + .iter() + .filter_map(|(version, votes)| votes.map(|votes| (*version, votes))) + .filter(|(version, _)| *version > current_version) + .max_by_key(|&(version, votes)| (votes, version)) + .map_or((None, None), |(version, votes)| { + (Some(version), Some(votes)) + }) +} + +#[cfg(test)] +mod tests { + use super::next_version_upgrade; + use drive_proof_verifier::types::ProtocolVersionUpgrades; - #[wasm_bindgen(getter = "isThresholdReached")] - pub fn is_threshold_reached(&self) -> bool { - self.is_threshold_reached + #[test] + fn empty_upgrades_yield_no_candidate() { + let upgrades = ProtocolVersionUpgrades::new(); + + assert_eq!(next_version_upgrade(&upgrades, 12), (None, None)); + } + + #[test] + fn none_vote_counts_are_skipped() { + let upgrades = ProtocolVersionUpgrades::from_iter([(13, None), (14, None)]); + + assert_eq!(next_version_upgrade(&upgrades, 12), (None, None)); + } + + #[test] + fn versions_at_or_below_current_are_excluded() { + let upgrades = ProtocolVersionUpgrades::from_iter([(11, Some(50)), (12, Some(80))]); + + assert_eq!(next_version_upgrade(&upgrades, 12), (None, None)); + } + + #[test] + fn picks_future_version_with_most_votes() { + let upgrades = ProtocolVersionUpgrades::from_iter([ + (11, Some(200)), + (13, Some(7)), + (14, Some(132)), + (15, Some(3)), + ]); + + assert_eq!(next_version_upgrade(&upgrades, 12), (Some(14), Some(132))); + } + + #[test] + fn equal_votes_resolve_to_highest_version() { + let upgrades = ProtocolVersionUpgrades::from_iter([(13, Some(9)), (14, Some(9))]); + + assert_eq!(next_version_upgrade(&upgrades, 12), (Some(14), Some(9))); } } @@ -108,36 +158,18 @@ impl WasmSdk { use dash_sdk::platform::FetchMany; use drive_proof_verifier::types::ProtocolVersionVoteCount; - let upgrade_result: drive_proof_verifier::types::ProtocolVersionUpgrades = - ProtocolVersionVoteCount::fetch_many(self.as_ref(), ()).await?; - - // Get the current protocol version from the SDK - let current_version = self.version(); - - // Find the next version with votes - let mut next_version = None; - let mut activation_height = None; - let mut vote_count = None; - let mut threshold_reached = false; - - // The result is an IndexMap> where u32 is version and Option is activation height - for (version, height_opt) in upgrade_result.iter() { - if *version > current_version { - next_version = Some(*version); - activation_height = *height_opt; - // TODO: Get actual vote count and threshold from platform - vote_count = None; - threshold_reached = height_opt.is_some(); - break; - } - } + let (upgrade_result, metadata): (drive_proof_verifier::types::ProtocolVersionUpgrades, _) = + ProtocolVersionVoteCount::fetch_many_with_metadata(self.as_ref(), (), None).await?; + + // The chain's protocol version at the time of the response + let current_version = metadata.protocol_version; + + let (next_version, vote_count) = next_version_upgrade(&upgrade_result, current_version); Ok(ProtocolVersionUpgradeStateWasm::new( current_version, next_version, - activation_height, vote_count, - threshold_reached, )) } @@ -194,32 +226,12 @@ impl WasmSdk { ) = ProtocolVersionVoteCount::fetch_many_with_metadata_and_proof(self.as_ref(), (), None) .await?; - // Get the current protocol version from the SDK - let current_version = self.version(); - - // Find the next version with votes - let mut next_version = None; - let mut activation_height = None; - let mut vote_count = None; - let mut threshold_reached = false; - - for (version, height_opt) in upgrade_result.iter() { - if *version > current_version { - next_version = Some(*version); - activation_height = *height_opt; - vote_count = None; - threshold_reached = height_opt.is_some(); - break; - } - } + // The chain's protocol version at the time of the response + let current_version = metadata.protocol_version; - let state = ProtocolVersionUpgradeStateWasm::new( - current_version, - next_version, - activation_height, - vote_count, - threshold_reached, - ); + let (next_version, vote_count) = next_version_upgrade(&upgrade_result, current_version); + + let state = ProtocolVersionUpgradeStateWasm::new(current_version, next_version, vote_count); Ok(ProofMetadataResponseWasm::from_sdk_parts( state, metadata, proof, diff --git a/packages/wasm-sdk/tests/unit/conversion-simple-types.spec.ts b/packages/wasm-sdk/tests/unit/conversion-simple-types.spec.ts index e3fb1eb7521..24eed82a766 100644 --- a/packages/wasm-sdk/tests/unit/conversion-simple-types.spec.ts +++ b/packages/wasm-sdk/tests/unit/conversion-simple-types.spec.ts @@ -52,17 +52,13 @@ describe('Simple Type Conversions', () => { const jsonFixture = { currentProtocolVersion: 7, nextProtocolVersion: 8, - activationHeight: 50000, voteCount: 100, - thresholdReached: false, }; const objectFixture = { currentProtocolVersion: 7, nextProtocolVersion: 8, - activationHeight: 50000n, - voteCount: 100, - thresholdReached: false, + voteCount: 100n, }; describe('toJSON()', () => { @@ -84,8 +80,7 @@ describe('Simple Type Conversions', () => { const result = sdk.ProtocolVersionUpgradeState.fromJSON(jsonFixture); expect(result.currentProtocolVersion).to.equal(7); expect(result.nextProtocolVersion).to.equal(8); - expect(result.voteCount).to.equal(100); - expect(result.isThresholdReached).to.equal(false); + expect(result.voteCount).to.equal(100n); }); }); @@ -94,17 +89,13 @@ describe('Simple Type Conversions', () => { const fixture = { currentProtocolVersion: 7, nextProtocolVersion: null, - activationHeight: null, voteCount: null, - thresholdReached: true, }; const result = sdk.ProtocolVersionUpgradeState.fromJSON(fixture); expect(result.currentProtocolVersion).to.equal(7); expect(result.nextProtocolVersion).to.be.undefined(); - expect(result.activationHeight).to.be.undefined(); expect(result.voteCount).to.be.undefined(); - expect(result.isThresholdReached).to.equal(true); }); }); });