From a4f6552fea41093b308b4f686a7b139fc90a272a Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 16:45:38 -0700 Subject: [PATCH 01/27] Refactor reward account handling to use `StakeAddress` - Replace `RewardAccount` with `StakeAddress` across modules for consistency. - Adapt serialization, deserialization, and encoding/decoding logic for `StakeAddress`. - Adjust related tests, helpers, and comments to align with the refactor. --- common/src/address.rs | 49 ++++++++++++++++++- common/src/types.rs | 13 +++-- modules/accounts_state/src/rewards.rs | 9 ++-- modules/accounts_state/src/snapshot.rs | 6 +-- modules/accounts_state/src/state.rs | 45 ++++++++--------- modules/rest_blockfrost/src/handlers/pools.rs | 2 +- modules/spo_state/src/state.rs | 30 ++++++++---- modules/tx_unpacker/src/map_parameters.rs | 13 +++-- 8 files changed, 116 insertions(+), 51 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index bb63a00e..1efe052c 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -214,7 +214,7 @@ impl StakeAddress { } } - /// Read from string format + /// Read from a string format pub fn from_string(text: &str) -> Result { let (hrp, data) = bech32::decode(text)?; if let Some(header) = data.first() { @@ -223,7 +223,7 @@ impl StakeAddress { false => AddressNetwork::Main, }; - let payload = match (header >> 4) & 0x0F { + let payload = match (header >> 4) & 0x0Fu8 { 0b1110 => StakeAddressPayload::StakeKeyHash(data[1..].to_vec()), 0b1111 => StakeAddressPayload::ScriptHash(data[1..].to_vec()), _ => return Err(anyhow!("Unknown header {header} in stake address")), @@ -273,6 +273,51 @@ impl StakeAddress { } } +impl Default for StakeAddress { + fn default() -> Self { + StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![0u8; 28]), + } + } +} + +impl minicbor::Encode for StakeAddress { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + // Encode as bytes (same format as from_binary expects) + let network_bits = match self.network { + AddressNetwork::Main => 0b1u8, + AddressNetwork::Test => 0b0u8, + }; + + let (stake_bits, stake_hash): (u8, &Vec) = match &self.payload { + StakeAddressPayload::StakeKeyHash(data) => (0b1110, data), + StakeAddressPayload::ScriptHash(data) => (0b1111, data), + }; + + let mut data = vec![network_bits | (stake_bits << 4)]; + data.extend(stake_hash); + + e.bytes(&data)?; + Ok(()) + } +} + +impl<'b, C> minicbor::Decode<'b, C> for StakeAddress { + fn decode( + d: &mut minicbor::Decoder<'b>, + _ctx: &mut C, + ) -> Result { + let bytes = d.bytes()?; + StakeAddress::from_binary(bytes) + .map_err(|e| minicbor::decode::Error::message(e.to_string())) + } +} + /// A Cardano address #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Address { diff --git a/common/src/types.rs b/common/src/types.rs index 898ca34a..0f879083 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -709,9 +709,9 @@ pub struct PoolRegistration { pub margin: Ratio, /// Reward account - #[serde_as(as = "Hex")] + // #[serde_as(as = "Hex")] #[n(5)] - pub reward_account: RewardAccount, + pub reward_account: StakeAddress, /// Pool owners by their key hash #[serde_as(as = "Vec")] @@ -1586,7 +1586,7 @@ pub struct VotingOutcome { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ProposalProcedure { pub deposit: Lovelace, - pub reward_account: RewardAccount, + pub reward_account: StakeAddress, pub gov_action_id: GovActionId, pub gov_action: GovernanceAction, pub anchor: Anchor, @@ -1748,6 +1748,8 @@ pub struct AssetAddressEntry { mod tests { use super::*; use anyhow::Result; + use crate::{AddressNetwork, StakeAddressPayload}; + #[test] fn era_order() -> Result<()> { @@ -1828,7 +1830,10 @@ mod tests { let proposal = ProposalProcedure { deposit: 9876, - reward_account: vec![7, 4, 6, 7], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]) + }, gov_action_id, gov_action, anchor: Anchor { diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index 054d0577..e2e8004f 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -1,10 +1,7 @@ //! Acropolis AccountsState: rewards calculations use crate::snapshot::{Snapshot, SnapshotSPO}; -use acropolis_common::{ - protocol_params::ShelleyParams, rational_number::RationalNumber, KeyHash, Lovelace, - RewardAccount, SPORewards, -}; +use acropolis_common::{protocol_params::ShelleyParams, rational_number::RationalNumber, KeyHash, Lovelace, RewardAccount, SPORewards, StakeAddress}; use anyhow::{bail, Result}; use bigdecimal::{BigDecimal, One, ToPrimitive, Zero}; use std::cmp::min; @@ -231,7 +228,7 @@ impl RewardsState { hex::encode(&hash)); // Transfer from reserves to this account - result.rewards.push((hash.clone(), to_pay)); + result.rewards.push((RewardAccount::from(spo.reward_account.get_hash()), to_pay)); result.total_paid += to_pay; *num_delegators_paid += 1; @@ -243,7 +240,7 @@ impl RewardsState { costs.to_u64().unwrap_or(0) }; - result.rewards.push((spo.reward_account.clone(), spo_benefit)); + result.rewards.push((RewardAccount::from(spo.reward_account.get_hash()), spo_benefit)); result.spo_rewards.push(( operator_id.clone(), SPORewards { diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 58f93b94..16d41879 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -1,9 +1,7 @@ //! Acropolis AccountsState: snapshot for rewards calculations use crate::state::Pots; -use acropolis_common::{ - stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, RewardAccount, -}; +use acropolis_common::{stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, RewardAccount, StakeAddress}; use imbl::OrdMap; use std::collections::HashMap; use tracing::info; @@ -30,7 +28,7 @@ pub struct SnapshotSPO { pub blocks_produced: usize, /// Reward account - pub reward_account: RewardAccount, + pub reward_account: StakeAddress, /// Pool owners pub pool_owners: Vec, diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index d6e4d8d2..aa151517 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -95,11 +95,11 @@ impl State { // rewards to calculate let shelly_params = match &self.protocol_parameters { Some(ProtocolParams { - shelley: Some(sp), .. - }) => sp, + shelley: Some(sp), .. + }) => sp, _ => return None, } - .clone(); + .clone(); let total_supply = shelly_params.max_lovelace_supply - self.rewards_state.mark.pots.reserves; @@ -218,11 +218,11 @@ impl State { // rewards to calculate let shelley_params = match &self.protocol_parameters { Some(ProtocolParams { - shelley: Some(sp), .. - }) => sp, + shelley: Some(sp), .. + }) => sp, _ => return Ok(vec![]), } - .clone(); + .clone(); // Filter the block counts for SPOs that are registered - treating any we don't know // as 'OBFT' style (the legacy nodes) @@ -518,7 +518,7 @@ impl State { // save SPO rewards spo_rewards = reward_result.spo_rewards.into_iter().collect(); - // Adjust the reserves for next time with amount actually paid + // Adjust the reserves for next time with the amount actually paid self.pots.reserves -= reward_result.total_paid; } _ => (), @@ -565,18 +565,13 @@ impl State { self.pool_refunds = Vec::new(); for id in &spo_msg.retired_spos { if let Some(retired_spo) = new_spos.get(id) { - match StakeAddress::from_binary(&retired_spo.reward_account) { - Ok(stake_address) => { - let keyhash = stake_address.get_hash(); - debug!( - "SPO {} has retired - refunding their deposit to {}", - hex::encode(id), - hex::encode(keyhash) - ); - self.pool_refunds.push(keyhash.to_vec()); - } - Err(e) => error!("Error repaying SPO deposit: {e}"), - } + let keyhash = retired_spo.reward_account.get_hash(); + debug!( + "SPO {} has retired - refunding their deposit to {}", + hex::encode(id), + hex::encode(keyhash) +); + self.pool_refunds.push(keyhash.to_vec()); // Remove from our list new_spos.remove(id); @@ -852,7 +847,10 @@ mod tests { numerator: 1, denominator: 20, }, - reward_account: Vec::new(), + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]) + }, pool_owners: Vec::new(), relays: Vec::new(), pool_metadata: None, @@ -866,7 +864,10 @@ mod tests { numerator: 1, denominator: 10, }, - reward_account: Vec::new(), + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]) + }, pool_owners: Vec::new(), relays: Vec::new(), pool_metadata: None, @@ -1207,7 +1208,7 @@ mod tests { DRepDelegationDistribution { abstain: 10_000, no_confidence: 100_000, - dreps: vec![(drep_addr_cred, 1_000_100), (drep_script_cred, 2_001_000),], + dreps: vec![(drep_addr_cred, 1_000_100), (drep_script_cred, 2_001_000), ], } ); diff --git a/modules/rest_blockfrost/src/handlers/pools.rs b/modules/rest_blockfrost/src/handlers/pools.rs index d5bf5771..faf6cacc 100644 --- a/modules/rest_blockfrost/src/handlers/pools.rs +++ b/modules/rest_blockfrost/src/handlers/pools.rs @@ -692,7 +692,7 @@ async fn handle_pools_spo_blockfrost( let live_pledge = live_pledge?; let pool_id = pool_info.operator.to_bech32_with_hrp("pool").unwrap(); - let reward_account = pool_info.reward_account.to_bech32_with_hrp("stake"); + let reward_account = pool_info.reward_account.to_string(); let Ok(reward_account) = reward_account else { return Ok(RESTResponse::with_text(404, "Invalid Reward Account")); }; diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 931ad608..8f904864 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -671,10 +671,7 @@ impl State { mod tests { use super::*; use crate::test_utils::*; - use acropolis_common::{ - state_history::{StateHistory, StateHistoryStore}, - PoolRetirement, Ratio, TxCertificate, TxHash, - }; + use acropolis_common::{state_history::{StateHistory, StateHistoryStore}, AddressNetwork, PoolRetirement, Ratio, StakeAddress, StakeAddressPayload, TxCertificate, TxHash}; use tokio::sync::Mutex; #[test] @@ -719,7 +716,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -868,7 +868,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -924,7 +927,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -1041,7 +1047,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -1090,7 +1099,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index d7ae1fd8..728ed617 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -245,7 +245,10 @@ pub fn map_certificate( numerator: margin.numerator, denominator: margin.denominator, }, - reward_account: reward_account.to_vec(), + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::ScriptHash(reward_account.to_vec()), + }, pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { @@ -347,7 +350,10 @@ pub fn map_certificate( numerator: margin.numerator, denominator: margin.denominator, }, - reward_account: reward_account.to_vec(), + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::ScriptHash(reward_account.to_vec()), + }, pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { @@ -804,7 +810,8 @@ pub fn map_governance_proposals_procedures( ) -> Result { Ok(ProposalProcedure { deposit: prop.deposit, - reward_account: prop.reward_account.to_vec(), + reward_account: StakeAddress::from_binary(&prop.reward_account) + .expect("Failed to convert reward account"), gov_action_id: gov_action_id.clone(), gov_action: map_governance_action(&prop.gov_action)?, anchor: map_anchor(&prop.anchor), From db993900435ca1006619c408dcff8d41a5811cdc Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 16:57:21 -0700 Subject: [PATCH 02/27] Refactor `StakeAddress` usage and clean up redundant annotations - Simplify `reward_account` handling by utilizing `StakeAddress::from_binary`. - Adjust `StakeAddress` default implementation for consistency. --- common/src/address.rs | 19 +++++++++---------- common/src/types.rs | 6 ++---- modules/tx_unpacker/src/map_parameters.rs | 10 ++-------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 1efe052c..85b1b3d3 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -273,22 +273,12 @@ impl StakeAddress { } } -impl Default for StakeAddress { - fn default() -> Self { - StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![0u8; 28]), - } - } -} - impl minicbor::Encode for StakeAddress { fn encode( &self, e: &mut minicbor::Encoder, _ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { - // Encode as bytes (same format as from_binary expects) let network_bits = match self.network { AddressNetwork::Main => 0b1u8, AddressNetwork::Test => 0b0u8, @@ -318,6 +308,15 @@ impl<'b, C> minicbor::Decode<'b, C> for StakeAddress { } } +impl Default for StakeAddress { + fn default() -> Self { + StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![0u8; 28]), + } + } +} + /// A Cardano address #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Address { diff --git a/common/src/types.rs b/common/src/types.rs index 0f879083..83ee7770 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -709,7 +709,6 @@ pub struct PoolRegistration { pub margin: Ratio, /// Reward account - // #[serde_as(as = "Hex")] #[n(5)] pub reward_account: StakeAddress, @@ -1747,9 +1746,8 @@ pub struct AssetAddressEntry { #[cfg(test)] mod tests { use super::*; - use anyhow::Result; use crate::{AddressNetwork, StakeAddressPayload}; - + use anyhow::Result; #[test] fn era_order() -> Result<()> { @@ -1832,7 +1830,7 @@ mod tests { deposit: 9876, reward_account: StakeAddress { network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]) + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), }, gov_action_id, gov_action, diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index 728ed617..01793d9b 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -245,10 +245,7 @@ pub fn map_certificate( numerator: margin.numerator, denominator: margin.denominator, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::ScriptHash(reward_account.to_vec()), - }, + reward_account: StakeAddress::from_binary(reward_account)?, pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { @@ -350,10 +347,7 @@ pub fn map_certificate( numerator: margin.numerator, denominator: margin.denominator, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::ScriptHash(reward_account.to_vec()), - }, + reward_account: StakeAddress::from_binary(reward_account)?, pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { From 97f38c248a25fc69978a125e655e83e696db0b63 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 17:15:35 -0700 Subject: [PATCH 03/27] Refactor `StakeAddress` usage to simplify `reward_account` initialization - Replace `reward_account` field with `StakeAddress::default()` for consistency. - Remove redundant `StakeAddressPayload` in test cases. - Clean up unused imports and redundant code across modules. --- common/src/types.rs | 6 +----- modules/accounts_state/src/state.rs | 32 ++++++++++++----------------- modules/spo_state/src/state.rs | 30 ++++++++------------------- 3 files changed, 23 insertions(+), 45 deletions(-) diff --git a/common/src/types.rs b/common/src/types.rs index 83ee7770..dd0c06e1 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -1746,7 +1746,6 @@ pub struct AssetAddressEntry { #[cfg(test)] mod tests { use super::*; - use crate::{AddressNetwork, StakeAddressPayload}; use anyhow::Result; #[test] @@ -1828,10 +1827,7 @@ mod tests { let proposal = ProposalProcedure { deposit: 9876, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), gov_action_id, gov_action, anchor: Anchor { diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index aa151517..a57a949b 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -95,11 +95,11 @@ impl State { // rewards to calculate let shelly_params = match &self.protocol_parameters { Some(ProtocolParams { - shelley: Some(sp), .. - }) => sp, + shelley: Some(sp), .. + }) => sp, _ => return None, } - .clone(); + .clone(); let total_supply = shelly_params.max_lovelace_supply - self.rewards_state.mark.pots.reserves; @@ -218,11 +218,11 @@ impl State { // rewards to calculate let shelley_params = match &self.protocol_parameters { Some(ProtocolParams { - shelley: Some(sp), .. - }) => sp, + shelley: Some(sp), .. + }) => sp, _ => return Ok(vec![]), } - .clone(); + .clone(); // Filter the block counts for SPOs that are registered - treating any we don't know // as 'OBFT' style (the legacy nodes) @@ -567,10 +567,10 @@ impl State { if let Some(retired_spo) = new_spos.get(id) { let keyhash = retired_spo.reward_account.get_hash(); debug!( - "SPO {} has retired - refunding their deposit to {}", - hex::encode(id), - hex::encode(keyhash) -); + "SPO {} has retired - refunding their deposit to {}", + hex::encode(id), + hex::encode(keyhash) + ); self.pool_refunds.push(keyhash.to_vec()); // Remove from our list @@ -847,10 +847,7 @@ mod tests { numerator: 1, denominator: 20, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]) - }, + reward_account: StakeAddress::default(), pool_owners: Vec::new(), relays: Vec::new(), pool_metadata: None, @@ -864,10 +861,7 @@ mod tests { numerator: 1, denominator: 10, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]) - }, + reward_account: StakeAddress::default(), pool_owners: Vec::new(), relays: Vec::new(), pool_metadata: None, @@ -1208,7 +1202,7 @@ mod tests { DRepDelegationDistribution { abstain: 10_000, no_confidence: 100_000, - dreps: vec![(drep_addr_cred, 1_000_100), (drep_script_cred, 2_001_000), ], + dreps: vec![(drep_addr_cred, 1_000_100), (drep_script_cred, 2_001_000),], } ); diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 8f904864..1c07d201 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -671,7 +671,10 @@ impl State { mod tests { use super::*; use crate::test_utils::*; - use acropolis_common::{state_history::{StateHistory, StateHistoryStore}, AddressNetwork, PoolRetirement, Ratio, StakeAddress, StakeAddressPayload, TxCertificate, TxHash}; + use acropolis_common::{ + state_history::{StateHistory, StateHistoryStore}, + PoolRetirement, Ratio, StakeAddress, TxCertificate, TxHash, + }; use tokio::sync::Mutex; #[test] @@ -716,10 +719,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -868,10 +868,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -927,10 +924,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -1047,10 +1041,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -1099,10 +1090,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, From f76615fd685c9bd2bce6b855327ecb41da440a79 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 17:43:43 -0700 Subject: [PATCH 04/27] Simplify `StakeAddress::from_binary` error handling in `reward_account` initialization --- modules/tx_unpacker/src/map_parameters.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index 01793d9b..630c0e8c 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -804,8 +804,7 @@ pub fn map_governance_proposals_procedures( ) -> Result { Ok(ProposalProcedure { deposit: prop.deposit, - reward_account: StakeAddress::from_binary(&prop.reward_account) - .expect("Failed to convert reward account"), + reward_account: StakeAddress::from_binary(&prop.reward_account)?, gov_action_id: gov_action_id.clone(), gov_action: map_governance_action(&prop.gov_action)?, anchor: map_anchor(&prop.anchor), From 5948f369aa1c56e82e943bbf9a931d72e04813f6 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 16:45:38 -0700 Subject: [PATCH 05/27] Refactor reward account handling to use `StakeAddress` - Replace `RewardAccount` with `StakeAddress` across modules for consistency. - Adapt serialization, deserialization, and encoding/decoding logic for `StakeAddress`. - Adjust related tests, helpers, and comments to align with the refactor. # Conflicts: # modules/accounts_state/src/rewards.rs # modules/accounts_state/src/snapshot.rs # modules/accounts_state/src/state.rs --- common/src/address.rs | 49 ++++++++++++++++++- common/src/types.rs | 13 +++-- modules/accounts_state/src/snapshot.rs | 5 +- modules/rest_blockfrost/src/handlers/pools.rs | 2 +- modules/spo_state/src/state.rs | 30 ++++++++---- modules/tx_unpacker/src/map_parameters.rs | 13 +++-- 6 files changed, 90 insertions(+), 22 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 9d5a17c7..31a16e4d 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -214,7 +214,7 @@ impl StakeAddress { } } - /// Read from string format + /// Read from a string format pub fn from_string(text: &str) -> Result { let (hrp, data) = bech32::decode(text)?; if let Some(header) = data.first() { @@ -223,7 +223,7 @@ impl StakeAddress { false => AddressNetwork::Main, }; - let payload = match (header >> 4) & 0x0F { + let payload = match (header >> 4) & 0x0Fu8 { 0b1110 => StakeAddressPayload::StakeKeyHash(data[1..].to_vec()), 0b1111 => StakeAddressPayload::ScriptHash(data[1..].to_vec()), _ => return Err(anyhow!("Unknown header {header} in stake address")), @@ -273,6 +273,51 @@ impl StakeAddress { } } +impl Default for StakeAddress { + fn default() -> Self { + StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![0u8; 28]), + } + } +} + +impl minicbor::Encode for StakeAddress { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + // Encode as bytes (same format as from_binary expects) + let network_bits = match self.network { + AddressNetwork::Main => 0b1u8, + AddressNetwork::Test => 0b0u8, + }; + + let (stake_bits, stake_hash): (u8, &Vec) = match &self.payload { + StakeAddressPayload::StakeKeyHash(data) => (0b1110, data), + StakeAddressPayload::ScriptHash(data) => (0b1111, data), + }; + + let mut data = vec![network_bits | (stake_bits << 4)]; + data.extend(stake_hash); + + e.bytes(&data)?; + Ok(()) + } +} + +impl<'b, C> minicbor::Decode<'b, C> for StakeAddress { + fn decode( + d: &mut minicbor::Decoder<'b>, + _ctx: &mut C, + ) -> Result { + let bytes = d.bytes()?; + StakeAddress::from_binary(bytes) + .map_err(|e| minicbor::decode::Error::message(e.to_string())) + } +} + /// A Cardano address #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Address { diff --git a/common/src/types.rs b/common/src/types.rs index 898ca34a..0f879083 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -709,9 +709,9 @@ pub struct PoolRegistration { pub margin: Ratio, /// Reward account - #[serde_as(as = "Hex")] + // #[serde_as(as = "Hex")] #[n(5)] - pub reward_account: RewardAccount, + pub reward_account: StakeAddress, /// Pool owners by their key hash #[serde_as(as = "Vec")] @@ -1586,7 +1586,7 @@ pub struct VotingOutcome { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ProposalProcedure { pub deposit: Lovelace, - pub reward_account: RewardAccount, + pub reward_account: StakeAddress, pub gov_action_id: GovActionId, pub gov_action: GovernanceAction, pub anchor: Anchor, @@ -1748,6 +1748,8 @@ pub struct AssetAddressEntry { mod tests { use super::*; use anyhow::Result; + use crate::{AddressNetwork, StakeAddressPayload}; + #[test] fn era_order() -> Result<()> { @@ -1828,7 +1830,10 @@ mod tests { let proposal = ProposalProcedure { deposit: 9876, - reward_account: vec![7, 4, 6, 7], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]) + }, gov_action_id, gov_action, anchor: Anchor { diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 41f96c51..9e13d71c 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -1,8 +1,7 @@ //! Acropolis AccountsState: snapshot for rewards calculations +use acropolis_common::{stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, RewardAccount, StakeAddress}; use crate::state::{Pots, RegistrationChange}; -use acropolis_common::stake_addresses::StakeAddressMap; -use acropolis_common::{KeyHash, Lovelace, PoolRegistration, Ratio, RewardAccount, StakeAddress}; use imbl::OrdMap; use std::collections::HashMap; use std::sync::Arc; @@ -30,7 +29,7 @@ pub struct SnapshotSPO { pub blocks_produced: usize, /// Reward account - pub reward_account: RewardAccount, + pub reward_account: StakeAddress, /// Is the reward account from two epochs ago registered at the time of this snapshot? pub two_previous_reward_account_is_registered: bool, diff --git a/modules/rest_blockfrost/src/handlers/pools.rs b/modules/rest_blockfrost/src/handlers/pools.rs index 74cf52bf..53cdacd6 100644 --- a/modules/rest_blockfrost/src/handlers/pools.rs +++ b/modules/rest_blockfrost/src/handlers/pools.rs @@ -692,7 +692,7 @@ async fn handle_pools_spo_blockfrost( let live_pledge = live_pledge?; let pool_id = pool_info.operator.to_bech32_with_hrp("pool").unwrap(); - let reward_account = pool_info.reward_account.to_bech32_with_hrp("stake"); + let reward_account = pool_info.reward_account.to_string(); let Ok(reward_account) = reward_account else { return Ok(RESTResponse::with_text(404, "Invalid Reward Account")); }; diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 5c4dcb77..5e8f2b71 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -679,10 +679,7 @@ impl State { mod tests { use super::*; use crate::test_utils::*; - use acropolis_common::{ - state_history::{StateHistory, StateHistoryStore}, - PoolRetirement, Ratio, TxCertificate, TxHash, - }; + use acropolis_common::{state_history::{StateHistory, StateHistoryStore}, AddressNetwork, PoolRetirement, Ratio, StakeAddress, StakeAddressPayload, TxCertificate, TxHash}; use tokio::sync::Mutex; #[test] @@ -721,7 +718,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -870,7 +870,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -926,7 +929,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -1044,7 +1050,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -1094,7 +1103,10 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: vec![0], + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), + }, pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index d7ae1fd8..728ed617 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -245,7 +245,10 @@ pub fn map_certificate( numerator: margin.numerator, denominator: margin.denominator, }, - reward_account: reward_account.to_vec(), + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::ScriptHash(reward_account.to_vec()), + }, pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { @@ -347,7 +350,10 @@ pub fn map_certificate( numerator: margin.numerator, denominator: margin.denominator, }, - reward_account: reward_account.to_vec(), + reward_account: StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::ScriptHash(reward_account.to_vec()), + }, pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { @@ -804,7 +810,8 @@ pub fn map_governance_proposals_procedures( ) -> Result { Ok(ProposalProcedure { deposit: prop.deposit, - reward_account: prop.reward_account.to_vec(), + reward_account: StakeAddress::from_binary(&prop.reward_account) + .expect("Failed to convert reward account"), gov_action_id: gov_action_id.clone(), gov_action: map_governance_action(&prop.gov_action)?, anchor: map_anchor(&prop.anchor), From d033ba778f0169c1636cde984a3a5d93ca9e0052 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 16:57:21 -0700 Subject: [PATCH 06/27] Refactor `StakeAddress` usage and clean up redundant annotations - Simplify `reward_account` handling by utilizing `StakeAddress::from_binary`. - Adjust `StakeAddress` default implementation for consistency. --- common/src/address.rs | 19 +++++++++---------- common/src/types.rs | 6 ++---- modules/tx_unpacker/src/map_parameters.rs | 10 ++-------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 31a16e4d..f7b46af4 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -273,22 +273,12 @@ impl StakeAddress { } } -impl Default for StakeAddress { - fn default() -> Self { - StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![0u8; 28]), - } - } -} - impl minicbor::Encode for StakeAddress { fn encode( &self, e: &mut minicbor::Encoder, _ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { - // Encode as bytes (same format as from_binary expects) let network_bits = match self.network { AddressNetwork::Main => 0b1u8, AddressNetwork::Test => 0b0u8, @@ -318,6 +308,15 @@ impl<'b, C> minicbor::Decode<'b, C> for StakeAddress { } } +impl Default for StakeAddress { + fn default() -> Self { + StakeAddress { + network: AddressNetwork::Main, + payload: StakeAddressPayload::StakeKeyHash(vec![0u8; 28]), + } + } +} + /// A Cardano address #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Address { diff --git a/common/src/types.rs b/common/src/types.rs index 0f879083..83ee7770 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -709,7 +709,6 @@ pub struct PoolRegistration { pub margin: Ratio, /// Reward account - // #[serde_as(as = "Hex")] #[n(5)] pub reward_account: StakeAddress, @@ -1747,9 +1746,8 @@ pub struct AssetAddressEntry { #[cfg(test)] mod tests { use super::*; - use anyhow::Result; use crate::{AddressNetwork, StakeAddressPayload}; - + use anyhow::Result; #[test] fn era_order() -> Result<()> { @@ -1832,7 +1830,7 @@ mod tests { deposit: 9876, reward_account: StakeAddress { network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]) + payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), }, gov_action_id, gov_action, diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index 728ed617..01793d9b 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -245,10 +245,7 @@ pub fn map_certificate( numerator: margin.numerator, denominator: margin.denominator, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::ScriptHash(reward_account.to_vec()), - }, + reward_account: StakeAddress::from_binary(reward_account)?, pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { @@ -350,10 +347,7 @@ pub fn map_certificate( numerator: margin.numerator, denominator: margin.denominator, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::ScriptHash(reward_account.to_vec()), - }, + reward_account: StakeAddress::from_binary(reward_account)?, pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { From add6d5d75906f9ba8bb6eeec06d34d2c02424770 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 17:15:35 -0700 Subject: [PATCH 07/27] Refactor `StakeAddress` usage to simplify `reward_account` initialization - Replace `reward_account` field with `StakeAddress::default()` for consistency. - Remove redundant `StakeAddressPayload` in test cases. - Clean up unused imports and redundant code across modules. --- common/src/types.rs | 6 +----- modules/spo_state/src/state.rs | 30 +++++++++--------------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/common/src/types.rs b/common/src/types.rs index 83ee7770..dd0c06e1 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -1746,7 +1746,6 @@ pub struct AssetAddressEntry { #[cfg(test)] mod tests { use super::*; - use crate::{AddressNetwork, StakeAddressPayload}; use anyhow::Result; #[test] @@ -1828,10 +1827,7 @@ mod tests { let proposal = ProposalProcedure { deposit: 9876, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), gov_action_id, gov_action, anchor: Anchor { diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 5e8f2b71..a63c15c0 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -679,7 +679,10 @@ impl State { mod tests { use super::*; use crate::test_utils::*; - use acropolis_common::{state_history::{StateHistory, StateHistoryStore}, AddressNetwork, PoolRetirement, Ratio, StakeAddress, StakeAddressPayload, TxCertificate, TxHash}; + use acropolis_common::{ + state_history::{StateHistory, StateHistoryStore}, + PoolRetirement, Ratio, StakeAddress, TxCertificate, TxHash, + }; use tokio::sync::Mutex; #[test] @@ -718,10 +721,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -870,10 +870,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -929,10 +926,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -1050,10 +1044,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, @@ -1103,10 +1094,7 @@ mod tests { numerator: 0, denominator: 0, }, - reward_account: StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(vec![1, 2, 3, 4]), - }, + reward_account: StakeAddress::default(), pool_owners: vec![vec![0]], relays: vec![], pool_metadata: None, From 3e4509701a5792b080bb8c43e00076c94791d377 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 17:43:43 -0700 Subject: [PATCH 08/27] Simplify `StakeAddress::from_binary` error handling in `reward_account` initialization --- modules/tx_unpacker/src/map_parameters.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index 01793d9b..630c0e8c 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -804,8 +804,7 @@ pub fn map_governance_proposals_procedures( ) -> Result { Ok(ProposalProcedure { deposit: prop.deposit, - reward_account: StakeAddress::from_binary(&prop.reward_account) - .expect("Failed to convert reward account"), + reward_account: StakeAddress::from_binary(&prop.reward_account)?, gov_action_id: gov_action_id.clone(), gov_action: map_governance_action(&prop.gov_action)?, anchor: map_anchor(&prop.anchor), From bc0a98134757f2291dd9f03a96883735704be3c0 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 17:58:23 -0700 Subject: [PATCH 09/27] Refactor `reward_account` handling to use `get_hash` method - Replace `StakeAddress::from_binary` usage with `reward_account.get_hash` for simplicity and consistency. - Update all modules to align with the updated `StakeAddress` handling. - Remove redundant error handling and simplify logging where applicable. --- modules/accounts_state/src/rewards.rs | 13 +++++------ modules/accounts_state/src/snapshot.rs | 22 +++++------------- modules/accounts_state/src/state.rs | 31 +++++++++++--------------- 3 files changed, 25 insertions(+), 41 deletions(-) diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index 4beef5b4..d0a22fb6 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -130,11 +130,11 @@ pub fn calculate_rewards( if !pay_to_pool_reward_account { debug!( "Checking old reward account {}", - hex::encode(&staking_spo.reward_account) + hex::encode(&staking_spo.reward_account.get_hash()) ); // Note we use the staking reward account - it could have changed - pay_to_pool_reward_account = registrations.contains(&staking_spo.reward_account) + pay_to_pool_reward_account = registrations.contains(staking_spo.reward_account.get_hash()) } // There was a bug in the original node from Shelley until Allegra where if multiple SPOs @@ -155,7 +155,7 @@ pub fn calculate_rewards( warn!("Shelley shared reward account bug: Dropping reward to {} in favour of {} on shared account {}", hex::encode(&operator_id), hex::encode(&other_id), - hex::encode(&staking_spo.reward_account)); + hex::encode(&staking_spo.reward_account.get_hash())); break; } } @@ -357,8 +357,7 @@ fn calculate_spo_rewards( } // Check pool's reward address - removing e1 prefix - // TODO use StakeAddress.get_hash() - if spo.reward_account[1..] == *hash { + if spo.reward_account.get_hash() == *hash { debug!( "Skipping pool reward account {}, losing {to_pay}", hex::encode(hash) @@ -395,7 +394,7 @@ fn calculate_spo_rewards( if pay_to_pool_reward_account { rewards.push(RewardDetail { // TODO Hack to remove e1 header - needs resolving properly with StakeAddress - account: RewardAccount::from(&spo.reward_account[1..]), + account: RewardAccount::from(spo.reward_account.get_hash()), rtype: RewardType::Leader, amount: spo_benefit, }); @@ -403,7 +402,7 @@ fn calculate_spo_rewards( info!( "SPO {}'s reward account {} not paid {}", hex::encode(&operator_id), - hex::encode(&spo.reward_account), + hex::encode(&spo.reward_account.get_hash()), spo_benefit, ); } diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 9e13d71c..9ad4b3b3 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -87,22 +87,12 @@ impl Snapshot { // TODO should spo.reward_account be a StakeAddress to begin with? let two_previous_reward_account_is_registered = match two_previous_snapshot.spos.get(spo_id) { - Some(old_spo) => match StakeAddress::from_binary(&old_spo.reward_account) { - Ok(spo_reward_address) => { - let spo_reward_hash = spo_reward_address.get_hash(); - stake_addresses - .get(spo_reward_hash) - .map(|sas| sas.registered) - .unwrap_or(false) - } - Err(e) => { - error!( - "Can't decode reward address for SPO {}: {e}", - hex::encode(&spo_id) - ); - - false - } + Some(old_spo) => { + let spo_reward_hash = old_spo.reward_account.get_hash(); + stake_addresses + .get(spo_reward_hash) + .map(|sas| sas.registered) + .unwrap_or(false) }, None => false, }; diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 97c626e3..25cd3fed 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -773,23 +773,18 @@ impl State { self.pool_refunds = Vec::new(); for id in &spo_msg.retired_spos { if let Some(retired_spo) = new_spos.get(id) { - match StakeAddress::from_binary(&retired_spo.reward_account) { - Ok(stake_address) => { - let keyhash = stake_address.get_hash(); - debug!( - "SPO {} has retired - refunding their deposit to {}", - hex::encode(id), - hex::encode(keyhash) - ); - self.pool_refunds.push(keyhash.to_vec()); - } - Err(e) => error!("Error repaying SPO deposit: {e}"), - } - - // Schedule to retire - we need them to still be in place when we count - // blocks for the previous epoch - self.retiring_spos.push(id.to_vec()); + let key_hash = &retired_spo.reward_account.get_hash(); + debug!( + "SPO {} has retired - refunding their deposit to {}", + hex::encode(id), + hex::encode(key_hash) + ); + self.pool_refunds.push(key_hash.to_vec()); } + + // Schedule to retire - we need them to still be in place when we count + // blocks for the previous epoch + self.retiring_spos.push(id.clone()); } self.spos = new_spos; @@ -1072,7 +1067,7 @@ mod tests { numerator: 1, denominator: 20, }, - reward_account: Vec::new(), + reward_account: StakeAddress::default(), pool_owners: Vec::new(), relays: Vec::new(), pool_metadata: None, @@ -1086,7 +1081,7 @@ mod tests { numerator: 1, denominator: 10, }, - reward_account: Vec::new(), + reward_account: StakeAddress::default(), pool_owners: Vec::new(), relays: Vec::new(), pool_metadata: None, From c90a1c1e9b1c8b3f8fba31f22d0b92885655514e Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 14 Oct 2025 18:35:07 -0700 Subject: [PATCH 10/27] Replace `RewardAccount` with `StakeAddress` for consistency and simplify related logic - Update `RewardDetail` and reward handling logic to use `StakeAddress` directly. - Replace `reward_account` field with `.get_hash()` where necessary. --- modules/accounts_state/src/rewards.rs | 14 +++++++------- modules/accounts_state/src/snapshot.rs | 26 ++++++++++++++------------ modules/accounts_state/src/state.rs | 9 +++++---- modules/accounts_state/src/verifier.rs | 21 +++++++++++---------- 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index d0a22fb6..aeb636be 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -3,7 +3,7 @@ use crate::snapshot::{Snapshot, SnapshotSPO}; use acropolis_common::{ protocol_params::ShelleyParams, rational_number::RationalNumber, KeyHash, Lovelace, - RewardAccount, SPORewards, + RewardAccount, SPORewards, StakeAddress, }; use anyhow::{bail, Result}; use bigdecimal::{BigDecimal, One, ToPrimitive, Zero}; @@ -24,7 +24,7 @@ pub enum RewardType { #[derive(Debug, Clone)] pub struct RewardDetail { /// Account reward paid to - pub account: RewardAccount, + pub account: StakeAddress, /// Type of reward pub rtype: RewardType, @@ -134,7 +134,8 @@ pub fn calculate_rewards( ); // Note we use the staking reward account - it could have changed - pay_to_pool_reward_account = registrations.contains(staking_spo.reward_account.get_hash()) + pay_to_pool_reward_account = + registrations.contains(staking_spo.reward_account.get_hash()) } // There was a bug in the original node from Shelley until Allegra where if multiple SPOs @@ -376,7 +377,7 @@ fn calculate_spo_rewards( // Transfer from reserves to this account rewards.push(RewardDetail { - account: hash.clone(), + account: spo.reward_account.clone(), rtype: RewardType::Member, amount: to_pay, }); @@ -393,8 +394,7 @@ fn calculate_spo_rewards( if pay_to_pool_reward_account { rewards.push(RewardDetail { - // TODO Hack to remove e1 header - needs resolving properly with StakeAddress - account: RewardAccount::from(spo.reward_account.get_hash()), + account: spo.reward_account.clone(), rtype: RewardType::Leader, amount: spo_benefit, }); @@ -402,7 +402,7 @@ fn calculate_spo_rewards( info!( "SPO {}'s reward account {} not paid {}", hex::encode(&operator_id), - hex::encode(&spo.reward_account.get_hash()), + hex::encode(spo.reward_account.get_hash()), spo_benefit, ); } diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 9ad4b3b3..3ef5ff5f 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -1,7 +1,10 @@ //! Acropolis AccountsState: snapshot for rewards calculations -use acropolis_common::{stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, RewardAccount, StakeAddress}; use crate::state::{Pots, RegistrationChange}; +use acropolis_common::{ + stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, RewardAccount, + StakeAddress, +}; use imbl::OrdMap; use std::collections::HashMap; use std::sync::Arc; @@ -85,17 +88,16 @@ impl Snapshot { // Check if the reward account from two epochs ago is still registered // TODO should spo.reward_account be a StakeAddress to begin with? - let two_previous_reward_account_is_registered = - match two_previous_snapshot.spos.get(spo_id) { - Some(old_spo) => { - let spo_reward_hash = old_spo.reward_account.get_hash(); - stake_addresses - .get(spo_reward_hash) - .map(|sas| sas.registered) - .unwrap_or(false) - }, - None => false, - }; + let two_previous_reward_account_is_registered = match two_previous_snapshot + .spos + .get(spo_id) + { + Some(old_spo) => { + let spo_reward_hash = old_spo.reward_account.get_hash(); + stake_addresses.get(spo_reward_hash).map(|sas| sas.registered).unwrap_or(false) + } + None => false, + }; // Add the new one snapshot.spos.insert( diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 25cd3fed..db5566e5 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -663,7 +663,7 @@ impl State { rewards .iter() .map(|reward| StakeRewardDelta { - hash: reward.account.clone(), + hash: reward.account.get_hash().to_vec(), delta: reward.amount as i64, }) .collect::>(), @@ -677,7 +677,8 @@ impl State { let mut stake_addresses = self.stake_addresses.lock().unwrap(); for (_, rewards) in reward_result.rewards { for reward in rewards { - stake_addresses.add_to_reward(&reward.account, reward.amount); + stake_addresses + .add_to_reward(&reward.account.get_hash().to_vec(), reward.amount); } } @@ -784,14 +785,14 @@ impl State { // Schedule to retire - we need them to still be in place when we count // blocks for the previous epoch - self.retiring_spos.push(id.clone()); + self.retiring_spos.push(id.to_vec()); } self.spos = new_spos; Ok(()) } - /// Register a stake address, with specified deposit if known + /// Register a stake address, with a specified deposit if known fn register_stake_address(&mut self, credential: &StakeCredential, deposit: Option) { // Stake addresses can be registered after being used in UTXOs let mut stake_addresses = self.stake_addresses.lock().unwrap(); diff --git a/modules/accounts_state/src/verifier.rs b/modules/accounts_state/src/verifier.rs index c2bbbd86..5403201d 100644 --- a/modules/accounts_state/src/verifier.rs +++ b/modules/accounts_state/src/verifier.rs @@ -1,7 +1,7 @@ //! Verification of calculated values against captured CSV from Haskell node / DBSync use crate::rewards::{RewardDetail, RewardType, RewardsResult}; use crate::state::Pots; -use acropolis_common::{KeyHash, RewardAccount}; +use acropolis_common::{KeyHash, RewardAccount, StakeAddress}; use hex::FromHex; use itertools::EitherOrBoth::{Both, Left, Right}; use itertools::Itertools; @@ -114,7 +114,7 @@ impl Verifier { match (&left.rtype, &right.rtype) { (RewardType::Leader, RewardType::Member) => Ordering::Less, (RewardType::Member, RewardType::Leader) => Ordering::Greater, - _ => left.account.cmp(&right.account), + _ => left.account.get_hash().cmp(&right.account.get_hash()), } } @@ -161,12 +161,13 @@ impl Verifier { _ => continue, }; - // Convert account with e1 header to just hash - // TODO: use StakeAddress, skipping first byte (e1) for now - let account = RewardAccount::from(&account[1..]); + let Ok(stake_address) = StakeAddress::from_binary(&account[1..]) else { + error!("Bad stake address in {path} for address: {address} - skipping"); + continue; + }; expected_rewards.entry(spo).or_default().push(RewardDetail { - account, + account: stake_address, rtype, amount, }); @@ -217,7 +218,7 @@ impl Verifier { error!( "Missing reward: SPO {} account {} {:?} {}", hex::encode(&expected_spo.0), - hex::encode(&expected.account), + hex::encode(&expected.account.get_hash()), expected.rtype, expected.amount ); @@ -227,7 +228,7 @@ impl Verifier { error!( "Extra reward: SPO {} account {} {:?} {}", hex::encode(&actual_spo.0), - hex::encode(&actual.account), + hex::encode(&actual.account.get_hash()), actual.rtype, actual.amount ); @@ -237,7 +238,7 @@ impl Verifier { if expected.amount != actual.amount { error!("Different reward: SPO {} account {} {:?} expected {}, actual {} ({})", hex::encode(&expected_spo.0), - hex::encode(&expected.account), + hex::encode(&expected.account.get_hash()), expected.rtype, expected.amount, actual.amount, @@ -247,7 +248,7 @@ impl Verifier { debug!( "Reward match: SPO {} account {} {:?} {}", hex::encode(&expected_spo.0), - hex::encode(&expected.account), + hex::encode(&expected.account.get_hash()), expected.rtype, expected.amount ); From f6251719c442d72b55074bc9671426f8c3d7f55c Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 15 Oct 2025 08:38:47 -0700 Subject: [PATCH 11/27] - Eliminate the `RewardAccount` type definition as it is no longer needed. - Update imports and logic across modules to use `StakeAddress` exclusively. --- common/src/types.rs | 2 -- modules/accounts_state/src/rewards.rs | 4 ++-- modules/accounts_state/src/snapshot.rs | 5 ++--- modules/accounts_state/src/state.rs | 3 +-- modules/accounts_state/src/verifier.rs | 2 +- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/common/src/types.rs b/common/src/types.rs index dd0c06e1..9a238195 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -662,8 +662,6 @@ pub struct PoolMetadata { pub hash: DataHash, } -pub type RewardAccount = Vec; - /// Pool registration with position #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PoolRegistrationWithPos { diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index aeb636be..de7952b1 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -2,8 +2,8 @@ use crate::snapshot::{Snapshot, SnapshotSPO}; use acropolis_common::{ - protocol_params::ShelleyParams, rational_number::RationalNumber, KeyHash, Lovelace, - RewardAccount, SPORewards, StakeAddress, + protocol_params::ShelleyParams, rational_number::RationalNumber, KeyHash, Lovelace, SPORewards, + StakeAddress, }; use anyhow::{bail, Result}; use bigdecimal::{BigDecimal, One, ToPrimitive, Zero}; diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 3ef5ff5f..316b4e78 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -2,13 +2,12 @@ use crate::state::{Pots, RegistrationChange}; use acropolis_common::{ - stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, RewardAccount, - StakeAddress, + stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, StakeAddress, }; use imbl::OrdMap; use std::collections::HashMap; use std::sync::Arc; -use tracing::{debug, error, info}; +use tracing::{debug, info}; /// SPO data for stake snapshot #[derive(Debug, Default)] diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index db5566e5..37b5f2cf 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -15,8 +15,7 @@ use acropolis_common::{ stake_addresses::{StakeAddressMap, StakeAddressState}, BlockInfo, DRepChoice, DRepCredential, DelegatedStake, InstantaneousRewardSource, InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, PoolLiveStakeInfo, - PoolRegistration, Pot, SPORewards, StakeAddress, StakeCredential, StakeRewardDelta, - TxCertificate, + PoolRegistration, Pot, SPORewards, StakeCredential, StakeRewardDelta, TxCertificate, }; use anyhow::Result; use imbl::OrdMap; diff --git a/modules/accounts_state/src/verifier.rs b/modules/accounts_state/src/verifier.rs index 5403201d..fed7caec 100644 --- a/modules/accounts_state/src/verifier.rs +++ b/modules/accounts_state/src/verifier.rs @@ -1,7 +1,7 @@ //! Verification of calculated values against captured CSV from Haskell node / DBSync use crate::rewards::{RewardDetail, RewardType, RewardsResult}; use crate::state::Pots; -use acropolis_common::{KeyHash, RewardAccount, StakeAddress}; +use acropolis_common::{KeyHash, StakeAddress}; use hex::FromHex; use itertools::EitherOrBoth::{Both, Left, Right}; use itertools::Itertools; From d4e7163bcd2fdb2bd19e6e79ce0f8add734c850f Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 15 Oct 2025 13:30:52 -0700 Subject: [PATCH 12/27] Add encode/decod, add comprehensive test cases for encoding, decoding --- common/src/address.rs | 168 ++++++++++++++++++++++++++++++++---------- 1 file changed, 131 insertions(+), 37 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index f7b46af4..ccfea1c7 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -214,7 +214,18 @@ impl StakeAddress { } } - /// Read from a string format + /// Convert to string stake1xxx format + pub fn to_string(&self) -> Result { + let hrp = match self.network { + AddressNetwork::Main => bech32::Hrp::parse("stake")?, + AddressNetwork::Test => bech32::Hrp::parse("stake_test")?, + }; + + let data = self.to_binary(); + Ok(bech32::encode::(hrp, &data)?) + } + + /// Read from a string format ("stake1xxx...") pub fn from_string(text: &str) -> Result { let (hrp, data) = bech32::decode(text)?; if let Some(header) = data.first() { @@ -235,6 +246,23 @@ impl StakeAddress { Err(anyhow!("Empty stake address data")) } + /// Convert to binary format (29 bytes) + pub fn to_binary(&self) -> Vec { + let network_bits = match self.network { + AddressNetwork::Main => 0b1u8, + AddressNetwork::Test => 0b0u8, + }; + + let (stake_bits, stake_hash): (u8, &Vec) = match &self.payload { + StakeAddressPayload::StakeKeyHash(data) => (0b1110, data), + StakeAddressPayload::ScriptHash(data) => (0b1111, data), + }; + + let mut data = vec![network_bits | (stake_bits << 4)]; + data.extend(stake_hash); + data + } + /// Read from binary format (29 bytes) pub fn from_binary(data: &[u8]) -> Result { if data.len() != 29 { @@ -252,24 +280,7 @@ impl StakeAddress { _ => bail!("Unknown header byte {:x} in stake address", data[0]), }; - return Ok(StakeAddress { network, payload }); - } - - /// Convert to string stake1xxx form - pub fn to_string(&self) -> Result { - let (hrp, network_bits) = match self.network { - AddressNetwork::Main => (bech32::Hrp::parse("stake")?, 1u8), - AddressNetwork::Test => (bech32::Hrp::parse("stake_test")?, 0u8), - }; - - let (stake_hash, stake_bits): (&Vec, u8) = match &self.payload { - StakeAddressPayload::StakeKeyHash(data) => (data, 0b1110), - StakeAddressPayload::ScriptHash(data) => (data, 0b1111), - }; - - let mut data = vec![network_bits | (stake_bits << 4)]; - data.extend(stake_hash); - Ok(bech32::encode::(hrp, &data)?) + Ok(StakeAddress { network, payload }) } } @@ -279,20 +290,8 @@ impl minicbor::Encode for StakeAddress { e: &mut minicbor::Encoder, _ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { - let network_bits = match self.network { - AddressNetwork::Main => 0b1u8, - AddressNetwork::Test => 0b0u8, - }; - - let (stake_bits, stake_hash): (u8, &Vec) = match &self.payload { - StakeAddressPayload::StakeKeyHash(data) => (0b1110, data), - StakeAddressPayload::ScriptHash(data) => (0b1111, data), - }; - - let mut data = vec![network_bits | (stake_bits << 4)]; - data.extend(stake_hash); - - e.bytes(&data)?; + let bytes = self.to_binary(); + e.writer_mut().write_all(&bytes).map_err(|err| minicbor::encode::Error::write(err))?; Ok(()) } } @@ -302,8 +301,7 @@ impl<'b, C> minicbor::Decode<'b, C> for StakeAddress { d: &mut minicbor::Decoder<'b>, _ctx: &mut C, ) -> Result { - let bytes = d.bytes()?; - StakeAddress::from_binary(bytes) + StakeAddress::from_binary(d.input()) .map_err(|e| minicbor::decode::Error::message(e.to_string())) } } @@ -340,10 +338,10 @@ impl Address { return Some(ptr.clone()); } } - return None; + None } - /// Read from string format + /// Read from string format ("addr1...") pub fn from_string(text: &str) -> Result { if text.starts_with("addr1") || text.starts_with("addr_test1") { Ok(Self::Shelley(ShelleyAddress::from_string(text)?)) @@ -374,6 +372,7 @@ impl Address { mod tests { use super::*; use crate::crypto::keyhash_224; + use minicbor::{Decode, Encode}; #[test] fn byron_address() { @@ -637,4 +636,99 @@ mod tests { "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" ); } + + fn mainnet_stake_address() -> StakeAddress { + let binary = + hex::decode("e1558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001").unwrap(); + StakeAddress::from_binary(&binary).unwrap() + } + + fn testnet_script_address() -> StakeAddress { + let binary = + hex::decode("e0558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001").unwrap(); + StakeAddress::from_binary(&binary).unwrap() + } + + #[test] + fn stake_addresses_encode_mainnet_stake() { + let address = mainnet_stake_address(); + let expected = address.to_binary(); + + let mut actual = Vec::new(); + let mut encoder = minicbor::Encoder::new(&mut actual); + let result = address.encode(&mut encoder, &mut ()); + + assert!(result.is_ok()); + assert_eq!(actual.len(), 29); + assert_eq!(&actual[..], &expected[..]); + } + + #[test] + fn stake_addresses_decode_mainnet_stake() { + let binary = mainnet_stake_address().to_binary(); + + let mut decoder = minicbor::Decoder::new(&binary); + let decoded = StakeAddress::decode(&mut decoder, &mut ()).unwrap(); + + assert_eq!(decoded.network, AddressNetwork::Main); + assert_eq!( + match decoded.payload { + StakeAddressPayload::StakeKeyHash(key) => hex::encode(&key), + _ => "STAKE".to_string(), + }, + "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" + ); + } + #[test] + fn stake_addresses_round_trip_mainnet_stake() { + let binary = + hex::decode("f1558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001").unwrap(); + let original = StakeAddress::from_binary(&binary).unwrap(); + + let mut encoded = Vec::new(); + let mut encoder = minicbor::Encoder::new(&mut encoded); + original.encode(&mut encoder, &mut ()).unwrap(); + + let mut decoder = minicbor::Decoder::new(&encoded); + let decoded = StakeAddress::decode(&mut decoder, &mut ()).unwrap(); + + assert_eq!(decoded.network, AddressNetwork::Main); + assert_eq!( + match decoded.payload { + StakeAddressPayload::ScriptHash(key) => hex::encode(&key), + _ => "STAKE".to_string(), + }, + "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" + ); + } + + #[test] + fn stake_addresses_roundtrip_testnet_script() { + let original = testnet_script_address(); + + let mut encoded = Vec::new(); + let mut encoder = minicbor::Encoder::new(&mut encoded); + original.encode(&mut encoder, &mut ()).unwrap(); + + let mut decoder = minicbor::Decoder::new(&encoded); + let decoded = StakeAddress::decode(&mut decoder, &mut ()).unwrap(); + + assert_eq!(decoded.network, AddressNetwork::Test); + assert_eq!( + match decoded.payload { + StakeAddressPayload::StakeKeyHash(key) => hex::encode(&key), + _ => "SCRIPT".to_string(), + }, + "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" + ); + } + + #[test] + fn stake_addresses_decode_invalid_length() { + let bad_data = vec![0xe1, 0x00, 0x01, 0x02, 0x03]; + let mut decoder = minicbor::Decoder::new(&bad_data); + + let result = StakeAddress::decode(&mut decoder, &mut ()); + assert!(result.is_err()); + } } From 37a533449bcc140a7cfbff9088f3c4d5dc994baf Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 15 Oct 2025 14:24:09 -0700 Subject: [PATCH 13/27] Add encode/decod, add comprehensive test cases for encoding, decoding --- common/src/address.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index ccfea1c7..ff40717e 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -290,8 +290,7 @@ impl minicbor::Encode for StakeAddress { e: &mut minicbor::Encoder, _ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { - let bytes = self.to_binary(); - e.writer_mut().write_all(&bytes).map_err(|err| minicbor::encode::Error::write(err))?; + e.bytes(&self.to_binary())?; Ok(()) } } @@ -301,8 +300,9 @@ impl<'b, C> minicbor::Decode<'b, C> for StakeAddress { d: &mut minicbor::Decoder<'b>, _ctx: &mut C, ) -> Result { - StakeAddress::from_binary(d.input()) - .map_err(|e| minicbor::decode::Error::message(e.to_string())) + let bytes = d.bytes()?; + Self::from_binary(bytes) + .map_err(|_| minicbor::decode::Error::message("invalid stake address")) } } @@ -652,20 +652,30 @@ mod tests { #[test] fn stake_addresses_encode_mainnet_stake() { let address = mainnet_stake_address(); - let expected = address.to_binary(); + let binary = address.to_binary(); + + // CBOR encoding wraps the raw 29-byte stake address in a byte string: + // - 0x58: CBOR major type 2 (byte string) with 1-byte length follows + // - 0x1d: Length of 29 bytes (the stake address data) + // - [29 bytes]: The actual stake address (network header + 28-byte hash) + // Total: 31 bytes (2-byte CBOR framing + 29-byte payload) + let expected = [[0x58, 0x1d].as_slice(), &binary].concat(); let mut actual = Vec::new(); let mut encoder = minicbor::Encoder::new(&mut actual); - let result = address.encode(&mut encoder, &mut ()); + address.encode(&mut encoder, &mut ()).unwrap(); - assert!(result.is_ok()); - assert_eq!(actual.len(), 29); - assert_eq!(&actual[..], &expected[..]); + assert_eq!(actual.len(), 31); + assert_eq!(actual, expected); } #[test] fn stake_addresses_decode_mainnet_stake() { - let binary = mainnet_stake_address().to_binary(); + let binary = { + let mut v = vec![0x58, 0x1d]; + v.extend_from_slice(&mainnet_stake_address().to_binary()); + v + }; let mut decoder = minicbor::Decoder::new(&binary); let decoded = StakeAddress::decode(&mut decoder, &mut ()).unwrap(); @@ -679,6 +689,7 @@ mod tests { "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" ); } + #[test] fn stake_addresses_round_trip_mainnet_stake() { let binary = From 6ee41c86bf3c01b7f9daca1f151491b286cb2180 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 15 Oct 2025 16:35:30 -0700 Subject: [PATCH 14/27] Update common/src/address.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- common/src/address.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/address.rs b/common/src/address.rs index ff40717e..ea08322e 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -727,7 +727,7 @@ mod tests { assert_eq!(decoded.network, AddressNetwork::Test); assert_eq!( match decoded.payload { - StakeAddressPayload::StakeKeyHash(key) => hex::encode(&key), + StakeAddressPayload::ScriptHash(key) => hex::encode(&key), _ => "SCRIPT".to_string(), }, "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" From 1ae4fea69bd846ca18ee5936c92bbe95c37ee47e Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 15 Oct 2025 14:24:09 -0700 Subject: [PATCH 15/27] Add encode/decod, add comprehensive test cases for encoding, decoding --- common/src/address.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index ccfea1c7..4a70fa8e 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -290,8 +290,7 @@ impl minicbor::Encode for StakeAddress { e: &mut minicbor::Encoder, _ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { - let bytes = self.to_binary(); - e.writer_mut().write_all(&bytes).map_err(|err| minicbor::encode::Error::write(err))?; + e.bytes(&self.to_binary())?; Ok(()) } } @@ -301,8 +300,9 @@ impl<'b, C> minicbor::Decode<'b, C> for StakeAddress { d: &mut minicbor::Decoder<'b>, _ctx: &mut C, ) -> Result { - StakeAddress::from_binary(d.input()) - .map_err(|e| minicbor::decode::Error::message(e.to_string())) + let bytes = d.bytes()?; + Self::from_binary(bytes) + .map_err(|e| minicbor::decode::Error::message(format!("invalid stake address: {e}"))) } } @@ -652,20 +652,30 @@ mod tests { #[test] fn stake_addresses_encode_mainnet_stake() { let address = mainnet_stake_address(); - let expected = address.to_binary(); + let binary = address.to_binary(); + + // CBOR encoding wraps the raw 29-byte stake address in a byte string: + // - 0x58: CBOR major type 2 (byte string) with 1-byte length follows + // - 0x1d: Length of 29 bytes (the stake address data) + // - [29 bytes]: The actual stake address (network header + 28-byte hash) + // Total: 31 bytes (2-byte CBOR framing + 29-byte payload) + let expected = [[0x58, 0x1d].as_slice(), &binary].concat(); let mut actual = Vec::new(); let mut encoder = minicbor::Encoder::new(&mut actual); - let result = address.encode(&mut encoder, &mut ()); + address.encode(&mut encoder, &mut ()).unwrap(); - assert!(result.is_ok()); - assert_eq!(actual.len(), 29); - assert_eq!(&actual[..], &expected[..]); + assert_eq!(actual.len(), 31); + assert_eq!(actual, expected); } #[test] fn stake_addresses_decode_mainnet_stake() { - let binary = mainnet_stake_address().to_binary(); + let binary = { + let mut v = vec![0x58, 0x1d]; + v.extend_from_slice(&mainnet_stake_address().to_binary()); + v + }; let mut decoder = minicbor::Decoder::new(&binary); let decoded = StakeAddress::decode(&mut decoder, &mut ()).unwrap(); @@ -679,6 +689,7 @@ mod tests { "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" ); } + #[test] fn stake_addresses_round_trip_mainnet_stake() { let binary = From 6d757e56f83d3328af343d617f1c3e8eb50dea7d Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Fri, 17 Oct 2025 10:16:32 -0700 Subject: [PATCH 16/27] - Add `from_stake_key_hash` constructor for `StakeAddress` to simplify creation. - Update reward handling to use `from_stake_key_hash` with `NetworkId`. - Implement `From` for `AddressNetwork` for easier conversion. --- common/src/address.rs | 10 +++++++++- common/src/types.rs | 11 +++++++++++ modules/accounts_state/src/rewards.rs | 4 ++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 4a70fa8e..381ccddf 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -2,7 +2,7 @@ // We don't use these types in the acropolis_common crate itself #![allow(dead_code)] use crate::cip19::{VarIntDecoder, VarIntEncoder}; -use crate::types::{KeyHash, ScriptHash}; +use crate::types::{KeyHash, NetworkId, ScriptHash}; use anyhow::{anyhow, bail, Result}; use serde_with::{hex::Hex, serde_as}; @@ -214,6 +214,14 @@ impl StakeAddress { } } + /// Construct from a stake key hash + pub fn from_stake_key_hash(hash: &KeyHash, network_id: NetworkId) -> StakeAddress { + StakeAddress { + network: network_id.into(), + payload: StakeAddressPayload::StakeKeyHash(hash.to_vec()), + } + } + /// Convert to string stake1xxx format pub fn to_string(&self) -> Result { let hrp = match self.network { diff --git a/common/src/types.rs b/common/src/types.rs index 9a238195..f3dc2522 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -6,6 +6,7 @@ use crate::{ address::{Address, ShelleyAddress, StakeAddress}, protocol_params, rational_number::RationalNumber, + AddressNetwork, }; use anyhow::{anyhow, bail, Error, Result}; use bech32::{Bech32, Hrp}; @@ -25,6 +26,16 @@ pub enum NetworkId { Mainnet, } +// TODO: Would really like to consolidate NetworkId and AddressNetwork at some point +impl From for AddressNetwork { + fn from(network_id: NetworkId) -> Self { + match network_id { + NetworkId::Mainnet => AddressNetwork::Main, + NetworkId::Testnet => AddressNetwork::Test, + } + } +} + /// Protocol era #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index de7952b1..09e8dca4 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -327,7 +327,7 @@ fn calculate_spo_rewards( .with_scale(0); let costs = &fixed_cost + &margin_cost; - // Pay the delegators - split remainder in proportional to delegated stake, + // Pay the delegators - split the remainder proportional to the delegated stake, // * as it was 2 epochs ago * // You'd think this was just (pool_rewards - costs) here, but the Haskell code recalculates @@ -377,7 +377,7 @@ fn calculate_spo_rewards( // Transfer from reserves to this account rewards.push(RewardDetail { - account: spo.reward_account.clone(), + account: StakeAddress::from_stake_key_hash(hash, params.network_id.clone()), rtype: RewardType::Member, amount: to_pay, }); From 497214ec7cb7713bea2dc41fc7d91ffcfa8eeb1a Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Fri, 17 Oct 2025 16:14:21 -0700 Subject: [PATCH 17/27] Replace `KeyHash` with `StakeAddress` for consistency and unified handling across modules. - Update reward processing, delegation, and state management to use `StakeAddress`. - Simplify APIs and adjust implementations to process `StakeAddress` directly. - Refactor tests to reflect new `StakeAddress`-based structure. - Enhance error logging to include full stake address information. --- common/src/address.rs | 33 ++- common/src/stake_addresses.rs | 270 +++++++++++-------------- common/src/types.rs | 21 +- modules/accounts_state/src/rewards.rs | 28 +-- modules/accounts_state/src/snapshot.rs | 102 ++++++---- modules/accounts_state/src/state.rs | 213 ++++++++++--------- modules/accounts_state/src/verifier.rs | 2 +- modules/spo_state/src/state.rs | 21 +- 8 files changed, 379 insertions(+), 311 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 309269ab..4a7ef311 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -1,10 +1,13 @@ //! Cardano address definitions for Acropolis // We don't use these types in the acropolis_common crate itself #![allow(dead_code)] + use crate::cip19::{VarIntDecoder, VarIntEncoder}; use crate::types::{KeyHash, NetworkId, ScriptHash}; use anyhow::{anyhow, bail, Result}; use serde_with::{hex::Hex, serde_as}; +use std::borrow::Borrow; +use std::hash::{Hash, Hasher}; /// a Byron-era address #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -174,7 +177,7 @@ impl ShelleyAddress { /// Payload of a stake address #[serde_as] -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)] pub enum StakeAddressPayload { /// Stake key StakeKeyHash(#[serde_as(as = "Hex")] Vec), @@ -196,7 +199,7 @@ impl StakeAddressPayload { } /// A stake address -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeAddress { /// Network id pub network: AddressNetwork, @@ -206,6 +209,10 @@ pub struct StakeAddress { } impl StakeAddress { + pub fn new(network: AddressNetwork, payload: StakeAddressPayload) -> Self { + StakeAddress { network, payload } + } + /// Get either hash of the payload pub fn get_hash(&self) -> &[u8] { match &self.payload { @@ -292,6 +299,26 @@ impl StakeAddress { } } +impl Hash for StakeAddress { + fn hash(&self, state: &mut H) { + self.get_hash().hash(state); + } +} + +impl PartialEq for StakeAddress { + fn eq(&self, other: &Self) -> bool { + self.get_hash() == other.get_hash() + } +} + +impl Eq for StakeAddress {} + +impl Borrow<[u8]> for StakeAddress { + fn borrow(&self) -> &[u8] { + self.get_hash() + } +} + impl minicbor::Encode for StakeAddress { fn encode( &self, @@ -653,7 +680,7 @@ mod tests { fn testnet_script_address() -> StakeAddress { let binary = - hex::decode("e0558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001").unwrap(); + hex::decode("f0558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001").unwrap(); StakeAddress::from_binary(&binary).unwrap() } diff --git a/common/src/stake_addresses.rs b/common/src/stake_addresses.rs index 8658f675..76c3bf92 100644 --- a/common/src/stake_addresses.rs +++ b/common/src/stake_addresses.rs @@ -10,8 +10,8 @@ use std::{ use crate::{ math::update_value_with_delta, messages::DRepDelegationDistribution, DRepChoice, - DRepCredential, DelegatedStake, KeyHash, Lovelace, PoolLiveStakeInfo, StakeAddressDelta, - StakeCredential, Withdrawal, + DRepCredential, DelegatedStake, KeyHash, Lovelace, PoolLiveStakeInfo, StakeAddress, + StakeAddressDelta, Withdrawal, }; use anyhow::Result; use dashmap::DashMap; @@ -42,7 +42,7 @@ pub struct StakeAddressState { #[derive(Default, Debug)] pub struct StakeAddressMap { - inner: HashMap, + inner: HashMap, } impl StakeAddressMap { @@ -54,49 +54,49 @@ impl StakeAddressMap { } #[inline] - pub fn get(&self, stake_key: &K) -> Option + pub fn get(&self, stake_address: &K) -> Option where - KeyHash: Borrow, + StakeAddress: Borrow, K: Hash + Eq + ?Sized, { - self.inner.get(stake_key).cloned() + self.inner.get(stake_address).cloned() } #[inline] - pub fn get_mut(&mut self, stake_key: &K) -> Option<&mut StakeAddressState> + pub fn get_mut(&mut self, stake_address: &K) -> Option<&mut StakeAddressState> where - KeyHash: Borrow, + StakeAddress: Borrow, K: Hash + Eq + ?Sized, { - self.inner.get_mut(stake_key) + self.inner.get_mut(stake_address) } #[inline] pub fn insert( &mut self, - stake_key: KeyHash, + stake_address: StakeAddress, stake_address_state: StakeAddressState, ) -> Option { - self.inner.insert(stake_key, stake_address_state) + self.inner.insert(stake_address, stake_address_state) } #[inline] - pub fn remove(&mut self, stake_key: &KeyHash) -> Option { - self.inner.remove(stake_key) + pub fn remove(&mut self, stake_address: &StakeAddress) -> Option { + self.inner.remove(stake_address) } #[inline] - pub fn entry(&mut self, stake_key: KeyHash) -> Entry { - self.inner.entry(stake_key) + pub fn entry(&mut self, stake_address: StakeAddress) -> Entry { + self.inner.entry(stake_address) } #[inline] - pub fn values(&self) -> Values { + pub fn values(&self) -> Values { self.inner.values() } #[inline] - pub fn iter(&self) -> Iter { + pub fn iter(&self) -> Iter { self.inner.iter() } @@ -106,8 +106,8 @@ impl StakeAddressMap { } #[inline] - pub fn is_registered(&self, stake_key: &KeyHash) -> bool { - self.get(stake_key).map(|sas| sas.registered).unwrap_or(false) + pub fn is_registered(&self, stake_address: &StakeAddress) -> bool { + self.get(stake_address).map(|sas| sas.registered).unwrap_or(false) } /// Get Pool's Live Stake Info @@ -170,7 +170,7 @@ impl StakeAddressMap { .filter_map(|(stake_key, sas)| match sas.delegated_spo.as_ref() { Some(delegated_spo) => { if delegated_spo.eq(pool_operator) { - Some((stake_key.clone(), sas.utxo_value + sas.rewards)) + Some((stake_key.to_binary().clone(), sas.utxo_value + sas.rewards)) } else { None } @@ -188,10 +188,10 @@ impl StakeAddressMap { let delegators: Vec<(KeyHash, u64)> = self .inner .iter() - .filter_map(|(stake_key, sas)| match sas.delegated_drep.as_ref() { + .filter_map(|(stake_address, sas)| match sas.delegated_drep.as_ref() { Some(delegated_drep) => { if delegated_drep.eq(drep) { - Some((stake_key.clone(), sas.utxo_value)) + Some((stake_address.to_binary(), sas.utxo_value)) } else { None } @@ -212,7 +212,7 @@ impl StakeAddressMap { let mut map = HashMap::new(); for key in stake_keys { - let account = self.get(key)?; + let account = self.get(key.as_slice())?; let utxo_value = account.utxo_value; map.insert(key.clone(), utxo_value); } @@ -220,35 +220,35 @@ impl StakeAddressMap { Some(map) } - /// Map stake_keys to their total balances (utxo + rewards) - /// Return None if any of the stake_keys are not found + /// Map stake_addresses to their total balances (utxo + rewards) + /// Return None if any of the stake_addresses are not found pub fn get_accounts_balances_map( &self, - stake_keys: &[Vec], + stake_addresses: &[Vec], ) -> Option, u64>> { let mut map = HashMap::new(); - for key in stake_keys { - let account = self.get(key)?; + for address in stake_addresses { + let account = self.get(address.as_slice())?; let balance = account.utxo_value + account.rewards; - map.insert(key.clone(), balance); + map.insert(address.clone(), balance); } Some(map) } - /// Map stake_keys to their delegated DRep - /// Return None if any of the stake_keys are not found + /// Map stake_addresses to their delegated DRep + /// Return None if any of the stake_addresses are not found pub fn get_drep_delegations_map( &self, - stake_keys: &[Vec], + stake_addresses: &[Vec], ) -> Option, Option>> { let mut map = HashMap::new(); - for stake_key in stake_keys { - let account = self.get(stake_key)?; + for stake_address in stake_addresses { + let account = self.get(stake_address.as_slice())?; let maybe_drep = account.delegated_drep.clone(); - map.insert(stake_key.clone(), maybe_drep); + map.insert(stake_address.clone(), maybe_drep); } Some(map) @@ -258,19 +258,19 @@ impl StakeAddressMap { /// Return None if any of the stake_keys are not found pub fn get_accounts_utxo_values_sum(&self, stake_keys: &[Vec]) -> Option { let mut total = 0; - for key in stake_keys { - let account = self.get(key)?; + for address in stake_keys { + let account = self.get(address.as_slice())?; total += account.utxo_value; } Some(total) } - /// Sum stake_keys balances (utxo + rewards) - /// Return None if any of stake_keys are not found - pub fn get_account_balances_sum(&self, stake_keys: &[Vec]) -> Option { + /// Sum stake_addresses balances (utxo + rewards) + /// Return None if any of stake_addresses are not found + pub fn get_account_balances_sum(&self, stake_addresses: &[Vec]) -> Option { let mut total = 0; - for key in stake_keys { - let account = self.get(key)?; + for address in stake_addresses { + let account = self.get(address.as_slice())?; total += account.utxo_value + account.rewards; } Some(total) @@ -369,71 +369,64 @@ impl StakeAddressMap { } } - /// Register a stake address, with specified deposit if known + /// Register a stake address /// Return True if registered, False if already registered - pub fn register_stake_address(&mut self, credential: &StakeCredential) -> bool { - let hash = credential.get_hash(); - + pub fn register_stake_address(&mut self, stake_address: &StakeAddress) -> bool { // Stake addresses can be registered after being used in UTXOs - let sas = self.entry(hash.clone()).or_default(); + let sas = self.entry(stake_address.clone()).or_default(); if sas.registered { error!( - "Stake address hash {} registered when already registered", - hex::encode(&hash) + "Stake address {} registered when already registered", + hex::encode(stake_address.get_hash()) ); false } else { sas.registered = true; - true } } - /// Deregister a stake address, with specified refund if known - /// Return True if deregistered, False if unregistered or unknown stake key hash - pub fn deregister_stake_address(&mut self, credential: &StakeCredential) -> bool { - let hash = credential.get_hash(); - + /// Deregister a stake address + /// Return True if deregistered, False if unregistered or unknown stake address + pub fn deregister_stake_address(&mut self, stake_address: &StakeAddress) -> bool { // Check if it existed - if let Some(sas) = self.get_mut(&hash) { + if let Some(sas) = self.get_mut(stake_address) { if sas.registered { sas.registered = false; true } else { error!( - "Deregistration of unregistered stake address hash {}", - hex::encode(hash) + "Deregistration of unregistered stake address {}", + hex::encode(stake_address.get_hash()) ); false } } else { error!( - "Deregistration of unknown stake address hash {}", - hex::encode(hash) + "Deregistration of unknown stake address {}", + hex::encode(stake_address.get_hash()) ); false } } /// Record a stake delegation - pub fn record_stake_delegation(&mut self, credential: &StakeCredential, spo: &KeyHash) -> bool { - let hash = credential.get_hash(); - - if let Some(sas) = self.get_mut(&hash) { + pub fn record_stake_delegation(&mut self, stake_address: &StakeAddress, spo: &KeyHash) -> bool { + if let Some(sas) = self.get_mut(stake_address) { if sas.registered { sas.delegated_spo = Some(spo.clone()); true } else { error!( "Unregistered stake address in stake delegation: {}", - hex::encode(hash) + hex::encode(stake_address.get_hash()) ); false } } else { error!( "Unknown stake address in stake delegation: {}", - hex::encode(hash) + hex::encode(stake_address.get_hash()) ); false } @@ -442,66 +435,69 @@ impl StakeAddressMap { /// Record a drep delegation pub fn record_drep_delegation( &mut self, - credential: &StakeCredential, + stake_address: &StakeAddress, drep: &DRepChoice, ) -> bool { - let hash = credential.get_hash(); - - if let Some(sas) = self.get_mut(&hash) { + if let Some(sas) = self.get_mut(stake_address) { if sas.registered { sas.delegated_drep = Some(drep.clone()); true } else { error!( "Unregistered stake address in DRep delegation: {}", - hex::encode(hash) + hex::encode(stake_address.get_hash()) ); false } } else { error!( "Unknown stake address in drep delegation: {}", - hex::encode(hash) + hex::encode(stake_address.get_hash()) ); false } } - /// Add a reward to a reward account (by stake key hash) - pub fn add_to_reward(&mut self, account: &KeyHash, amount: Lovelace) { + /// Add a reward to a reward account (by stake address) + pub fn add_to_reward(&mut self, stake_address: &StakeAddress, amount: Lovelace) { // Get or create account entry, avoiding clone when existing - let sas = match self.get_mut(account) { + let sas = match self.get_mut(stake_address) { Some(existing) => existing, None => { - self.insert(account.clone(), StakeAddressState::default()); - self.get_mut(account).unwrap() + self.insert(stake_address.clone(), StakeAddressState::default()); + self.get_mut(stake_address).unwrap() } }; if let Err(e) = update_value_with_delta(&mut sas.rewards, amount as i64) { - error!("Adding to reward account {}: {e}", hex::encode(account)); + error!( + "Adding to reward account {}: {e}", + hex::encode(stake_address.get_hash()) + ); } } /// Stake Delta pub fn process_stake_delta(&mut self, stake_delta: &StakeAddressDelta) { - // Fold both stake key and script hashes into one - assuming the chance of - // collision is negligible - let hash = stake_delta.address.get_hash(); + // Use the full stake address directly - no need to extract hash! + let stake_address = &stake_delta.address; // Stake addresses don't need to be registered if they aren't used for // stake or drep delegation, but we need to track them in case they are later - let sas = self.entry(hash.to_vec()).or_default(); + let sas = self.entry(stake_address.clone()).or_default(); if let Err(e) = update_value_with_delta(&mut sas.utxo_value, stake_delta.delta) { - error!("Applying delta to stake hash {}: {e}", hex::encode(hash)); + error!( + "Applying delta to stake address {}: {e}", + hex::encode(stake_address.get_hash()) + ); } } /// Withdraw pub fn process_withdrawal(&mut self, withdrawal: &Withdrawal) { - let hash = withdrawal.address.get_hash(); + let stake_address = &withdrawal.address; - if let Some(sas) = self.get(hash) { + if let Some(sas) = self.get(stake_address) { // Zero withdrawals are expected, as a way to validate stake addresses (per Pi) if withdrawal.value != 0 { let mut sas = sas.clone(); @@ -509,32 +505,31 @@ impl StakeAddressMap { update_value_with_delta(&mut sas.rewards, -(withdrawal.value as i64)) { error!( - "Withdrawing from stake address {} hash {}: {e}", - withdrawal.address.to_string().unwrap_or("???".to_string()), - hex::encode(hash) + "Withdrawing from stake address {}: {e}", + hex::encode(stake_address.get_hash()) ); } else { // Update the stake address - self.insert(hash.to_vec(), sas); + self.insert(stake_address.clone(), sas); } } } else { error!( "Unknown stake address in withdrawal: {}", - withdrawal.address.to_string().unwrap_or("???".to_string()) + hex::encode(stake_address.get_hash()) ); } } /// Update reward with delta - pub fn update_reward(&mut self, account: &KeyHash, delta: i64) -> Result<()> { - let sas = self.entry(account.clone()).or_default(); + pub fn update_reward(&mut self, stake_address: &StakeAddress, delta: i64) -> Result<()> { + let sas = self.entry(stake_address.clone()).or_default(); update_value_with_delta(&mut sas.rewards, delta) } /// Update utxo value with delta - pub fn update_utxo_value(&mut self, account: &KeyHash, delta: i64) -> Result<()> { - let sas = self.entry(account.clone()).or_default(); + pub fn update_utxo_value(&mut self, stake_address: &StakeAddress, delta: i64) -> Result<()> { + let sas = self.entry(stake_address.clone()).or_default(); update_value_with_delta(&mut sas.utxo_value, delta) } } @@ -545,138 +540,119 @@ mod tests { use super::*; - const STAKE_KEY_HASH: [u8; 3] = [0x99, 0x0f, 0x00]; - const SPO_HASH: [u8; 4] = [0x01, 0x02, 0x03, 0x04]; - const DREP_HASH: [u8; 4] = [0xca, 0xfe, 0xd0, 0x0d]; + const STAKE_KEY_HASH: [u8; 28] = [0x99; 28]; // Full 28 bytes + const SPO_HASH: [u8; 28] = [0x01; 28]; // Full 28 bytes + const DREP_HASH: [u8; 28] = [0xca; 28]; // Full 28 bytes - fn create_address(hash: &[u8]) -> StakeAddress { + fn create_stake_address(hash: &[u8]) -> StakeAddress { StakeAddress { network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(hash.to_vec()), + payload: StakeAddressPayload::StakeKeyHash( + hash.to_vec().try_into().expect("Invalid hash length"), + ), } } #[test] fn test_stake_delta() { let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); // Register first - stake_addresses - .register_stake_address(&StakeCredential::AddrKeyHash(STAKE_KEY_HASH.to_vec())); + stake_addresses.register_stake_address(&stake_address); assert_eq!(stake_addresses.len(), 1); // Pass in deltas let delta = StakeAddressDelta { - address: create_address(&STAKE_KEY_HASH), + address: stake_address.clone(), delta: 42, }; stake_addresses.process_stake_delta(&delta); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().utxo_value, - 42 - ); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 42); stake_addresses.process_stake_delta(&delta); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().utxo_value, - 84 - ); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 84); } #[test] fn test_stake_delta_and_reward() { let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); // Register first - stake_addresses - .register_stake_address(&StakeCredential::AddrKeyHash(STAKE_KEY_HASH.to_vec())); + stake_addresses.register_stake_address(&stake_address); assert_eq!(stake_addresses.len(), 1); // Pass in deltas let delta = StakeAddressDelta { - address: create_address(&STAKE_KEY_HASH), + address: stake_address.clone(), delta: 42, }; stake_addresses.process_stake_delta(&delta); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().utxo_value, - 42 - ); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 42); // Reward - stake_addresses.add_to_reward(&STAKE_KEY_HASH.to_vec(), 12); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().rewards, - 12 - ); + stake_addresses.add_to_reward(&stake_address, 12); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 12); } #[test] fn test_withdrawal() { let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); // Register first - stake_addresses - .register_stake_address(&StakeCredential::AddrKeyHash(STAKE_KEY_HASH.to_vec())); + stake_addresses.register_stake_address(&stake_address); assert_eq!(stake_addresses.len(), 1); // Reward - stake_addresses.add_to_reward(&STAKE_KEY_HASH.to_vec(), 12); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().rewards, - 12 - ); + stake_addresses.add_to_reward(&stake_address, 12); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 12); // Withdraw more than reward let withdrawal = Withdrawal { - address: create_address(&STAKE_KEY_HASH), + address: stake_address.clone(), value: 24, }; stake_addresses.process_withdrawal(&withdrawal); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().rewards, - 12 - ); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 12); // Withdraw less than reward let withdrawal = Withdrawal { - address: create_address(&STAKE_KEY_HASH), + address: stake_address.clone(), value: 2, }; stake_addresses.process_withdrawal(&withdrawal); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().rewards, - 10 - ); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 10); } #[test] fn test_certs() { let mut stake_addresses = StakeAddressMap::new(); - let stake_credential = StakeCredential::AddrKeyHash(STAKE_KEY_HASH.to_vec()); + let stake_address = create_stake_address(&STAKE_KEY_HASH); // Register first - stake_addresses.register_stake_address(&stake_credential); + stake_addresses.register_stake_address(&stake_address); assert_eq!(stake_addresses.len(), 1); // Stake delegation - stake_addresses.record_stake_delegation(&stake_credential, &SPO_HASH.to_vec()); + stake_addresses.record_stake_delegation(&stake_address, &SPO_HASH.to_vec()); assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().delegated_spo, + stake_addresses.get(&stake_address).unwrap().delegated_spo, Some(SPO_HASH.to_vec()) ); // Drep delegation let drep_choice = DRepChoice::Key(DREP_HASH.to_vec()); - stake_addresses.record_drep_delegation(&stake_credential, &drep_choice); + stake_addresses.record_drep_delegation(&stake_address, &drep_choice); assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().delegated_drep, + stake_addresses.get(&stake_address).unwrap().delegated_drep, Some(drep_choice) ); // Deregister - stake_addresses.deregister_stake_address(&stake_credential); + stake_addresses.deregister_stake_address(&stake_address); assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().registered, + stake_addresses.get(&stake_address).unwrap().registered, false ); } diff --git a/common/src/types.rs b/common/src/types.rs index f3dc2522..4fd5bd11 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -6,7 +6,7 @@ use crate::{ address::{Address, ShelleyAddress, StakeAddress}, protocol_params, rational_number::RationalNumber, - AddressNetwork, + AddressNetwork, StakeAddressPayload, }; use anyhow::{anyhow, bail, Error, Result}; use bech32::{Bech32, Hrp}; @@ -169,7 +169,7 @@ pub struct StakeAddressDelta { /// Stake Address Reward change #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeRewardDelta { - pub hash: KeyHash, + pub stake_address: StakeAddress, pub delta: i64, } @@ -612,6 +612,23 @@ impl Credential { pub type StakeCredential = Credential; +impl StakeCredential { + pub fn to_stake_address(&self, network: Option) -> StakeAddress { + let payload = match self { + StakeCredential::AddrKeyHash(hash) => StakeAddressPayload::StakeKeyHash( + hash.clone().try_into().expect("Invalid hash length"), + ), + StakeCredential::ScriptHash(hash) => StakeAddressPayload::ScriptHash( + hash.clone().try_into().expect("Invalid hash length"), + ), + }; + StakeAddress::new( + network.unwrap_or(AddressNetwork::Main), + payload + ) + } +} + /// Relay single host address #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)] pub struct SingleHostAddr { diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index 09e8dca4..f246a9dc 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -59,8 +59,8 @@ pub fn calculate_rewards( staking: Arc, params: &ShelleyParams, stake_rewards: Lovelace, - registrations: &HashSet, - deregistrations: &HashSet, + registrations: &HashSet, + deregistrations: &HashSet, ) -> Result { let mut result = RewardsResult::default(); result.epoch = epoch; @@ -135,7 +135,7 @@ pub fn calculate_rewards( // Note we use the staking reward account - it could have changed pay_to_pool_reward_account = - registrations.contains(staking_spo.reward_account.get_hash()) + registrations.contains(&staking_spo.reward_account); } // There was a bug in the original node from Shelley until Allegra where if multiple SPOs @@ -237,7 +237,7 @@ fn calculate_spo_rewards( params: &ShelleyParams, staking: Arc, pay_to_pool_reward_account: bool, - deregistrations: &HashSet, + deregistrations: &HashSet, ) -> Vec { // Active stake (sigma) let pool_stake = BigDecimal::from(spo.total_stake); @@ -338,7 +338,9 @@ fn calculate_spo_rewards( let mut delegators_paid: usize = 0; if !to_delegators.is_zero() { let total_stake = BigDecimal::from(spo.total_stake); - for (hash, stake) in &spo.delegators { + for (delegator_stake_address, stake) in &spo.delegators { + + let delegator_stake_address_hash = delegator_stake_address.get_hash(); let proportion = BigDecimal::from(stake) / &total_stake; // and hence how much of the total reward they get @@ -346,38 +348,38 @@ fn calculate_spo_rewards( let to_pay = reward.with_scale(0).to_u64().unwrap_or(0); debug!("Reward stake {stake} -> proportion {proportion} of SPO rewards {to_delegators} -> {to_pay} to hash {}", - hex::encode(hash)); + hex::encode(delegator_stake_address_hash)); // Pool owners don't get member rewards (seems unfair!) - if spo.pool_owners.contains(hash) { + if spo.pool_owners.contains(&delegator_stake_address_hash.to_vec()) { debug!( "Skipping pool owner reward account {}, losing {to_pay}", - hex::encode(hash) + hex::encode(delegator_stake_address_hash) ); continue; } // Check pool's reward address - removing e1 prefix - if spo.reward_account.get_hash() == *hash { + if spo.reward_account.get_hash() == delegator_stake_address_hash { debug!( "Skipping pool reward account {}, losing {to_pay}", - hex::encode(hash) + hex::encode(delegator_stake_address_hash) ); continue; } // Check if it was deregistered between staking and now - if deregistrations.contains(hash) { + if deregistrations.contains(delegator_stake_address) { info!( "Recently deregistered member account {}, losing {to_pay}", - hex::encode(hash) + hex::encode(delegator_stake_address_hash) ); continue; } // Transfer from reserves to this account rewards.push(RewardDetail { - account: StakeAddress::from_stake_key_hash(hash, params.network_id.clone()), + account: delegator_stake_address.clone(), rtype: RewardType::Member, amount: to_pay, }); diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 316b4e78..d1bcbee3 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -13,7 +13,7 @@ use tracing::{debug, info}; #[derive(Debug, Default)] pub struct SnapshotSPO { /// List of delegator stake addresses and amounts - pub delegators: Vec<(KeyHash, Lovelace)>, + pub delegators: Vec<(StakeAddress, Lovelace)>, /// Total stake delegated pub total_stake: Lovelace, @@ -87,16 +87,14 @@ impl Snapshot { // Check if the reward account from two epochs ago is still registered // TODO should spo.reward_account be a StakeAddress to begin with? - let two_previous_reward_account_is_registered = match two_previous_snapshot - .spos - .get(spo_id) - { - Some(old_spo) => { - let spo_reward_hash = old_spo.reward_account.get_hash(); - stake_addresses.get(spo_reward_hash).map(|sas| sas.registered).unwrap_or(false) - } - None => false, - }; + let two_previous_reward_account_is_registered = + match two_previous_snapshot.spos.get(spo_id) { + Some(old_spo) => stake_addresses + .get(&old_spo.reward_account) + .map(|sas| sas.registered) + .unwrap_or(false), + None => false, + }; // Add the new one snapshot.spos.insert( @@ -118,13 +116,13 @@ impl Snapshot { // Scan all stake addresses and post to their delegated SPO's list // Note this is 'active stake', for reward calculations, and does include rewards let mut total_stake: Lovelace = 0; - for (hash, sas) in stake_addresses.iter() { + for (stake_address, sas) in stake_addresses.iter() { let active_stake = sas.utxo_value + sas.rewards; if sas.registered && active_stake > 0 { if let Some(spo_id) = &sas.delegated_spo { if let Some(snap_spo) = snapshot.spos.get_mut(spo_id) { - snap_spo.delegators.push((hash.clone(), active_stake)); + snap_spo.delegators.push((stake_address.clone(), active_stake)); snap_spo.total_stake += active_stake; } else { // SPO has retired - this stake is simply ignored @@ -132,7 +130,7 @@ impl Snapshot { epoch, "SPO {} for hash {} retired? Ignored", hex::encode(spo_id), - hex::encode(hash) + hex::encode(stake_address.get_hash()) ); continue; } @@ -175,7 +173,7 @@ impl Snapshot { .delegators .iter() .filter_map(|(addr, amount)| { - if addr_set.contains(addr) { + if addr_set.contains(&addr.get_hash().to_vec()) { Some(*amount) } else { None @@ -187,20 +185,38 @@ impl Snapshot { #[cfg(test)] mod tests { + use super::*; + use acropolis_common::AddressNetwork::Main; + use acropolis_common::{StakeAddress, StakeAddressPayload}; use acropolis_common::stake_addresses::StakeAddressState; - use super::*; + // Helper function to create stake addresses for testing + fn create_test_stake_address(id: u8) -> StakeAddress { + let mut hash = vec![0u8; 28]; + hash[0] = id; + StakeAddress { + network: Main, + payload: StakeAddressPayload::StakeKeyHash(hash.try_into().expect("Invalid hash length")), + } + } + + // Helper function to create SPO key hashes for testing + fn create_test_spo_hash(id: u8) -> KeyHash { + let mut hash = vec![0u8; 28]; + hash[0] = id; + hash + } #[test] fn get_stake_snapshot_counts_stake_and_ignores_unregistered_undelegated_and_zero_values() { - let spo1: KeyHash = vec![0x01]; - let spo2: KeyHash = vec![0x02]; + let spo1 = create_test_spo_hash(0x01); + let spo2 = create_test_spo_hash(0x02); - let addr1: KeyHash = vec![0x11]; - let addr2: KeyHash = vec![0x12]; - let addr3: KeyHash = vec![0x13]; - let addr4: KeyHash = vec![0x14]; - let addr5: KeyHash = vec![0x15]; + let addr1 = create_test_stake_address(0x11); + let addr2 = create_test_stake_address(0x12); + let addr3 = create_test_stake_address(0x13); + let addr4 = create_test_stake_address(0x14); + let addr5 = create_test_stake_address(0x15); let mut stake_addresses: StakeAddressMap = StakeAddressMap::new(); stake_addresses.insert( @@ -268,15 +284,15 @@ mod tests { let spod1 = snapshot.spos.get(&spo1).unwrap(); assert_eq!(spod1.delegators.len(), 1); - let (hash1, value1) = &spod1.delegators[0]; - assert_eq!(*hash1, addr1); + let (stake_address1, value1) = &spod1.delegators[0]; + assert_eq!(*stake_address1, addr1); assert_eq!(*value1, 42); assert_eq!(spod1.total_stake, 42); let spod2 = snapshot.spos.get(&spo2).unwrap(); assert_eq!(spod2.delegators.len(), 1); - let (hash2, value2) = &spod2.delegators[0]; - assert_eq!(*hash2, addr2); + let (stake_address2, value2) = &spod2.delegators[0]; + assert_eq!(*stake_address2, addr2); assert_eq!(*value2, 99); assert_eq!(spod2.total_stake, 99); } @@ -284,12 +300,12 @@ mod tests { #[test] fn get_stake_delegated_to_spo_by_addresses_when_some_match_is_correct() { let mut snapshot = Snapshot::default(); - let spo1: KeyHash = vec![0x01]; + let spo1 = create_test_spo_hash(0x01); - let addr1: KeyHash = vec![0x11]; - let addr2: KeyHash = vec![0x12]; - let addr3: KeyHash = vec![0x13]; - let addr4: KeyHash = vec![0x14]; + let addr1 = create_test_stake_address(0x11); + let addr2 = create_test_stake_address(0x12); + let addr3 = create_test_stake_address(0x13); + let addr4 = create_test_stake_address(0x14); snapshot.spos.insert( spo1.clone(), @@ -304,7 +320,12 @@ mod tests { }, ); - let addresses = vec![addr2, addr3, addr4]; + // Extract key hashes from stake addresses for the API call + let addresses = vec![ + addr2.get_hash().to_vec(), + addr3.get_hash().to_vec(), + addr4.get_hash().to_vec(), + ]; let result = snapshot.get_stake_delegated_to_spo_by_addresses(&spo1, &addresses); assert_eq!(result, 500); } @@ -312,10 +333,10 @@ mod tests { #[test] fn get_stake_delegated_to_spo_by_addresses_with_no_match_is_0() { let mut snapshot = Snapshot::default(); - let spo1: KeyHash = vec![0x01]; + let spo1 = create_test_spo_hash(0x01); - let addr1: KeyHash = vec![0x11]; - let addr_x: KeyHash = vec![0x99]; + let addr1 = create_test_stake_address(0x11); + let addr_x = create_test_stake_address(0x99); snapshot.spos.insert( spo1.clone(), @@ -326,7 +347,8 @@ mod tests { }, ); - let addresses = vec![addr_x]; + // Extract key hash from stake address for the API call + let addresses = vec![addr_x.get_hash().to_vec()]; let result = snapshot.get_stake_delegated_to_spo_by_addresses(&spo1, &addresses); assert_eq!(result, 0); } @@ -334,7 +356,7 @@ mod tests { #[test] fn get_stake_delegated_to_spo_by_addresses_with_unknown_spo_is_0() { let snapshot = Snapshot::default(); - let spo_unknown: KeyHash = vec![0xFF]; + let spo_unknown = create_test_spo_hash(0xFF); let result = snapshot.get_stake_delegated_to_spo_by_addresses(&spo_unknown, &[]); assert_eq!(result, 0); } @@ -342,8 +364,8 @@ mod tests { #[test] fn get_stake_delegated_to_spo_by_addresses_with_empty_addresses_is_0() { let mut snapshot = Snapshot::default(); - let spo1: KeyHash = vec![0x01]; - let addr1: KeyHash = vec![0x11]; + let spo1 = create_test_spo_hash(0x01); + let addr1 = create_test_stake_address(0x11); snapshot.spos.insert( spo1.clone(), diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 37b5f2cf..bc84e5f3 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -15,7 +15,8 @@ use acropolis_common::{ stake_addresses::{StakeAddressMap, StakeAddressState}, BlockInfo, DRepChoice, DRepCredential, DelegatedStake, InstantaneousRewardSource, InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, PoolLiveStakeInfo, - PoolRegistration, Pot, SPORewards, StakeCredential, StakeRewardDelta, TxCertificate, + PoolRegistration, Pot, SPORewards, StakeAddress, StakeCredential, StakeRewardDelta, + TxCertificate, }; use anyhow::Result; use imbl::OrdMap; @@ -78,11 +79,11 @@ pub enum RegistrationChangeKind { /// Registration change on a stake address #[derive(Debug, Clone)] pub struct RegistrationChange { - /// Stake address hash - address: KeyHash, + /// Stake address (full address, not just hash) + pub address: StakeAddress, /// Change type - kind: RegistrationChangeKind, + pub kind: RegistrationChangeKind, } /// Overall state - stored per block @@ -114,10 +115,10 @@ pub struct State { previous_protocol_parameters: Option, /// Pool refunds to apply next epoch (list of reward accounts to refund to) - pool_refunds: Vec, + pool_refunds: Vec, /// Stake address refunds to apply next epoch - stake_refunds: Vec<(KeyHash, Lovelace)>, + stake_refunds: Vec<(StakeAddress, Lovelace)>, /// MIRs to pay next epoch mirs: Vec, @@ -135,7 +136,7 @@ pub struct State { impl State { /// Get the stake address state for a give stake key pub fn get_stake_state(&self, stake_key: &KeyHash) -> Option { - self.stake_addresses.lock().unwrap().get(stake_key) + self.stake_addresses.lock().unwrap().get(stake_key.as_slice()) } /// Get the current pot balances @@ -145,11 +146,11 @@ impl State { /// Get maximum pool size /// ( total_supply - reserves) / nopt (from protocol parameters) - /// Return None if it is before Shelly Era + /// Return None if it is before Shelley Era pub fn get_optimal_pool_sizing(&self) -> Option { // Get Shelley parameters, silently return if too early in the chain so no // rewards to calculate - let shelly_params = match &self.protocol_parameters { + let shelley_params = match &self.protocol_parameters { Some(ProtocolParams { shelley: Some(sp), .. }) => sp, @@ -158,8 +159,8 @@ impl State { .clone(); let total_supply = - shelly_params.max_lovelace_supply - self.epoch_snapshots.mark.pots.reserves; - let nopt = shelly_params.protocol_params.stake_pool_target_num as u64; + shelley_params.max_lovelace_supply - self.epoch_snapshots.mark.pots.reserves; + let nopt = shelley_params.protocol_params.stake_pool_target_num as u64; Some(OptimalPoolSizing { total_supply, nopt }) } @@ -357,8 +358,8 @@ impl State { // huge map. If the snapshot was ever changed to store addresses in a way where an // individual could be looked up, this could be simplified - but you still need to // handle the Shelley bug part! - let mut registrations: HashSet = HashSet::new(); - let mut deregistrations: HashSet = HashSet::new(); + let mut registrations: HashSet = HashSet::new(); + let mut deregistrations: HashSet = HashSet::new(); Self::apply_registration_changes( &self.epoch_snapshots.set.registration_changes, &mut registrations, @@ -385,8 +386,12 @@ impl State { ); if tracing::enabled!(Level::DEBUG) { - registrations.iter().for_each(|k| debug!("Registration {}", hex::encode(k))); - deregistrations.iter().for_each(|k| debug!("Deregistration {}", hex::encode(k))); + registrations + .iter() + .for_each(|addr| debug!("Registration {}", hex::encode(addr.get_hash()))); + deregistrations + .iter() + .for_each(|addr| debug!("Deregistration {}", hex::encode(addr.get_hash()))); } // Calculate reward payouts for previous epoch @@ -414,13 +419,13 @@ impl State { Ok(reward_deltas) } - /// Apply a registration change set to a deregistrations list + /// Apply a registration change set to registration/deregistration lists /// registrations gets all registrations still in effect at the end of the changes /// deregistrations likewise for net deregistrations fn apply_registration_changes( changes: &Vec, - registrations: &mut HashSet, - deregistrations: &mut HashSet, + registrations: &mut HashSet, + deregistrations: &mut HashSet, ) { for change in changes { match change.kind { @@ -473,19 +478,19 @@ impl State { } // Send them their deposits back - for keyhash in refunds { + for stake_address in refunds { // If their reward account has been deregistered, it goes to Treasury let mut stake_addresses = self.stake_addresses.lock().unwrap(); - if stake_addresses.is_registered(&keyhash) { + if stake_addresses.is_registered(&stake_address) { reward_deltas.push(StakeRewardDelta { - hash: keyhash.clone(), + stake_address: stake_address.clone(), delta: deposit as i64, }); - stake_addresses.add_to_reward(&keyhash, deposit); + stake_addresses.add_to_reward(&stake_address, deposit); } else { warn!( "SPO reward account {} deregistered - paying refund to treasury", - hex::encode(keyhash) + hex::encode(stake_address.get_hash()) ); self.pots.treasury += deposit; } @@ -510,13 +515,13 @@ impl State { } // Send them their deposits back - for (keyhash, deposit) in refunds { + for (stake_address, deposit) in refunds { let mut stake_addresses = self.stake_addresses.lock().unwrap(); reward_deltas.push(StakeRewardDelta { - hash: keyhash.clone(), + stake_address: stake_address.clone(), // Extract hash for delta delta: deposit as i64, }); - stake_addresses.add_to_reward(&keyhash, deposit); + stake_addresses.add_to_reward(&stake_address, deposit); self.pots.deposits -= deposit; } @@ -549,19 +554,22 @@ impl State { // Transfer to (in theory also from) stake addresses from (to) a pot let mut total_value: u64 = 0; for (credential, value) in deltas.iter() { - let hash = credential.get_hash(); + let stake_address = credential.to_stake_address(None); // Need to convert credential to address // Get old stake address state, or create one let mut stake_addresses = self.stake_addresses.lock().unwrap(); - let sas = stake_addresses.entry(hash.clone()).or_default(); + let sas = stake_addresses.entry(stake_address.clone()).or_default(); // Add to this one reward_deltas.push(StakeRewardDelta { - hash: hash.clone(), + stake_address: stake_address.clone(), delta: *value, }); if let Err(e) = update_value_with_delta(&mut sas.rewards, *value) { - error!("MIR to stake hash {}: {e}", hex::encode(hash)); + error!( + "MIR to stake address {}: {e}", + hex::encode(stake_address.get_hash()) + ); } // Update the source @@ -604,7 +612,7 @@ impl State { stake_addresses.generate_spdd() } - /// Derive the DRep Delegation Distribution (SPDD) - the total amount + /// Derive the DRep Delegation Distribution (DRDD) - the total amount /// delegated to each DRep, including the special "abstain" and "no confidence" dreps. pub fn generate_drdd(&self) -> DRepDelegationDistribution { let stake_addresses = self.stake_addresses.lock().unwrap(); @@ -662,7 +670,7 @@ impl State { rewards .iter() .map(|reward| StakeRewardDelta { - hash: reward.account.get_hash().to_vec(), + stake_address: reward.account.clone(), delta: reward.amount as i64, }) .collect::>(), @@ -676,8 +684,7 @@ impl State { let mut stake_addresses = self.stake_addresses.lock().unwrap(); for (_, rewards) in reward_result.rewards { for reward in rewards { - stake_addresses - .add_to_reward(&reward.account.get_hash().to_vec(), reward.amount); + stake_addresses.add_to_reward(&reward.account, reward.amount); } } @@ -773,13 +780,12 @@ impl State { self.pool_refunds = Vec::new(); for id in &spo_msg.retired_spos { if let Some(retired_spo) = new_spos.get(id) { - let key_hash = &retired_spo.reward_account.get_hash(); debug!( "SPO {} has retired - refunding their deposit to {}", hex::encode(id), - hex::encode(key_hash) + hex::encode(retired_spo.reward_account.get_hash()) ); - self.pool_refunds.push(key_hash.to_vec()); + self.pool_refunds.push(retired_spo.reward_account.clone()); // Store full StakeAddress } // Schedule to retire - we need them to still be in place when we count @@ -793,9 +799,11 @@ impl State { /// Register a stake address, with a specified deposit if known fn register_stake_address(&mut self, credential: &StakeCredential, deposit: Option) { + let stake_address = credential.to_stake_address(None); // Convert credential to full address + // Stake addresses can be registered after being used in UTXOs let mut stake_addresses = self.stake_addresses.lock().unwrap(); - if stake_addresses.register_stake_address(credential) { + if stake_addresses.register_stake_address(&stake_address) { // Account for the deposit let deposit = match deposit { Some(deposit) => deposit, @@ -814,16 +822,18 @@ impl State { // Add to registration changes self.current_epoch_registration_changes.lock().unwrap().push(RegistrationChange { - address: credential.get_hash(), + address: stake_address, kind: RegistrationChangeKind::Registered, }); } /// Deregister a stake address, with specified refund if known fn deregister_stake_address(&mut self, credential: &StakeCredential, refund: Option) { + let stake_address = credential.to_stake_address(None); // Convert credential to full address + // Check if it existed let mut stake_addresses = self.stake_addresses.lock().unwrap(); - if stake_addresses.deregister_stake_address(credential) { + if stake_addresses.deregister_stake_address(&stake_address) { // Account for the deposit, if registered before let deposit = match refund { Some(deposit) => deposit, @@ -838,9 +848,12 @@ impl State { }; self.pots.deposits -= deposit; + // Schedule refund + self.stake_refunds.push((stake_address.clone(), deposit)); + // Add to registration changes self.current_epoch_registration_changes.lock().unwrap().push(RegistrationChange { - address: credential.get_hash(), + address: stake_address, kind: RegistrationChangeKind::Deregistered, }); } @@ -852,8 +865,9 @@ impl State { /// Record a stake delegation fn record_stake_delegation(&mut self, credential: &StakeCredential, spo: &KeyHash) { + let stake_address = credential.to_stake_address(None); // Convert credential to full address let mut stake_addresses = self.stake_addresses.lock().unwrap(); - stake_addresses.record_stake_delegation(credential, spo); + stake_addresses.record_stake_delegation(&stake_address, spo); } /// Handle an MoveInstantaneousReward (pre-Conway only) @@ -864,8 +878,9 @@ impl State { /// record a drep delegation fn record_drep_delegation(&mut self, credential: &StakeCredential, drep: &DRepChoice) { + let stake_address = credential.to_stake_address(None); // Convert credential to full address let mut stake_addresses = self.stake_addresses.lock().unwrap(); - stake_addresses.record_drep_delegation(credential, drep); + stake_addresses.record_drep_delegation(&stake_address, drep); } /// Handle TxCertificates @@ -985,19 +1000,27 @@ mod tests { StakeRegistrationAndVoteDelegation, VoteDelegation, Withdrawal, }; - const STAKE_KEY_HASH: [u8; 3] = [0x99, 0x0f, 0x00]; - const DREP_HASH: [u8; 4] = [0xca, 0xfe, 0xd0, 0x0d]; - + // Helper to create a StakeAddress from a byte slice fn create_address(hash: &[u8]) -> StakeAddress { + let mut full_hash = vec![0u8; 28]; + full_hash[..hash.len().min(28)].copy_from_slice(&hash[..hash.len().min(28)]); StakeAddress { network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash(hash.to_vec()), + payload: StakeAddressPayload::StakeKeyHash(full_hash), } } + fn create_stake_credential(hash: &[u8]) -> StakeCredential { + StakeCredential::AddrKeyHash(hash.to_vec()) + } + + const STAKE_KEY_HASH: [u8; 3] = [0x99, 0x0f, 0x00]; + const DREP_HASH: [u8; 4] = [0xca, 0xfe, 0xd0, 0x0d]; + #[test] fn stake_addresses_initialise_to_first_delta_and_increment_subsequently() { let mut state = State::default(); + let stake_address = create_address(&STAKE_KEY_HASH); // Register first state.register_stake_address(&StakeCredential::AddrKeyHash(STAKE_KEY_HASH.to_vec()), None); @@ -1010,7 +1033,7 @@ mod tests { // Pass in deltas let msg = StakeAddressDeltasMessage { deltas: vec![StakeAddressDelta { - address: create_address(&STAKE_KEY_HASH), + address: stake_address.clone(), delta: 42, }], }; @@ -1019,20 +1042,14 @@ mod tests { { let stake_addresses = state.stake_addresses.lock().unwrap(); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().utxo_value, - 42 - ); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 42); } state.handle_stake_deltas(&msg).unwrap(); { let stake_addresses = state.stake_addresses.lock().unwrap(); - assert_eq!( - stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap().utxo_value, - 84 - ); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 84); } } @@ -1092,20 +1109,20 @@ mod tests { .unwrap(); // Delegate - let addr1: KeyHash = vec![0x11]; - let cred1 = Credential::AddrKeyHash(addr1.clone()); + let addr1 = create_address(&[0x11]); + let cred1 = Credential::AddrKeyHash(addr1.get_hash().to_vec()); state.register_stake_address(&cred1, None); state.record_stake_delegation(&cred1, &spo1); - let addr2: KeyHash = vec![0x12]; - let cred2 = Credential::AddrKeyHash(addr2.clone()); + let addr2 = create_address(&[0x12]); + let cred2 = Credential::AddrKeyHash(addr2.get_hash().to_vec()); state.register_stake_address(&cred2, None); state.record_stake_delegation(&cred2, &spo2); // Put some value in let msg1 = StakeAddressDeltasMessage { deltas: vec![StakeAddressDelta { - address: create_address(&addr1), + address: addr1.clone(), delta: 42, }], }; @@ -1114,7 +1131,7 @@ mod tests { let msg2 = StakeAddressDeltasMessage { deltas: vec![StakeAddressDelta { - address: create_address(&addr2), + address: addr2.clone(), delta: 21, }], }; @@ -1205,16 +1222,18 @@ mod tests { #[test] fn mir_transfers_to_stake_addresses() { let mut state = State::default(); + let stake_address = create_address(&STAKE_KEY_HASH); + let stake_credential = create_stake_credential(stake_address.get_hash()); // Bootstrap with some in reserves state.pots.reserves = 100; // Set up one stake address - state.register_stake_address(&StakeCredential::AddrKeyHash(STAKE_KEY_HASH.to_vec()), None); + state.register_stake_address(&stake_credential, None); let msg = StakeAddressDeltasMessage { deltas: vec![StakeAddressDelta { - address: create_address(&STAKE_KEY_HASH), + address: stake_address.clone(), delta: 99, }], }; @@ -1223,7 +1242,7 @@ mod tests { { let stake_addresses = state.stake_addresses.lock().unwrap(); assert_eq!(stake_addresses.len(), 1); - let sas = stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap(); + let sas = stake_addresses.get(&stake_address).unwrap(); assert_eq!(sas.utxo_value, 99); assert_eq!(sas.rewards, 0); } @@ -1232,8 +1251,8 @@ mod tests { let mir = MoveInstantaneousReward { source: InstantaneousRewardSource::Reserves, target: InstantaneousRewardTarget::StakeCredentials(vec![ - (Credential::AddrKeyHash(STAKE_KEY_HASH.to_vec()), 47), - (Credential::AddrKeyHash(STAKE_KEY_HASH.to_vec()), -5), + (stake_credential.clone(), 47), + (stake_credential, -5), ]), }; @@ -1244,7 +1263,7 @@ mod tests { assert_eq!(state.pots.deposits, 2_000_000); // Paid deposit let stake_addresses = state.stake_addresses.lock().unwrap(); - let sas = stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap(); + let sas = stake_addresses.get(&stake_address).unwrap(); assert_eq!(sas.utxo_value, 99); assert_eq!(sas.rewards, 42); } @@ -1252,15 +1271,17 @@ mod tests { #[test] fn withdrawal_transfers_from_stake_addresses() { let mut state = State::default(); + let stake_address = create_address(&STAKE_KEY_HASH); + let stake_credential = create_stake_credential(stake_address.get_hash()); // Bootstrap with some in reserves state.pots.reserves = 100; // Set up one stake address - state.register_stake_address(&StakeCredential::AddrKeyHash(STAKE_KEY_HASH.to_vec()), None); + state.register_stake_address(&stake_credential, None); let msg = StakeAddressDeltasMessage { deltas: vec![StakeAddressDelta { - address: create_address(&STAKE_KEY_HASH), + address: stake_address.clone(), delta: 99, }], }; @@ -1271,7 +1292,7 @@ mod tests { let stake_addresses = state.stake_addresses.lock().unwrap(); assert_eq!(stake_addresses.len(), 1); - let sas = stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap(); + let sas = stake_addresses.get(&stake_address).unwrap(); assert_eq!(sas.utxo_value, 99); assert_eq!(sas.rewards, 0); } @@ -1279,29 +1300,26 @@ mod tests { // Send in a MIR reserves->42->stake let mir = MoveInstantaneousReward { source: InstantaneousRewardSource::Reserves, - target: InstantaneousRewardTarget::StakeCredentials(vec![( - Credential::AddrKeyHash(STAKE_KEY_HASH.to_vec()), - 42, - )]), + target: InstantaneousRewardTarget::StakeCredentials(vec![(stake_credential, 42)]), }; state.handle_mir(&mir).unwrap(); let diffs = state.pay_mirs(); assert_eq!(state.pots.reserves, 58); assert_eq!(diffs.len(), 1); - assert_eq!(diffs[0].hash, STAKE_KEY_HASH.to_vec()); + assert_eq!(diffs[0].stake_address.get_hash(), stake_address.get_hash()); assert_eq!(diffs[0].delta, 42); { let stake_addresses = state.stake_addresses.lock().unwrap(); - let sas = stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap(); + let sas = stake_addresses.get(&stake_address).unwrap(); assert_eq!(sas.rewards, 42); } // Withdraw most of it let withdrawals = WithdrawalsMessage { withdrawals: vec![Withdrawal { - address: create_address(&STAKE_KEY_HASH), + address: stake_address.clone(), value: 39, }], }; @@ -1309,7 +1327,7 @@ mod tests { state.handle_withdrawals(&withdrawals).unwrap(); let stake_addresses = state.stake_addresses.lock().unwrap(); - let sas = stake_addresses.get(&STAKE_KEY_HASH.to_vec()).unwrap(); + let sas = stake_addresses.get(&stake_address).unwrap(); assert_eq!(sas.rewards, 3); } @@ -1355,39 +1373,44 @@ mod tests { ], }); - let spo1 = vec![0x01]; - let spo2 = vec![0x02]; - let spo3 = vec![0x03]; - let spo4 = vec![0x04]; + let spo1 = create_address(&[0x01]); + let spo2 = create_address(&[0x02]); + let spo3 = create_address(&[0x03]); + let spo4 = create_address(&[0x04]); + + let spo1_credential = create_stake_credential(spo1.get_hash()); + let spo2_credential = create_stake_credential(spo2.get_hash()); + let spo3_credential = create_stake_credential(spo3.get_hash()); + let spo4_credential = create_stake_credential(spo4.get_hash()); let certificates = vec![ // register the first two SPOs separately from their delegation TxCertificate::Registration(Registration { - credential: Credential::AddrKeyHash(spo1.clone()), + credential: spo1_credential.clone(), deposit: 1, }), TxCertificate::Registration(Registration { - credential: Credential::AddrKeyHash(spo2.clone()), + credential: spo2_credential.clone(), deposit: 1, }), TxCertificate::VoteDelegation(VoteDelegation { - credential: Credential::AddrKeyHash(spo1.clone()), + credential: spo1_credential.clone(), drep: DRepChoice::Key(DREP_HASH.to_vec()), }), TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { - credential: Credential::AddrKeyHash(spo2.clone()), - operator: spo1.clone(), + credential: spo2_credential.clone(), + operator: spo1.get_hash().to_vec(), drep: DRepChoice::Script(DREP_HASH.to_vec()), }), TxCertificate::StakeRegistrationAndVoteDelegation(StakeRegistrationAndVoteDelegation { - credential: Credential::AddrKeyHash(spo3.clone()), + credential: spo3_credential.clone(), drep: DRepChoice::Abstain, deposit: 1, }), TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( StakeRegistrationAndStakeAndVoteDelegation { - credential: Credential::AddrKeyHash(spo4.clone()), - operator: spo1.clone(), + credential: spo4_credential, + operator: spo1.get_hash().to_vec(), drep: DRepChoice::NoConfidence, deposit: 1, }, @@ -1398,19 +1421,19 @@ mod tests { let deltas = vec![ StakeAddressDelta { - address: create_address(&spo1), + address: spo1, delta: 100, }, StakeAddressDelta { - address: create_address(&spo2), + address: spo2, delta: 1_000, }, StakeAddressDelta { - address: create_address(&spo3), + address: spo3, delta: 10_000, }, StakeAddressDelta { - address: create_address(&spo4), + address: spo4, delta: 100_000, }, ]; diff --git a/modules/accounts_state/src/verifier.rs b/modules/accounts_state/src/verifier.rs index fed7caec..35096d87 100644 --- a/modules/accounts_state/src/verifier.rs +++ b/modules/accounts_state/src/verifier.rs @@ -161,7 +161,7 @@ impl Verifier { _ => continue, }; - let Ok(stake_address) = StakeAddress::from_binary(&account[1..]) else { + let Ok(stake_address) = StakeAddress::from_binary(&account) else { error!("Bad stake address in {path} for address: {address} - skipping"); continue; }; diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index a63c15c0..45d734bc 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -442,29 +442,29 @@ impl State { return; }; let mut stake_addresses = stake_addresses.lock().unwrap(); - stake_addresses.register_stake_address(credential); + stake_addresses.register_stake_address(&credential.to_stake_address(None)); } fn deregister_stake_address(&mut self, credential: &StakeCredential) { let Some(stake_addresses) = self.stake_addresses.as_ref() else { return; }; - let hash = credential.get_hash(); + let stake_address = credential.to_stake_address(None); let mut stake_addresses = stake_addresses.lock().unwrap(); - let old_spo = stake_addresses.get(&hash).map(|s| s.delegated_spo.clone()).flatten(); + let old_spo = stake_addresses.get(&stake_address).map(|s| s.delegated_spo.clone()).flatten(); - if stake_addresses.deregister_stake_address(credential) { + if stake_addresses.deregister_stake_address(&stake_address) { // update historical_spos if let Some(historical_spos) = self.historical_spos.as_mut() { if let Some(old_spo) = old_spo.as_ref() { // remove delegators from old_spo if let Some(historical_spo) = historical_spos.get_mut(old_spo) { - if let Some(removed) = historical_spo.remove_delegator(&hash) { + if let Some(removed) = historical_spo.remove_delegator(&credential.get_hash()) { if !removed { error!( "Historical SPO state for {} does not contain delegator {}", hex::encode(old_spo), - hex::encode(&hash) + hex::encode(&credential.get_hash()) ); } } @@ -481,10 +481,11 @@ impl State { return; }; let hash = credential.get_hash(); + let stake_address = credential.to_stake_address(None); let mut stake_addresses = stake_addresses.lock().unwrap(); - let old_spo = stake_addresses.get(&hash).map(|s| s.delegated_spo.clone()).flatten(); + let old_spo = stake_addresses.get(&stake_address).map(|s| s.delegated_spo.clone()).flatten(); - if stake_addresses.record_stake_delegation(credential, spo) { + if stake_addresses.record_stake_delegation(&stake_address, spo) { // update historical_spos if let Some(historical_spos) = self.historical_spos.as_mut() { // Remove old delegator @@ -661,8 +662,8 @@ impl State { // Handle deltas for delta in reward_deltas_msg.deltas.iter() { let mut stake_addresses = stake_addresses.lock().unwrap(); - if let Err(e) = stake_addresses.update_reward(&delta.hash, delta.delta) { - error!("Updating reward account {}: {e}", hex::encode(&delta.hash)); + if let Err(e) = stake_addresses.update_reward(&delta.stake_address, delta.delta) { + error!("Updating reward account {}: {e}", hex::encode(&delta.stake_address.get_hash())); } } From f503455ceca007c4b906660e0c65f94f7a628eb8 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Sat, 18 Oct 2025 09:57:43 -0700 Subject: [PATCH 18/27] Refactor `stake_addresses` module: API improvements, test coverage expansion, and minor documentation fixes - Rename `stake_addresses` function parameters for clarity and consistency (`stake_addresses` -> `stake_keys`). - Adjust test cases to include comprehensive workflows for registration, delegation, withdrawal, and state updates. - Add new utility test modules to validate delegation and reward processes. - Minor improvements in comment documentation for public functions. --- common/src/stake_addresses.rs | 1087 ++++++++++++++++++++++++--- modules/accounts_state/src/state.rs | 4 +- modules/drep_state/src/state.rs | 17 +- 3 files changed, 987 insertions(+), 121 deletions(-) diff --git a/common/src/stake_addresses.rs b/common/src/stake_addresses.rs index 76c3bf92..981edcc5 100644 --- a/common/src/stake_addresses.rs +++ b/common/src/stake_addresses.rs @@ -29,7 +29,7 @@ pub struct StakeAddressState { /// Total value in UTXO addresses pub utxo_value: u64, - /// Value in reward account + /// Value in a reward account pub rewards: u64, /// SPO ID they are delegated to ("operator" ID) @@ -224,14 +224,14 @@ impl StakeAddressMap { /// Return None if any of the stake_addresses are not found pub fn get_accounts_balances_map( &self, - stake_addresses: &[Vec], + stake_keys: &[Vec], ) -> Option, u64>> { let mut map = HashMap::new(); - for address in stake_addresses { - let account = self.get(address.as_slice())?; + for stake_key in stake_keys { + let account = self.get(stake_key.as_slice())?; let balance = account.utxo_value + account.rewards; - map.insert(address.clone(), balance); + map.insert(stake_key.clone(), balance); } Some(map) @@ -241,14 +241,14 @@ impl StakeAddressMap { /// Return None if any of the stake_addresses are not found pub fn get_drep_delegations_map( &self, - stake_addresses: &[Vec], + stake_keys: &[Vec], ) -> Option, Option>> { let mut map = HashMap::new(); - for stake_address in stake_addresses { - let account = self.get(stake_address.as_slice())?; + for stake_key in stake_keys { + let account = self.get(stake_key.as_slice())?; let maybe_drep = account.delegated_drep.clone(); - map.insert(stake_address.clone(), maybe_drep); + map.insert(stake_key.clone(), maybe_drep); } Some(map) @@ -267,10 +267,10 @@ impl StakeAddressMap { /// Sum stake_addresses balances (utxo + rewards) /// Return None if any of stake_addresses are not found - pub fn get_account_balances_sum(&self, stake_addresses: &[Vec]) -> Option { + pub fn get_account_balances_sum(&self, stake_keys: &[Vec]) -> Option { let mut total = 0; - for address in stake_addresses { - let account = self.get(address.as_slice())?; + for stake_key in stake_keys { + let account = self.get(stake_key.as_slice())?; total += account.utxo_value + account.rewards; } Some(total) @@ -279,7 +279,7 @@ impl StakeAddressMap { /// Derive the Stake Pool Delegation Distribution (SPDD) - a map of total stake values /// (both with and without rewards) for each active SPO /// And Stake Pool Reward State (rewards and delegators_count for each pool) - /// Key of returned map is the SPO 'operator' ID + /// Key of the returned map is the SPO 'operator' ID pub fn generate_spdd(&self) -> BTreeMap { // Shareable Dashmap with referenced keys let spo_stakes = DashMap::::new(); @@ -540,120 +540,991 @@ mod tests { use super::*; - const STAKE_KEY_HASH: [u8; 28] = [0x99; 28]; // Full 28 bytes - const SPO_HASH: [u8; 28] = [0x01; 28]; // Full 28 bytes - const DREP_HASH: [u8; 28] = [0xca; 28]; // Full 28 bytes + const STAKE_KEY_HASH: [u8; 28] = [0x99; 28]; + const STAKE_KEY_HASH_2: [u8; 28] = [0xaa; 28]; + const STAKE_KEY_HASH_3: [u8; 28] = [0xbb; 28]; + + const SPO_HASH: [u8; 28] = [0x01; 28]; + const SPO_HASH_2: [u8; 28] = [0x02; 28]; + const DREP_HASH: [u8; 28] = [0xca; 28]; fn create_stake_address(hash: &[u8]) -> StakeAddress { - StakeAddress { - network: AddressNetwork::Main, - payload: StakeAddressPayload::StakeKeyHash( + StakeAddress::new( + AddressNetwork::Main, + StakeAddressPayload::StakeKeyHash( hash.to_vec().try_into().expect("Invalid hash length"), ), + ) + } + + mod registration_tests { + use super::*; + + #[test] + fn test_register_success() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + assert!(stake_addresses.register_stake_address(&stake_address)); + assert_eq!(stake_addresses.len(), 1); + assert!(stake_addresses.get(&stake_address).unwrap().registered); + } + + #[test] + fn test_double_registration_fails() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + assert!(stake_addresses.register_stake_address(&stake_address)); + assert!(!stake_addresses.register_stake_address(&stake_address)); + assert_eq!(stake_addresses.len(), 1); + } + + #[test] + fn test_deregister_success() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); + assert!(stake_addresses.deregister_stake_address(&stake_address)); + assert!(!stake_addresses.get(&stake_address).unwrap().registered); + } + + #[test] + fn test_deregister_unregistered_fails() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + // Create an entry but don't register + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: stake_address.clone(), + delta: 100, + }); + + assert!(!stake_addresses.deregister_stake_address(&stake_address)); + } + + #[test] + fn test_deregister_unknown_fails() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + assert!(!stake_addresses.deregister_stake_address(&stake_address)); + } + + #[test] + fn test_stake_address_lifecycle() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + // Register + assert!(stake_addresses.register_stake_address(&stake_address)); + + // Delegate + stake_addresses.record_stake_delegation(&stake_address, &SPO_HASH.to_vec()); + let drep_choice = DRepChoice::Key(DREP_HASH.to_vec()); + stake_addresses.record_drep_delegation(&stake_address, &drep_choice); + + // Deregister + assert!(stake_addresses.deregister_stake_address(&stake_address)); + assert!(!stake_addresses.get(&stake_address).unwrap().registered); } } - #[test] - fn test_stake_delta() { - let mut stake_addresses = StakeAddressMap::new(); - let stake_address = create_stake_address(&STAKE_KEY_HASH); + mod delegation_tests { + use super::*; - // Register first - stake_addresses.register_stake_address(&stake_address); - assert_eq!(stake_addresses.len(), 1); + #[test] + fn test_spo_delegation_success() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); - // Pass in deltas - let delta = StakeAddressDelta { - address: stake_address.clone(), - delta: 42, - }; - stake_addresses.process_stake_delta(&delta); - assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 42); - stake_addresses.process_stake_delta(&delta); - assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 84); + stake_addresses.register_stake_address(&stake_address); + assert!(stake_addresses.record_stake_delegation(&stake_address, &SPO_HASH.to_vec())); + assert_eq!( + stake_addresses.get(&stake_address).unwrap().delegated_spo, + Some(SPO_HASH.to_vec()) + ); + } + + #[test] + fn test_drep_delegation_success() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); + let drep_choice = DRepChoice::Key(DREP_HASH.to_vec()); + assert!(stake_addresses.record_drep_delegation(&stake_address, &drep_choice)); + assert_eq!( + stake_addresses.get(&stake_address).unwrap().delegated_drep, + Some(drep_choice) + ); + } + + #[test] + fn test_delegation_requires_registration() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + // Test unknown address + assert!(!stake_addresses.record_stake_delegation(&stake_address, &SPO_HASH.to_vec())); + assert!(!stake_addresses + .record_drep_delegation(&stake_address, &DRepChoice::Key(DREP_HASH.to_vec()))); + + // Create an unregistered entry with UTXO value + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: stake_address.clone(), + delta: 100, + }); + + // Delegation should still fail for unregistered address + assert!(!stake_addresses.record_stake_delegation(&stake_address, &SPO_HASH.to_vec())); + assert!(!stake_addresses + .record_drep_delegation(&stake_address, &DRepChoice::Key(DREP_HASH.to_vec()))); + } + + #[test] + fn test_re_delegation() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); + + // First SPO delegation + stake_addresses.record_stake_delegation(&stake_address, &SPO_HASH.to_vec()); + assert_eq!( + stake_addresses.get(&stake_address).unwrap().delegated_spo, + Some(SPO_HASH.to_vec()) + ); + + // Re-delegate to different pool + stake_addresses.record_stake_delegation(&stake_address, &SPO_HASH_2.to_vec()); + assert_eq!( + stake_addresses.get(&stake_address).unwrap().delegated_spo, + Some(SPO_HASH_2.to_vec()) + ); + + // First DRep delegation + stake_addresses.record_drep_delegation(&stake_address, &DRepChoice::Abstain); + assert_eq!( + stake_addresses.get(&stake_address).unwrap().delegated_drep, + Some(DRepChoice::Abstain) + ); + + // DRep re-delegation + stake_addresses.record_drep_delegation(&stake_address, &DRepChoice::NoConfidence); + assert_eq!( + stake_addresses.get(&stake_address).unwrap().delegated_drep, + Some(DRepChoice::NoConfidence) + ); + } } - #[test] - fn test_stake_delta_and_reward() { - let mut stake_addresses = StakeAddressMap::new(); - let stake_address = create_stake_address(&STAKE_KEY_HASH); + mod stake_delta_tests { + use super::*; - // Register first - stake_addresses.register_stake_address(&stake_address); - assert_eq!(stake_addresses.len(), 1); + #[test] + fn test_positive_delta_accumulates() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); - // Pass in deltas - let delta = StakeAddressDelta { - address: stake_address.clone(), - delta: 42, - }; - stake_addresses.process_stake_delta(&delta); - assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 42); + stake_addresses.register_stake_address(&stake_address); + + let delta = StakeAddressDelta { + address: stake_address.clone(), + delta: 42, + }; + stake_addresses.process_stake_delta(&delta); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 42); + + stake_addresses.process_stake_delta(&delta); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 84); + } + + #[test] + fn test_negative_delta_reduces() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: stake_address.clone(), + delta: 100, + }); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: stake_address.clone(), + delta: -30, + }); + + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 70); + } + + #[test] + fn test_negative_delta_underflow_prevented() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); - // Reward - stake_addresses.add_to_reward(&stake_address, 12); - assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 12); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: stake_address.clone(), + delta: 50, + }); + + // Try to subtract more than available + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: stake_address.clone(), + delta: -100, + }); + + // Value should remain unchanged after error + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 50); + } } - #[test] - fn test_withdrawal() { - let mut stake_addresses = StakeAddressMap::new(); - let stake_address = create_stake_address(&STAKE_KEY_HASH); + mod reward_tests { + use super::*; - // Register first - stake_addresses.register_stake_address(&stake_address); - assert_eq!(stake_addresses.len(), 1); + #[test] + fn test_utxo_and_rewards_tracked_independently() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); - // Reward - stake_addresses.add_to_reward(&stake_address, 12); - assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 12); + stake_addresses.register_stake_address(&stake_address); - // Withdraw more than reward - let withdrawal = Withdrawal { - address: stake_address.clone(), - value: 24, - }; - stake_addresses.process_withdrawal(&withdrawal); - assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 12); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: stake_address.clone(), + delta: 42, + }); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 42); - // Withdraw less than reward - let withdrawal = Withdrawal { - address: stake_address.clone(), - value: 2, - }; - stake_addresses.process_withdrawal(&withdrawal); - assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 10); - } - - #[test] - fn test_certs() { - let mut stake_addresses = StakeAddressMap::new(); - let stake_address = create_stake_address(&STAKE_KEY_HASH); - - // Register first - stake_addresses.register_stake_address(&stake_address); - assert_eq!(stake_addresses.len(), 1); - - // Stake delegation - stake_addresses.record_stake_delegation(&stake_address, &SPO_HASH.to_vec()); - assert_eq!( - stake_addresses.get(&stake_address).unwrap().delegated_spo, - Some(SPO_HASH.to_vec()) - ); - - // Drep delegation - let drep_choice = DRepChoice::Key(DREP_HASH.to_vec()); - stake_addresses.record_drep_delegation(&stake_address, &drep_choice); - assert_eq!( - stake_addresses.get(&stake_address).unwrap().delegated_drep, - Some(drep_choice) - ); - - // Deregister - stake_addresses.deregister_stake_address(&stake_address); - assert_eq!( - stake_addresses.get(&stake_address).unwrap().registered, - false - ); + stake_addresses.add_to_reward(&stake_address, 12); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 12); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 42); + } + + #[test] + fn test_add_to_reward() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); + stake_addresses.add_to_reward(&stake_address, 100); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 100); + + stake_addresses.add_to_reward(&stake_address, 50); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 150); + } + + #[test] + fn test_update_reward_positive_delta() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + assert!(stake_addresses.update_reward(&stake_address, 100).is_ok()); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 100); + } + + #[test] + fn test_update_reward_negative_delta() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.update_reward(&stake_address, 100).unwrap(); + assert!(stake_addresses.update_reward(&stake_address, -50).is_ok()); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 50); + } + + #[test] + fn test_update_reward_underflow() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.update_reward(&stake_address, 50).unwrap(); + + let result = stake_addresses.update_reward(&stake_address, -100); + assert!(result.is_err()); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 50); + } + + #[test] + fn test_update_reward_creates_entry_if_missing() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + assert!(stake_addresses.update_reward(&stake_address, 100).is_ok()); + assert_eq!(stake_addresses.len(), 1); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 100); + } + } + + mod withdrawal_tests { + use super::*; + + #[test] + fn test_withdrawal_success() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); + stake_addresses.add_to_reward(&stake_address, 100); + + let withdrawal = Withdrawal { + address: stake_address.clone(), + value: 40, + }; + stake_addresses.process_withdrawal(&withdrawal); + + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 60); + } + + #[test] + fn test_withdrawal_prevents_underflow() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); + stake_addresses.add_to_reward(&stake_address, 12); + + // Withdraw more than reward (should be prevented) + let withdrawal = Withdrawal { + address: stake_address.clone(), + value: 24, + }; + stake_addresses.process_withdrawal(&withdrawal); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 12); + + // Withdraw less than reward (should succeed) + let withdrawal = Withdrawal { + address: stake_address.clone(), + value: 2, + }; + stake_addresses.process_withdrawal(&withdrawal); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 10); + } + + #[test] + fn test_zero_withdrawal() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&stake_address); + stake_addresses.add_to_reward(&stake_address, 100); + + let withdrawal = Withdrawal { + address: stake_address.clone(), + value: 0, + }; + + stake_addresses.process_withdrawal(&withdrawal); + assert_eq!(stake_addresses.get(&stake_address).unwrap().rewards, 100); + } + + #[test] + fn test_withdrawal_unknown_address() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + let withdrawal = Withdrawal { + address: stake_address.clone(), + value: 10, + }; + + // Should log error but not panic + stake_addresses.process_withdrawal(&withdrawal); + } + } + + mod update_tests { + use super::*; + + #[test] + fn test_update_utxo_value_positive_delta() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + assert!(stake_addresses.update_utxo_value(&stake_address, 500).is_ok()); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 500); + } + + #[test] + fn test_update_utxo_value_negative_delta() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.update_utxo_value(&stake_address, 500).unwrap(); + assert!(stake_addresses.update_utxo_value(&stake_address, -200).is_ok()); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 300); + } + + #[test] + fn test_update_utxo_value_underflow() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.update_utxo_value(&stake_address, 100).unwrap(); + + let result = stake_addresses.update_utxo_value(&stake_address, -200); + assert!(result.is_err()); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 100); + } + + #[test] + fn test_update_utxo_value_creates_entry_if_missing() { + let mut stake_addresses = StakeAddressMap::new(); + let stake_address = create_stake_address(&STAKE_KEY_HASH); + + assert!(stake_addresses.update_utxo_value(&stake_address, 500).is_ok()); + assert_eq!(stake_addresses.len(), 1); + assert_eq!(stake_addresses.get(&stake_address).unwrap().utxo_value, 500); + } + } + + mod distribution_tests { + use super::*; + + #[test] + fn test_generate_spdd_single_pool() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + stake_addresses.record_stake_delegation(&addr1, &SPO_HASH.to_vec()); + stake_addresses.record_stake_delegation(&addr2, &SPO_HASH.to_vec()); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.add_to_reward(&addr1, 50); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + stake_addresses.add_to_reward(&addr2, 100); + + let spdd = stake_addresses.generate_spdd(); + + let pool_stake = spdd.get(&SPO_HASH.to_vec()).unwrap(); + assert_eq!(pool_stake.active, 3000); // utxo only + assert_eq!(pool_stake.live, 3150); // utxo + rewards + assert_eq!(pool_stake.active_delegators_count, 2); + } + + #[test] + fn test_generate_spdd_multiple_pools() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + stake_addresses.record_stake_delegation(&addr1, &SPO_HASH.to_vec()); + stake_addresses.record_stake_delegation(&addr2, &SPO_HASH_2.to_vec()); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + + let spdd = stake_addresses.generate_spdd(); + + assert_eq!(spdd.len(), 2); + assert_eq!(spdd.get(&SPO_HASH.to_vec()).unwrap().active, 1000); + assert_eq!(spdd.get(&SPO_HASH_2.to_vec()).unwrap().active, 2000); + } + + #[test] + fn test_generate_spdd_no_delegations() { + let mut stake_addresses = StakeAddressMap::new(); + let addr1 = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + + let spdd = stake_addresses.generate_spdd(); + assert!(spdd.is_empty()); + } + + #[test] + fn test_generate_drdd_with_special_choices() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + let addr3 = create_stake_address(&STAKE_KEY_HASH_3); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + stake_addresses.register_stake_address(&addr3); + + stake_addresses.record_drep_delegation(&addr1, &DRepChoice::Abstain); + stake_addresses.record_drep_delegation(&addr2, &DRepChoice::NoConfidence); + stake_addresses.record_drep_delegation(&addr3, &DRepChoice::Key(DREP_HASH.to_vec())); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.add_to_reward(&addr1, 50); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + stake_addresses.add_to_reward(&addr2, 100); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr3.clone(), + delta: 3000, + }); + stake_addresses.add_to_reward(&addr3, 150); + + let dreps = vec![(DRepCredential::AddrKeyHash(DREP_HASH.to_vec()), 500)]; + let drdd = stake_addresses.generate_drdd(&dreps); + + assert_eq!(drdd.abstain, 1050); // 1000 + 50 + assert_eq!(drdd.no_confidence, 2100); // 2000 + 100 + + let drep_cred = DRepCredential::AddrKeyHash(DREP_HASH.to_vec()); + let drep_stake = drdd + .dreps + .iter() + .find(|(cred, _)| cred == &drep_cred) + .map(|(_, stake)| *stake) + .unwrap(); + + assert_eq!(drep_stake, 3650); // 3000 + 150 + 500 deposit + } + } + + mod pool_query_tests { + use super::*; + + #[test] + fn test_get_pool_live_stake_info() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + stake_addresses.record_stake_delegation(&addr1, &SPO_HASH.to_vec()); + stake_addresses.record_stake_delegation(&addr2, &SPO_HASH_2.to_vec()); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.add_to_reward(&addr1, 50); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + stake_addresses.add_to_reward(&addr2, 100); + + let info = stake_addresses.get_pool_live_stake_info(&SPO_HASH.to_vec()); + + assert_eq!(info.live_stake, 1050); // utxo + rewards for pool 1 + assert_eq!(info.live_delegators, 1); + assert_eq!(info.total_live_stakes, 3000); // total utxo across all addresses + } + + #[test] + fn test_get_pools_live_stakes() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + stake_addresses.record_stake_delegation(&addr1, &SPO_HASH.to_vec()); + stake_addresses.record_stake_delegation(&addr2, &SPO_HASH_2.to_vec()); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + + let pools = vec![SPO_HASH.to_vec(), SPO_HASH_2.to_vec()]; + let stakes = stake_addresses.get_pools_live_stakes(&pools); + + assert_eq!(stakes, vec![1000, 2000]); + } + + #[test] + fn test_get_pool_delegators() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + stake_addresses.record_stake_delegation(&addr1, &SPO_HASH.to_vec()); + stake_addresses.record_stake_delegation(&addr2, &SPO_HASH.to_vec()); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.add_to_reward(&addr1, 50); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + + let delegators = stake_addresses.get_pool_delegators(&SPO_HASH.to_vec()); + + assert_eq!(delegators.len(), 2); + assert!(delegators.iter().any(|(_, stake)| *stake == 1050)); + assert!(delegators.iter().any(|(_, stake)| *stake == 2000)); + } + } + + mod account_utxo_query_tests { + use super::*; + + #[test] + fn test_get_accounts_utxo_values_map_success() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.add_to_reward(&addr1, 500); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + + let keys = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let map = stake_addresses.get_accounts_utxo_values_map(&keys).unwrap(); + + assert_eq!(map.len(), 2); + assert_eq!(map.get(addr1.get_hash()).copied().unwrap(), 1000); + assert_eq!(map.get(addr2.get_hash()).copied().unwrap(), 2000); + } + + #[test] + fn test_get_accounts_utxo_values_map_missing_account() { + let mut stake_addresses = StakeAddressMap::new(); + let addr1 = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + + let keys = vec![addr1.get_hash().to_vec(), STAKE_KEY_HASH_2.to_vec()]; + + assert!(stake_addresses.get_accounts_utxo_values_map(&keys).is_none()); + } + + #[test] + fn test_get_accounts_utxo_values_map_empty_list() { + let stake_addresses = StakeAddressMap::new(); + + let keys: Vec> = vec![]; + let map = stake_addresses.get_accounts_utxo_values_map(&keys).unwrap(); + + assert!(map.is_empty()); + } + + #[test] + fn test_get_accounts_utxo_values_sum_success() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.add_to_reward(&addr1, 500); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + + let keys = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let sum = stake_addresses.get_accounts_utxo_values_sum(&keys).unwrap(); + + assert_eq!(sum, 3000); + } + + #[test] + fn test_get_accounts_utxo_values_sum_missing_account() { + let mut stake_addresses = StakeAddressMap::new(); + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + + let keys = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + + assert!(stake_addresses.get_accounts_utxo_values_sum(&keys).is_none()); + } + + #[test] + fn test_get_accounts_utxo_values_sum_empty_list() { + let stake_addresses = StakeAddressMap::new(); + + let keys: Vec> = vec![]; + let sum = stake_addresses.get_accounts_utxo_values_sum(&keys).unwrap(); + + assert_eq!(sum, 0); + } + } + + mod account_balance_query_tests { + use super::*; + + #[test] + fn test_get_accounts_balances_map_includes_rewards() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.add_to_reward(&addr1, 100); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + + let addresses = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let map = stake_addresses.get_accounts_balances_map(&addresses).unwrap(); + + assert_eq!(map.len(), 2); + assert_eq!(map.get(addr1.get_hash()).copied().unwrap(), 1100); + assert_eq!(map.get(addr2.get_hash()).copied().unwrap(), 2000); + } + + #[test] + fn test_get_accounts_balances_map_missing_account() { + let mut stake_addresses = StakeAddressMap::new(); + let addr1 = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + + let addresses = vec![addr1.to_binary(), STAKE_KEY_HASH_2.to_vec()]; + + assert!(stake_addresses.get_accounts_balances_map(&addresses).is_none()); + } + + #[test] + fn test_get_accounts_balances_map_empty_list() { + let stake_addresses = StakeAddressMap::new(); + + let addresses: Vec> = vec![]; + let map = stake_addresses.get_accounts_balances_map(&addresses).unwrap(); + + assert!(map.is_empty()); + } + + #[test] + fn test_get_account_balances_sum_includes_rewards() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.add_to_reward(&addr1, 100); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + + let addresses = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let sum = stake_addresses.get_account_balances_sum(&addresses).unwrap(); + + assert_eq!(sum, 3100); + } + + #[test] + fn test_get_account_balances_sum_missing_account() { + let mut stake_addresses = StakeAddressMap::new(); + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + + let addresses = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + + assert!(stake_addresses.get_account_balances_sum(&addresses).is_none()); + } + + #[test] + fn test_get_account_balances_sum_empty_list() { + let stake_addresses = StakeAddressMap::new(); + + let addresses = vec![]; + let sum = stake_addresses.get_account_balances_sum(&addresses).unwrap(); + + assert_eq!(sum, 0); + } + } + + mod drep_query_tests { + use super::*; + + #[test] + fn test_get_drep_delegations_map_various_choices() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + let addr3 = create_stake_address(&STAKE_KEY_HASH_3); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + stake_addresses.register_stake_address(&addr3); + + stake_addresses.record_drep_delegation(&addr1, &DRepChoice::Abstain); + stake_addresses.record_drep_delegation(&addr2, &DRepChoice::Key(DREP_HASH.to_vec())); + + let addresses = vec![ + addr1.get_hash().to_vec(), + addr2.get_hash().to_vec(), + addr3.get_hash().to_vec(), + ]; + let map = stake_addresses.get_drep_delegations_map(&addresses).unwrap(); + + assert_eq!(map.len(), 3); + assert_eq!( + map.get(addr1.get_hash()).unwrap(), + &Some(DRepChoice::Abstain) + ); + assert_eq!( + map.get(addr2.get_hash()).unwrap(), + &Some(DRepChoice::Key(DREP_HASH.to_vec())) + ); + assert_eq!(map.get(addr3.get_hash()).unwrap(), &None); + } + + #[test] + fn test_get_drep_delegations_map_missing_account() { + let mut stake_addresses = StakeAddressMap::new(); + let addr1 = create_stake_address(&STAKE_KEY_HASH); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.record_drep_delegation(&addr1, &DRepChoice::NoConfidence); + + let addresses = vec![addr1.to_binary(), STAKE_KEY_HASH_2.to_vec()]; + + assert!(stake_addresses.get_drep_delegations_map(&addresses).is_none()); + } + + #[test] + fn test_get_drep_delegations_map_empty_list() { + let stake_addresses = StakeAddressMap::new(); + + let addresses: Vec> = vec![]; + let map = stake_addresses.get_drep_delegations_map(&addresses).unwrap(); + + assert!(map.is_empty()); + } + + #[test] + fn test_get_drep_delegators() { + let mut stake_addresses = StakeAddressMap::new(); + + let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); + let addr3 = create_stake_address(&STAKE_KEY_HASH_3); + + stake_addresses.register_stake_address(&addr1); + stake_addresses.register_stake_address(&addr2); + stake_addresses.register_stake_address(&addr3); + + let drep_choice = DRepChoice::Key(DREP_HASH.to_vec()); + stake_addresses.record_drep_delegation(&addr1, &drep_choice); + stake_addresses.record_drep_delegation(&addr2, &drep_choice); + stake_addresses.record_drep_delegation(&addr3, &DRepChoice::Abstain); + + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr1.clone(), + delta: 1000, + }); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr2.clone(), + delta: 2000, + }); + stake_addresses.process_stake_delta(&StakeAddressDelta { + address: addr3.clone(), + delta: 3000, + }); + + let delegators = stake_addresses.get_drep_delegators(&drep_choice); + + assert_eq!(delegators.len(), 2); + assert!(delegators.iter().any(|(_, stake)| *stake == 1000)); + assert!(delegators.iter().any(|(_, stake)| *stake == 2000)); + } } } diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index bc84e5f3..6188f4fc 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -222,8 +222,8 @@ impl State { /// Map stake_keys to their delegated DRep pub fn get_drep_delegations_map( &self, - stake_keys: &[Vec], - ) -> Option, Option>> { + stake_keys: &[KeyHash], + ) -> Option>> { let stake_addresses = self.stake_addresses.lock().ok()?; // If lock fails, return None stake_addresses.get_drep_delegations_map(stake_keys) } diff --git a/modules/drep_state/src/state.rs b/modules/drep_state/src/state.rs index e21cf98d..b7165ced 100644 --- a/modules/drep_state/src/state.rs +++ b/modules/drep_state/src/state.rs @@ -1,15 +1,10 @@ //! Acropolis DRepState: State storage -use acropolis_common::{ - messages::{Message, StateQuery, StateQueryResponse}, - queries::{ - accounts::{AccountsStateQuery, AccountsStateQueryResponse, DEFAULT_ACCOUNTS_QUERY_TOPIC}, - get_query_topic, - governance::{DRepActionUpdate, DRepUpdateEvent, VoteRecord}, - }, - Anchor, Credential, DRepChoice, DRepCredential, Lovelace, StakeCredential, TxCertificate, - TxHash, Voter, VotingProcedures, -}; +use acropolis_common::{messages::{Message, StateQuery, StateQueryResponse}, queries::{ + accounts::{AccountsStateQuery, AccountsStateQueryResponse, DEFAULT_ACCOUNTS_QUERY_TOPIC}, + get_query_topic, + governance::{DRepActionUpdate, DRepUpdateEvent, VoteRecord}, +}, Anchor, Credential, DRepChoice, DRepCredential, KeyHash, Lovelace, StakeCredential, TxCertificate, TxHash, Voter, VotingProcedures}; use anyhow::{anyhow, Result}; use caryatid_sdk::Context; use serde_with::serde_as; @@ -475,7 +470,7 @@ impl State { delegators: Vec<(&StakeCredential, &DRepChoice)>, ) -> Result<()> { let stake_keys: Vec<_> = delegators.iter().map(|(sc, _)| sc.get_hash()).collect(); - let stake_key_to_input: HashMap<_, _> = delegators + let stake_key_to_input: HashMap = delegators .iter() .zip(&stake_keys) .map(|((sc, drep), key)| (key.clone(), (*sc, *drep))) From 529bc07c20380b7a1345264ea2b14f5c23259101 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Mon, 20 Oct 2025 13:57:23 -0700 Subject: [PATCH 19/27] Replace hex-encoded reward account logging with direct `StakeAddress` display and improve `StakeAddress` implementation - Update logging to use `StakeAddress` directly instead of `hex::encode(...get_hash())` for better readability. - Implement `Display` trait for `StakeAddress` to simplify its usage in logs and messages. - Adjust `PartialEq` implementation: include `network` and `payload` for accuracy. - Refactor related tests and code to align with the updated `StakeAddress`. --- common/src/address.rs | 25 ++++++++++------- common/src/stake_addresses.rs | 37 ++++++++------------------ modules/accounts_state/src/rewards.rs | 23 +++++++--------- modules/accounts_state/src/snapshot.rs | 9 ++++--- modules/accounts_state/src/verifier.rs | 8 +++--- 5 files changed, 45 insertions(+), 57 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 4a7ef311..51028bda 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -7,6 +7,7 @@ use crate::types::{KeyHash, NetworkId, ScriptHash}; use anyhow::{anyhow, bail, Result}; use serde_with::{hex::Hex, serde_as}; use std::borrow::Borrow; +use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; /// a Byron-era address @@ -199,7 +200,7 @@ impl StakeAddressPayload { } /// A stake address -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Eq, serde::Serialize, serde::Deserialize)] pub struct StakeAddress { /// Network id pub network: AddressNetwork, @@ -299,26 +300,32 @@ impl StakeAddress { } } -impl Hash for StakeAddress { - fn hash(&self, state: &mut H) { - self.get_hash().hash(state); +impl Display for StakeAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_string().unwrap()) } } -impl PartialEq for StakeAddress { - fn eq(&self, other: &Self) -> bool { - self.get_hash() == other.get_hash() +impl Hash for StakeAddress { + fn hash(&self, state: &mut H) { + self.network.hash(state); + std::mem::discriminant(&self.payload).hash(state); + self.get_hash().hash(state); } } -impl Eq for StakeAddress {} - impl Borrow<[u8]> for StakeAddress { fn borrow(&self) -> &[u8] { self.get_hash() } } +impl PartialEq for StakeAddress { + fn eq(&self, other: &Self) -> bool { + self.network == other.network && self.payload == other.payload + } +} + impl minicbor::Encode for StakeAddress { fn encode( &self, diff --git a/common/src/stake_addresses.rs b/common/src/stake_addresses.rs index 981edcc5..afc22a4f 100644 --- a/common/src/stake_addresses.rs +++ b/common/src/stake_addresses.rs @@ -377,7 +377,7 @@ impl StakeAddressMap { if sas.registered { error!( "Stake address {} registered when already registered", - hex::encode(stake_address.get_hash()) + stake_address ); false } else { @@ -397,15 +397,12 @@ impl StakeAddressMap { } else { error!( "Deregistration of unregistered stake address {}", - hex::encode(stake_address.get_hash()) + stake_address ); false } } else { - error!( - "Deregistration of unknown stake address {}", - hex::encode(stake_address.get_hash()) - ); + error!("Deregistration of unknown stake address {}", stake_address); false } } @@ -419,14 +416,14 @@ impl StakeAddressMap { } else { error!( "Unregistered stake address in stake delegation: {}", - hex::encode(stake_address.get_hash()) + stake_address ); false } } else { error!( "Unknown stake address in stake delegation: {}", - hex::encode(stake_address.get_hash()) + stake_address ); false } @@ -445,14 +442,14 @@ impl StakeAddressMap { } else { error!( "Unregistered stake address in DRep delegation: {}", - hex::encode(stake_address.get_hash()) + stake_address ); false } } else { error!( "Unknown stake address in drep delegation: {}", - hex::encode(stake_address.get_hash()) + stake_address ); false } @@ -470,10 +467,7 @@ impl StakeAddressMap { }; if let Err(e) = update_value_with_delta(&mut sas.rewards, amount as i64) { - error!( - "Adding to reward account {}: {e}", - hex::encode(stake_address.get_hash()) - ); + error!("Adding to reward account {}: {e}", stake_address); } } @@ -486,10 +480,7 @@ impl StakeAddressMap { // stake or drep delegation, but we need to track them in case they are later let sas = self.entry(stake_address.clone()).or_default(); if let Err(e) = update_value_with_delta(&mut sas.utxo_value, stake_delta.delta) { - error!( - "Applying delta to stake address {}: {e}", - hex::encode(stake_address.get_hash()) - ); + error!("Applying delta to stake address {}: {e}", stake_address); } } @@ -504,20 +495,14 @@ impl StakeAddressMap { if let Err(e) = update_value_with_delta(&mut sas.rewards, -(withdrawal.value as i64)) { - error!( - "Withdrawing from stake address {}: {e}", - hex::encode(stake_address.get_hash()) - ); + error!("Withdrawing from stake address {}: {e}", stake_address); } else { // Update the stake address self.insert(stake_address.clone(), sas); } } } else { - error!( - "Unknown stake address in withdrawal: {}", - hex::encode(stake_address.get_hash()) - ); + error!("Unknown stake address in withdrawal: {}", stake_address); } } diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index f246a9dc..56c50fb5 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -128,14 +128,10 @@ pub fn calculate_rewards( // Also, to handle the early Shelley timing bug, we allow it if it was registered // during the current epoch if !pay_to_pool_reward_account { - debug!( - "Checking old reward account {}", - hex::encode(&staking_spo.reward_account.get_hash()) - ); + debug!("Checking old reward account {}", staking_spo.reward_account); // Note we use the staking reward account - it could have changed - pay_to_pool_reward_account = - registrations.contains(&staking_spo.reward_account); + pay_to_pool_reward_account = registrations.contains(&staking_spo.reward_account); } // There was a bug in the original node from Shelley until Allegra where if multiple SPOs @@ -156,7 +152,7 @@ pub fn calculate_rewards( warn!("Shelley shared reward account bug: Dropping reward to {} in favour of {} on shared account {}", hex::encode(&operator_id), hex::encode(&other_id), - hex::encode(&staking_spo.reward_account.get_hash())); + staking_spo.reward_account); break; } } @@ -332,14 +328,13 @@ fn calculate_spo_rewards( // You'd think this was just (pool_rewards - costs) here, but the Haskell code recalculates // the margin without the relative_owner_stake term !? - // Note keeping fractional part, which is non-obvious + // Note keeping the fractional part, which is non-obvious let to_delegators = (&pool_rewards - &fixed_cost) * (BigDecimal::one() - &margin); let mut total_paid: u64 = 0; let mut delegators_paid: usize = 0; if !to_delegators.is_zero() { let total_stake = BigDecimal::from(spo.total_stake); for (delegator_stake_address, stake) in &spo.delegators { - let delegator_stake_address_hash = delegator_stake_address.get_hash(); let proportion = BigDecimal::from(stake) / &total_stake; @@ -348,13 +343,13 @@ fn calculate_spo_rewards( let to_pay = reward.with_scale(0).to_u64().unwrap_or(0); debug!("Reward stake {stake} -> proportion {proportion} of SPO rewards {to_delegators} -> {to_pay} to hash {}", - hex::encode(delegator_stake_address_hash)); + delegator_stake_address); // Pool owners don't get member rewards (seems unfair!) if spo.pool_owners.contains(&delegator_stake_address_hash.to_vec()) { debug!( "Skipping pool owner reward account {}, losing {to_pay}", - hex::encode(delegator_stake_address_hash) + delegator_stake_address ); continue; } @@ -363,7 +358,7 @@ fn calculate_spo_rewards( if spo.reward_account.get_hash() == delegator_stake_address_hash { debug!( "Skipping pool reward account {}, losing {to_pay}", - hex::encode(delegator_stake_address_hash) + delegator_stake_address ); continue; } @@ -372,7 +367,7 @@ fn calculate_spo_rewards( if deregistrations.contains(delegator_stake_address) { info!( "Recently deregistered member account {}, losing {to_pay}", - hex::encode(delegator_stake_address_hash) + delegator_stake_address ); continue; } @@ -404,7 +399,7 @@ fn calculate_spo_rewards( info!( "SPO {}'s reward account {} not paid {}", hex::encode(&operator_id), - hex::encode(spo.reward_account.get_hash()), + spo.reward_account, spo_benefit, ); } diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index d1bcbee3..71588117 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -86,7 +86,6 @@ impl Snapshot { let blocks_produced = spo_block_counts.get(spo_id).copied().unwrap_or(0); // Check if the reward account from two epochs ago is still registered - // TODO should spo.reward_account be a StakeAddress to begin with? let two_previous_reward_account_is_registered = match two_previous_snapshot.spos.get(spo_id) { Some(old_spo) => stake_addresses @@ -130,7 +129,7 @@ impl Snapshot { epoch, "SPO {} for hash {} retired? Ignored", hex::encode(spo_id), - hex::encode(stake_address.get_hash()) + stake_address ); continue; } @@ -186,9 +185,9 @@ impl Snapshot { #[cfg(test)] mod tests { use super::*; + use acropolis_common::stake_addresses::StakeAddressState; use acropolis_common::AddressNetwork::Main; use acropolis_common::{StakeAddress, StakeAddressPayload}; - use acropolis_common::stake_addresses::StakeAddressState; // Helper function to create stake addresses for testing fn create_test_stake_address(id: u8) -> StakeAddress { @@ -196,7 +195,9 @@ mod tests { hash[0] = id; StakeAddress { network: Main, - payload: StakeAddressPayload::StakeKeyHash(hash.try_into().expect("Invalid hash length")), + payload: StakeAddressPayload::StakeKeyHash( + hash.try_into().expect("Invalid hash length"), + ), } } diff --git a/modules/accounts_state/src/verifier.rs b/modules/accounts_state/src/verifier.rs index 35096d87..7e21661d 100644 --- a/modules/accounts_state/src/verifier.rs +++ b/modules/accounts_state/src/verifier.rs @@ -218,7 +218,7 @@ impl Verifier { error!( "Missing reward: SPO {} account {} {:?} {}", hex::encode(&expected_spo.0), - hex::encode(&expected.account.get_hash()), + expected.account, expected.rtype, expected.amount ); @@ -228,7 +228,7 @@ impl Verifier { error!( "Extra reward: SPO {} account {} {:?} {}", hex::encode(&actual_spo.0), - hex::encode(&actual.account.get_hash()), + actual.account, actual.rtype, actual.amount ); @@ -238,7 +238,7 @@ impl Verifier { if expected.amount != actual.amount { error!("Different reward: SPO {} account {} {:?} expected {}, actual {} ({})", hex::encode(&expected_spo.0), - hex::encode(&expected.account.get_hash()), + expected.account, expected.rtype, expected.amount, actual.amount, @@ -248,7 +248,7 @@ impl Verifier { debug!( "Reward match: SPO {} account {} {:?} {}", hex::encode(&expected_spo.0), - hex::encode(&expected.account.get_hash()), + expected.account, expected.rtype, expected.amount ); From 1d8aacc1f60a64086889147fca0633c749800aa9 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Mon, 20 Oct 2025 17:16:24 -0700 Subject: [PATCH 20/27] Refactor: Replace `KeyHash` with `Credential` across modules for unified handling. - Updated APIs, queries, and function parameters to use `StakeAddress`. - Refactored tests and adjusted utility functions for compatibility. - Improved code readability and consistency by streamlining `StakeAddress` usage. --- common/src/address.rs | 15 +-- common/src/queries/accounts.rs | 14 +- common/src/queries/governance.rs | 5 +- common/src/stake_addresses.rs | 112 +++++++-------- common/src/types.rs | 28 ++-- modules/accounts_state/src/accounts_state.rs | 24 ++-- modules/accounts_state/src/rewards.rs | 6 +- modules/accounts_state/src/snapshot.rs | 18 ++- modules/accounts_state/src/state.rs | 14 +- modules/drep_state/src/state.rs | 8 +- .../rest_blockfrost/src/handlers/accounts.rs | 21 ++- .../src/handlers/governance.rs | 31 ++--- modules/rest_blockfrost/src/handlers/pools.rs | 8 +- modules/spo_state/src/historical_spo_state.rs | 10 +- modules/spo_state/src/state.rs | 127 ++++++------------ modules/tx_unpacker/src/map_parameters.rs | 4 +- 16 files changed, 191 insertions(+), 254 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 51028bda..225a148e 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -3,7 +3,8 @@ #![allow(dead_code)] use crate::cip19::{VarIntDecoder, VarIntEncoder}; -use crate::types::{KeyHash, NetworkId, ScriptHash}; +use crate::types::{KeyHash, ScriptHash}; +use crate::Credential; use anyhow::{anyhow, bail, Result}; use serde_with::{hex::Hex, serde_as}; use std::borrow::Borrow; @@ -181,7 +182,7 @@ impl ShelleyAddress { #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)] pub enum StakeAddressPayload { /// Stake key - StakeKeyHash(#[serde_as(as = "Hex")] Vec), + StakeKeyHash(#[serde_as(as = "Hex")] KeyHash), /// Script hash ScriptHash(#[serde_as(as = "Hex")] ScriptHash), @@ -214,7 +215,6 @@ impl StakeAddress { StakeAddress { network, payload } } - /// Get either hash of the payload pub fn get_hash(&self) -> &[u8] { match &self.payload { StakeAddressPayload::StakeKeyHash(hash) => hash, @@ -222,11 +222,10 @@ impl StakeAddress { } } - /// Construct from a stake key hash - pub fn from_stake_key_hash(hash: &KeyHash, network_id: NetworkId) -> StakeAddress { - StakeAddress { - network: network_id.into(), - payload: StakeAddressPayload::StakeKeyHash(hash.to_vec()), + pub fn get_credential(&self) -> Credential { + match &self.payload { + StakeAddressPayload::StakeKeyHash(hash) => Credential::AddrKeyHash(hash.clone()), + StakeAddressPayload::ScriptHash(hash) => Credential::ScriptHash(hash.clone()), } } diff --git a/common/src/queries/accounts.rs b/common/src/queries/accounts.rs index e91dc99e..7dba5487 100644 --- a/common/src/queries/accounts.rs +++ b/common/src/queries/accounts.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; -use crate::{DRepChoice, KeyHash, PoolLiveStakeInfo}; +use crate::{DRepChoice, KeyHash, PoolLiveStakeInfo, StakeAddress}; pub const DEFAULT_ACCOUNTS_QUERY_TOPIC: (&str, &str) = ("accounts-state-query-topic", "cardano.query.accounts"); #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum AccountsStateQuery { - GetAccountInfo { stake_key: Vec }, + GetAccountInfo { stake_address: StakeAddress }, GetAccountRewardHistory { stake_key: Vec }, GetAccountHistory { stake_key: Vec }, GetAccountDelegationHistory { stake_key: Vec }, @@ -18,10 +18,10 @@ pub enum AccountsStateQuery { GetAccountAssets { stake_key: Vec }, GetAccountAssetsTotals { stake_key: Vec }, GetAccountUTxOs { stake_key: Vec }, - GetAccountsUtxoValuesMap { stake_keys: Vec> }, - GetAccountsUtxoValuesSum { stake_keys: Vec> }, - GetAccountsBalancesMap { stake_keys: Vec> }, - GetAccountsBalancesSum { stake_keys: Vec> }, + GetAccountsUtxoValuesMap { stake_addresses: Vec }, + GetAccountsUtxoValuesSum { stake_addresses: Vec }, + GetAccountsBalancesMap { stake_addresses: Vec }, + GetAccountsBalancesSum { stake_addresses: Vec }, // Epochs-related queries GetActiveStakes {}, @@ -34,7 +34,7 @@ pub enum AccountsStateQuery { // Dreps related queries GetDrepDelegators { drep: DRepChoice }, - GetAccountsDrepDelegationsMap { stake_keys: Vec> }, + GetAccountsDrepDelegationsMap { stake_addresses: Vec }, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] diff --git a/common/src/queries/governance.rs b/common/src/queries/governance.rs index ed0a6462..be6b1240 100644 --- a/common/src/queries/governance.rs +++ b/common/src/queries/governance.rs @@ -1,9 +1,6 @@ use std::collections::HashMap; -use crate::{ - Anchor, Credential, DRepCredential, GovActionId, Lovelace, ProposalProcedure, TxHash, Vote, - Voter, VotingProcedure, -}; +use crate::{Anchor, Credential, DRepCredential, GovActionId, Lovelace, ProposalProcedure, TxHash, Vote, Voter, VotingProcedure}; pub const DEFAULT_DREPS_QUERY_TOPIC: (&str, &str) = ("drep-state-query-topic", "cardano.query.dreps"); diff --git a/common/src/stake_addresses.rs b/common/src/stake_addresses.rs index afc22a4f..945aac4b 100644 --- a/common/src/stake_addresses.rs +++ b/common/src/stake_addresses.rs @@ -1,10 +1,8 @@ use std::{ - borrow::Borrow, collections::{ hash_map::{Entry, Iter, Values}, BTreeMap, HashMap, }, - hash::Hash, sync::atomic::AtomicU64, }; @@ -54,20 +52,12 @@ impl StakeAddressMap { } #[inline] - pub fn get(&self, stake_address: &K) -> Option - where - StakeAddress: Borrow, - K: Hash + Eq + ?Sized, - { + pub fn get(&self, stake_address: &StakeAddress) -> Option { self.inner.get(stake_address).cloned() } #[inline] - pub fn get_mut(&mut self, stake_address: &K) -> Option<&mut StakeAddressState> - where - StakeAddress: Borrow, - K: Hash + Eq + ?Sized, - { + pub fn get_mut(&mut self, stake_address: &StakeAddress) -> Option<&mut StakeAddressState> { self.inner.get_mut(stake_address) } @@ -86,17 +76,20 @@ impl StakeAddressMap { } #[inline] - pub fn entry(&mut self, stake_address: StakeAddress) -> Entry { + pub fn entry( + &mut self, + stake_address: StakeAddress, + ) -> Entry<'_, StakeAddress, StakeAddressState> { self.inner.entry(stake_address) } #[inline] - pub fn values(&self) -> Values { + pub fn values(&self) -> Values<'_, StakeAddress, StakeAddressState> { self.inner.values() } #[inline] - pub fn iter(&self) -> Iter { + pub fn iter(&self) -> Iter<'_, StakeAddress, StakeAddressState> { self.inner.iter() } @@ -207,14 +200,14 @@ impl StakeAddressMap { /// Return None if any of the stake_keys are not found pub fn get_accounts_utxo_values_map( &self, - stake_keys: &[Vec], + stake_addresses: &[StakeAddress], ) -> Option, u64>> { let mut map = HashMap::new(); - for key in stake_keys { - let account = self.get(key.as_slice())?; + for stake_address in stake_addresses { + let account = self.get(stake_address)?; let utxo_value = account.utxo_value; - map.insert(key.clone(), utxo_value); + map.insert(stake_address.to_binary().clone(), utxo_value); } Some(map) @@ -224,14 +217,14 @@ impl StakeAddressMap { /// Return None if any of the stake_addresses are not found pub fn get_accounts_balances_map( &self, - stake_keys: &[Vec], + stake_addresses: &[StakeAddress], ) -> Option, u64>> { let mut map = HashMap::new(); - for stake_key in stake_keys { - let account = self.get(stake_key.as_slice())?; + for stake_address in stake_addresses { + let account = self.get(stake_address)?; let balance = account.utxo_value + account.rewards; - map.insert(stake_key.clone(), balance); + map.insert(stake_address.to_binary().clone(), balance); } Some(map) @@ -241,25 +234,25 @@ impl StakeAddressMap { /// Return None if any of the stake_addresses are not found pub fn get_drep_delegations_map( &self, - stake_keys: &[Vec], + stake_addresses: &[StakeAddress], ) -> Option, Option>> { let mut map = HashMap::new(); - for stake_key in stake_keys { - let account = self.get(stake_key.as_slice())?; + for stake_address in stake_addresses { + let account = self.get(stake_address)?; let maybe_drep = account.delegated_drep.clone(); - map.insert(stake_key.clone(), maybe_drep); + map.insert(stake_address.to_binary().clone(), maybe_drep); } Some(map) } - /// Sum stake_keys utxo_values - /// Return None if any of the stake_keys are not found - pub fn get_accounts_utxo_values_sum(&self, stake_keys: &[Vec]) -> Option { + /// Sum stake_addresss utxo_values + /// Return None if any of the stake_addresss are not found + pub fn get_accounts_utxo_values_sum(&self, stake_addresses: &[StakeAddress]) -> Option { let mut total = 0; - for address in stake_keys { - let account = self.get(address.as_slice())?; + for address in stake_addresses { + let account = self.get(address)?; total += account.utxo_value; } Some(total) @@ -267,10 +260,10 @@ impl StakeAddressMap { /// Sum stake_addresses balances (utxo + rewards) /// Return None if any of stake_addresses are not found - pub fn get_account_balances_sum(&self, stake_keys: &[Vec]) -> Option { + pub fn get_account_balances_sum(&self, stake_addresses: &[StakeAddress]) -> Option { let mut total = 0; - for stake_key in stake_keys { - let account = self.get(stake_key.as_slice())?; + for stake_address in stake_addresses { + let account = self.get(stake_address)?; total += account.utxo_value + account.rewards; } Some(total) @@ -1213,18 +1206,19 @@ mod tests { delta: 2000, }); - let keys = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let keys = vec![addr1.clone(), addr2.clone()]; let map = stake_addresses.get_accounts_utxo_values_map(&keys).unwrap(); assert_eq!(map.len(), 2); - assert_eq!(map.get(addr1.get_hash()).copied().unwrap(), 1000); - assert_eq!(map.get(addr2.get_hash()).copied().unwrap(), 2000); + assert_eq!(map.get(&addr1.to_binary()).copied().unwrap(), 1000); + assert_eq!(map.get(&addr2.to_binary()).copied().unwrap(), 2000); } #[test] fn test_get_accounts_utxo_values_map_missing_account() { let mut stake_addresses = StakeAddressMap::new(); let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); stake_addresses.register_stake_address(&addr1); stake_addresses.process_stake_delta(&StakeAddressDelta { @@ -1232,7 +1226,7 @@ mod tests { delta: 1000, }); - let keys = vec![addr1.get_hash().to_vec(), STAKE_KEY_HASH_2.to_vec()]; + let keys = vec![addr1, addr2]; assert!(stake_addresses.get_accounts_utxo_values_map(&keys).is_none()); } @@ -1241,7 +1235,7 @@ mod tests { fn test_get_accounts_utxo_values_map_empty_list() { let stake_addresses = StakeAddressMap::new(); - let keys: Vec> = vec![]; + let keys: Vec = vec![]; let map = stake_addresses.get_accounts_utxo_values_map(&keys).unwrap(); assert!(map.is_empty()); @@ -1268,7 +1262,7 @@ mod tests { delta: 2000, }); - let keys = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let keys = vec![addr1, addr2]; let sum = stake_addresses.get_accounts_utxo_values_sum(&keys).unwrap(); assert_eq!(sum, 3000); @@ -1286,7 +1280,7 @@ mod tests { delta: 1000, }); - let keys = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let keys = vec![addr1, addr2]; assert!(stake_addresses.get_accounts_utxo_values_sum(&keys).is_none()); } @@ -1295,7 +1289,7 @@ mod tests { fn test_get_accounts_utxo_values_sum_empty_list() { let stake_addresses = StakeAddressMap::new(); - let keys: Vec> = vec![]; + let keys: Vec = vec![]; let sum = stake_addresses.get_accounts_utxo_values_sum(&keys).unwrap(); assert_eq!(sum, 0); @@ -1326,18 +1320,19 @@ mod tests { delta: 2000, }); - let addresses = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let addresses = vec![addr1.clone(), addr2.clone()]; let map = stake_addresses.get_accounts_balances_map(&addresses).unwrap(); assert_eq!(map.len(), 2); - assert_eq!(map.get(addr1.get_hash()).copied().unwrap(), 1100); - assert_eq!(map.get(addr2.get_hash()).copied().unwrap(), 2000); + assert_eq!(map.get(&addr1.to_binary()).copied().unwrap(), 1100); + assert_eq!(map.get(&addr2.to_binary()).copied().unwrap(), 2000); } #[test] fn test_get_accounts_balances_map_missing_account() { let mut stake_addresses = StakeAddressMap::new(); let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); stake_addresses.register_stake_address(&addr1); stake_addresses.process_stake_delta(&StakeAddressDelta { @@ -1345,7 +1340,7 @@ mod tests { delta: 1000, }); - let addresses = vec![addr1.to_binary(), STAKE_KEY_HASH_2.to_vec()]; + let addresses = vec![addr1, addr2]; assert!(stake_addresses.get_accounts_balances_map(&addresses).is_none()); } @@ -1354,7 +1349,7 @@ mod tests { fn test_get_accounts_balances_map_empty_list() { let stake_addresses = StakeAddressMap::new(); - let addresses: Vec> = vec![]; + let addresses: Vec = vec![]; let map = stake_addresses.get_accounts_balances_map(&addresses).unwrap(); assert!(map.is_empty()); @@ -1381,7 +1376,7 @@ mod tests { delta: 2000, }); - let addresses = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let addresses = vec![addr1, addr2]; let sum = stake_addresses.get_account_balances_sum(&addresses).unwrap(); assert_eq!(sum, 3100); @@ -1399,7 +1394,7 @@ mod tests { delta: 1000, }); - let addresses = vec![addr1.get_hash().to_vec(), addr2.get_hash().to_vec()]; + let addresses = vec![addr1, addr2]; assert!(stake_addresses.get_account_balances_sum(&addresses).is_none()); } @@ -1433,34 +1428,31 @@ mod tests { stake_addresses.record_drep_delegation(&addr1, &DRepChoice::Abstain); stake_addresses.record_drep_delegation(&addr2, &DRepChoice::Key(DREP_HASH.to_vec())); - let addresses = vec![ - addr1.get_hash().to_vec(), - addr2.get_hash().to_vec(), - addr3.get_hash().to_vec(), - ]; + let addresses = vec![addr1.clone(), addr2.clone(), addr3.clone()]; let map = stake_addresses.get_drep_delegations_map(&addresses).unwrap(); assert_eq!(map.len(), 3); assert_eq!( - map.get(addr1.get_hash()).unwrap(), + map.get(&addr1.to_binary()).unwrap(), &Some(DRepChoice::Abstain) ); assert_eq!( - map.get(addr2.get_hash()).unwrap(), + map.get(&addr2.to_binary()).unwrap(), &Some(DRepChoice::Key(DREP_HASH.to_vec())) ); - assert_eq!(map.get(addr3.get_hash()).unwrap(), &None); + assert_eq!(map.get(&addr3.to_binary()).unwrap(), &None); } #[test] fn test_get_drep_delegations_map_missing_account() { let mut stake_addresses = StakeAddressMap::new(); let addr1 = create_stake_address(&STAKE_KEY_HASH); + let addr2 = create_stake_address(&STAKE_KEY_HASH_2); stake_addresses.register_stake_address(&addr1); stake_addresses.record_drep_delegation(&addr1, &DRepChoice::NoConfidence); - let addresses = vec![addr1.to_binary(), STAKE_KEY_HASH_2.to_vec()]; + let addresses = vec![addr1, addr2]; assert!(stake_addresses.get_drep_delegations_map(&addresses).is_none()); } @@ -1469,7 +1461,7 @@ mod tests { fn test_get_drep_delegations_map_empty_list() { let stake_addresses = StakeAddressMap::new(); - let addresses: Vec> = vec![]; + let addresses: Vec = vec![]; let map = stake_addresses.get_drep_delegations_map(&addresses).unwrap(); assert!(map.is_empty()); diff --git a/common/src/types.rs b/common/src/types.rs index 4fd5bd11..96f66239 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -500,16 +500,27 @@ pub struct PotDelta { pub delta: LovelaceDelta, } -/// General credential #[derive( - Debug, Clone, Ord, Eq, PartialEq, PartialOrd, Hash, serde::Serialize, serde::Deserialize, + Debug, + Clone, + Ord, + Eq, + PartialEq, + PartialOrd, + Hash, + serde::Serialize, + serde::Deserialize, + minicbor::Decode, + minicbor::Encode, )] pub enum Credential { /// Address key hash - AddrKeyHash(KeyHash), + #[n(0)] + AddrKeyHash(#[n(0)] KeyHash), /// Script hash - ScriptHash(KeyHash), + #[n(1)] + ScriptHash(#[n(0)] KeyHash), } impl Credential { @@ -622,10 +633,7 @@ impl StakeCredential { hash.clone().try_into().expect("Invalid hash length"), ), }; - StakeAddress::new( - network.unwrap_or(AddressNetwork::Main), - payload - ) + StakeAddress::new(network.unwrap_or(AddressNetwork::Main), payload) } } @@ -739,9 +747,9 @@ pub struct PoolRegistration { pub reward_account: StakeAddress, /// Pool owners by their key hash - #[serde_as(as = "Vec")] + // #[serde_as(as = "Vec")] #[n(6)] - pub pool_owners: Vec, + pub pool_owners: Vec, // Relays #[n(7)] diff --git a/modules/accounts_state/src/accounts_state.rs b/modules/accounts_state/src/accounts_state.rs index 98573e70..787a0724 100644 --- a/modules/accounts_state/src/accounts_state.rs +++ b/modules/accounts_state/src/accounts_state.rs @@ -445,8 +445,8 @@ impl AccountsState { }; let response = match query { - AccountsStateQuery::GetAccountInfo { stake_key } => { - if let Some(account) = state.get_stake_state(stake_key) { + AccountsStateQuery::GetAccountInfo { stake_address } => { + if let Some(account) = state.get_stake_state(stake_address) { AccountsStateQueryResponse::AccountInfo(AccountInfo { utxo_value: account.utxo_value, rewards: account.rewards, @@ -482,8 +482,8 @@ impl AccountsState { }) } - AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_keys } => match state - .get_drep_delegations_map(stake_keys) + AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_addresses } => match state + .get_drep_delegations_map(stake_addresses) { Some(map) => AccountsStateQueryResponse::AccountsDrepDelegationsMap(map), None => AccountsStateQueryResponse::Error( @@ -497,8 +497,8 @@ impl AccountsState { ) } - AccountsStateQuery::GetAccountsUtxoValuesMap { stake_keys } => { - match state.get_accounts_utxo_values_map(stake_keys) { + AccountsStateQuery::GetAccountsUtxoValuesMap { stake_addresses } => { + match state.get_accounts_utxo_values_map(stake_addresses) { Some(map) => AccountsStateQueryResponse::AccountsUtxoValuesMap(map), None => AccountsStateQueryResponse::Error( "One or more accounts not found".to_string(), @@ -506,8 +506,8 @@ impl AccountsState { } } - AccountsStateQuery::GetAccountsUtxoValuesSum { stake_keys } => { - match state.get_accounts_utxo_values_sum(stake_keys) { + AccountsStateQuery::GetAccountsUtxoValuesSum { stake_addresses } => { + match state.get_accounts_utxo_values_sum(stake_addresses) { Some(sum) => AccountsStateQueryResponse::AccountsUtxoValuesSum(sum), None => AccountsStateQueryResponse::Error( "One or more accounts not found".to_string(), @@ -515,8 +515,8 @@ impl AccountsState { } } - AccountsStateQuery::GetAccountsBalancesMap { stake_keys } => { - match state.get_accounts_balances_map(stake_keys) { + AccountsStateQuery::GetAccountsBalancesMap { stake_addresses } => { + match state.get_accounts_balances_map(stake_addresses) { Some(map) => AccountsStateQueryResponse::AccountsBalancesMap(map), None => AccountsStateQueryResponse::Error( "One or more accounts not found".to_string(), @@ -530,8 +530,8 @@ impl AccountsState { ) } - AccountsStateQuery::GetAccountsBalancesSum { stake_keys } => { - match state.get_account_balances_sum(stake_keys) { + AccountsStateQuery::GetAccountsBalancesSum { stake_addresses } => { + match state.get_account_balances_sum(stake_addresses) { Some(sum) => AccountsStateQueryResponse::AccountsBalancesSum(sum), None => AccountsStateQueryResponse::Error( "One or more accounts not found".to_string(), diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index 56c50fb5..4b9bd8dd 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -335,7 +335,7 @@ fn calculate_spo_rewards( if !to_delegators.is_zero() { let total_stake = BigDecimal::from(spo.total_stake); for (delegator_stake_address, stake) in &spo.delegators { - let delegator_stake_address_hash = delegator_stake_address.get_hash(); + let delegator_credential = delegator_stake_address.get_credential(); let proportion = BigDecimal::from(stake) / &total_stake; // and hence how much of the total reward they get @@ -346,7 +346,7 @@ fn calculate_spo_rewards( delegator_stake_address); // Pool owners don't get member rewards (seems unfair!) - if spo.pool_owners.contains(&delegator_stake_address_hash.to_vec()) { + if spo.pool_owners.contains(&delegator_credential) { debug!( "Skipping pool owner reward account {}, losing {to_pay}", delegator_stake_address @@ -355,7 +355,7 @@ fn calculate_spo_rewards( } // Check pool's reward address - removing e1 prefix - if spo.reward_account.get_hash() == delegator_stake_address_hash { + if spo.reward_account == *delegator_stake_address { debug!( "Skipping pool reward account {}, losing {to_pay}", delegator_stake_address diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 71588117..c4720d3c 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -1,9 +1,7 @@ //! Acropolis AccountsState: snapshot for rewards calculations use crate::state::{Pots, RegistrationChange}; -use acropolis_common::{ - stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, StakeAddress, -}; +use acropolis_common::{stake_addresses::StakeAddressMap, Credential, KeyHash, Lovelace, PoolRegistration, Ratio, StakeAddress}; use imbl::OrdMap; use std::collections::HashMap; use std::sync::Arc; @@ -37,7 +35,7 @@ pub struct SnapshotSPO { pub two_previous_reward_account_is_registered: bool, /// Pool owners - pub pool_owners: Vec, + pub pool_owners: Vec, } /// Snapshot of stake distribution taken at the end of an particular epoch @@ -161,7 +159,7 @@ impl Snapshot { pub fn get_stake_delegated_to_spo_by_addresses( &self, spo: &KeyHash, - addresses: &[KeyHash], + addresses: &[Credential], ) -> Lovelace { let Some(snapshot_spo) = self.spos.get(spo) else { return 0; @@ -172,7 +170,7 @@ impl Snapshot { .delegators .iter() .filter_map(|(addr, amount)| { - if addr_set.contains(&addr.get_hash().to_vec()) { + if addr_set.contains(&addr.get_credential()) { Some(*amount) } else { None @@ -323,9 +321,9 @@ mod tests { // Extract key hashes from stake addresses for the API call let addresses = vec![ - addr2.get_hash().to_vec(), - addr3.get_hash().to_vec(), - addr4.get_hash().to_vec(), + addr2.get_credential(), + addr3.get_credential(), + addr4.get_credential(), ]; let result = snapshot.get_stake_delegated_to_spo_by_addresses(&spo1, &addresses); assert_eq!(result, 500); @@ -349,7 +347,7 @@ mod tests { ); // Extract key hash from stake address for the API call - let addresses = vec![addr_x.get_hash().to_vec()]; + let addresses = vec![addr_x.get_credential()]; let result = snapshot.get_stake_delegated_to_spo_by_addresses(&spo1, &addresses); assert_eq!(result, 0); } diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 6188f4fc..caaee723 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -135,8 +135,8 @@ pub struct State { impl State { /// Get the stake address state for a give stake key - pub fn get_stake_state(&self, stake_key: &KeyHash) -> Option { - self.stake_addresses.lock().unwrap().get(stake_key.as_slice()) + pub fn get_stake_state(&self, stake_key: &StakeAddress) -> Option { + self.stake_addresses.lock().unwrap().get(stake_key) } /// Get the current pot balances @@ -187,14 +187,14 @@ impl State { /// Map stake_keys to their utxo_values pub fn get_accounts_utxo_values_map( &self, - stake_keys: &[Vec], + stake_keys: &[StakeAddress], ) -> Option, u64>> { let stake_addresses = self.stake_addresses.lock().ok()?; // If lock fails, return None stake_addresses.get_accounts_utxo_values_map(stake_keys) } /// Sum stake_keys utxo_values - pub fn get_accounts_utxo_values_sum(&self, stake_keys: &[Vec]) -> Option { + pub fn get_accounts_utxo_values_sum(&self, stake_keys: &[StakeAddress]) -> Option { let stake_addresses = self.stake_addresses.lock().ok()?; // If lock fails, return None stake_addresses.get_accounts_utxo_values_sum(stake_keys) } @@ -202,7 +202,7 @@ impl State { /// Map stake_keys to their total balances (utxo + rewards) pub fn get_accounts_balances_map( &self, - stake_keys: &[Vec], + stake_keys: &[StakeAddress], ) -> Option, u64>> { let stake_addresses = self.stake_addresses.lock().ok()?; // If lock fails, return None stake_addresses.get_accounts_balances_map(stake_keys) @@ -222,14 +222,14 @@ impl State { /// Map stake_keys to their delegated DRep pub fn get_drep_delegations_map( &self, - stake_keys: &[KeyHash], + stake_keys: &[StakeAddress], ) -> Option>> { let stake_addresses = self.stake_addresses.lock().ok()?; // If lock fails, return None stake_addresses.get_drep_delegations_map(stake_keys) } /// Sum stake_keys balances (utxo + rewards) - pub fn get_account_balances_sum(&self, stake_keys: &[Vec]) -> Option { + pub fn get_account_balances_sum(&self, stake_keys: &[StakeAddress]) -> Option { let stake_addresses = self.stake_addresses.lock().ok()?; // If lock fails, return None stake_addresses.get_account_balances_sum(stake_keys) } diff --git a/modules/drep_state/src/state.rs b/modules/drep_state/src/state.rs index b7165ced..89fe9a5c 100644 --- a/modules/drep_state/src/state.rs +++ b/modules/drep_state/src/state.rs @@ -4,7 +4,7 @@ use acropolis_common::{messages::{Message, StateQuery, StateQueryResponse}, quer accounts::{AccountsStateQuery, AccountsStateQueryResponse, DEFAULT_ACCOUNTS_QUERY_TOPIC}, get_query_topic, governance::{DRepActionUpdate, DRepUpdateEvent, VoteRecord}, -}, Anchor, Credential, DRepChoice, DRepCredential, KeyHash, Lovelace, StakeCredential, TxCertificate, TxHash, Voter, VotingProcedures}; +}, Anchor, Credential, DRepChoice, DRepCredential, KeyHash, Lovelace, StakeAddress, StakeCredential, TxCertificate, TxHash, Voter, VotingProcedures}; use anyhow::{anyhow, Result}; use caryatid_sdk::Context; use serde_with::serde_as; @@ -469,7 +469,9 @@ impl State { context: &Arc>, delegators: Vec<(&StakeCredential, &DRepChoice)>, ) -> Result<()> { - let stake_keys: Vec<_> = delegators.iter().map(|(sc, _)| sc.get_hash()).collect(); + let stake_keys: Vec = delegators.iter().map(|(sc, _)| sc.get_hash()).collect(); + // TODO: USE NETWORK ID + let stake_addresses: Vec = delegators.iter().map(|(k, _) | k.to_stake_address(None) ).collect(); let stake_key_to_input: HashMap = delegators .iter() .zip(&stake_keys) @@ -477,7 +479,7 @@ impl State { .collect(); let msg = Arc::new(Message::StateQuery(StateQuery::Accounts( - AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_keys }, + AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_addresses }, ))); let accounts_query_topic = get_query_topic(context.clone(), DEFAULT_ACCOUNTS_QUERY_TOPIC); diff --git a/modules/rest_blockfrost/src/handlers/accounts.rs b/modules/rest_blockfrost/src/handlers/accounts.rs index e2b20bc5..7ae84995 100644 --- a/modules/rest_blockfrost/src/handlers/accounts.rs +++ b/modules/rest_blockfrost/src/handlers/accounts.rs @@ -5,7 +5,7 @@ use acropolis_common::messages::{Message, RESTResponse, StateQuery, StateQueryRe use acropolis_common::queries::accounts::{AccountsStateQuery, AccountsStateQueryResponse}; use acropolis_common::queries::utils::query_state; use acropolis_common::serialization::Bech32WithHrp; -use acropolis_common::{Address, DRepChoice, StakeAddress, StakeAddressPayload}; +use acropolis_common::{DRepChoice, StakeAddress}; use anyhow::{anyhow, Result}; use caryatid_sdk::Context; @@ -31,30 +31,27 @@ pub async fn handle_single_account_blockfrost( params: Vec, handlers_config: Arc, ) -> Result { - let Some(stake_address) = params.get(0) else { + let Some(stake_key) = params.get(0) else { return Ok(RESTResponse::with_text( 400, "Missing stake address parameter", )); }; - // Convert Bech32 stake address to StakeCredential - let stake_key = match Address::from_string(&stake_address) { - Ok(Address::Stake(StakeAddress { - payload: StakeAddressPayload::StakeKeyHash(hash), - .. - })) => hash, + // Convert Bech32 stake address to StakeAddress + let stake_address = match StakeAddress::from_string(&stake_key) { + Ok(addr) => addr, _ => { return Ok(RESTResponse::with_text( 400, - &format!("Not a valid stake address: {stake_address}"), + &format!("Not a valid stake address: {stake_key}"), )); } }; // Prepare the message let msg = Arc::new(Message::StateQuery(StateQuery::Accounts( - AccountsStateQuery::GetAccountInfo { stake_key }, + AccountsStateQuery::GetAccountInfo { stake_address }, ))); let account = query_state( &context, @@ -114,8 +111,8 @@ pub async fn handle_single_account_blockfrost( let rest_response = StakeAccountRest { utxo_value: account.utxo_value, rewards: account.rewards, - delegated_spo: delegated_spo, - delegated_drep: delegated_drep, + delegated_spo, + delegated_drep, }; match serde_json::to_string(&rest_response) { diff --git a/modules/rest_blockfrost/src/handlers/governance.rs b/modules/rest_blockfrost/src/handlers/governance.rs index ed0eff5e..0563ddc8 100644 --- a/modules/rest_blockfrost/src/handlers/governance.rs +++ b/modules/rest_blockfrost/src/handlers/governance.rs @@ -4,14 +4,10 @@ use crate::types::{ DRepInfoREST, DRepMetadataREST, DRepUpdateREST, DRepVoteREST, DRepsListREST, ProposalVoteREST, VoterRoleREST, }; -use acropolis_common::{ - messages::{Message, RESTResponse, StateQuery, StateQueryResponse}, - queries::{ - accounts::{AccountsStateQuery, AccountsStateQueryResponse}, - governance::{GovernanceStateQuery, GovernanceStateQueryResponse}, - }, - Credential, GovActionId, TxHash, Voter, -}; +use acropolis_common::{messages::{Message, RESTResponse, StateQuery, StateQueryResponse}, queries::{ + accounts::{AccountsStateQuery, AccountsStateQueryResponse}, + governance::{GovernanceStateQuery, GovernanceStateQueryResponse}, +}, Credential, GovActionId, StakeAddress, TxHash, Voter}; use anyhow::Result; use caryatid_sdk::Context; use reqwest::Client; @@ -99,16 +95,11 @@ pub async fn handle_single_drep_blockfrost( )) => { let active = !response.info.retired && !response.info.expired; - let accounts = response - .delegators - .iter() - .map(|addr| addr.get_hash()) // or `get_hash()` if using StakeCredential - .collect(); + let stake_addresses = + response.delegators.iter().map(|addr| addr.to_stake_address(None)).collect(); let sum_msg = Arc::new(Message::StateQuery(StateQuery::Accounts( - AccountsStateQuery::GetAccountsBalancesSum { - stake_keys: accounts, - }, + AccountsStateQuery::GetAccountsBalancesSum { stake_addresses }, ))); let raw_sum = @@ -197,7 +188,7 @@ pub async fn handle_drep_delegators_blockfrost( Message::StateQueryResponse(StateQueryResponse::Governance( GovernanceStateQueryResponse::DRepDelegators(delegators), )) => { - let mut stake_keys = Vec::new(); + let mut stake_addresses: Vec = Vec::new(); let mut stake_key_to_bech32 = HashMap::new(); for addr in &delegators.addresses { @@ -212,12 +203,14 @@ pub async fn handle_drep_delegators_blockfrost( }; let hash = addr.get_hash(); - stake_keys.push(hash.clone()); + // TODO: NETWORK ID + let stake_address = addr.to_stake_address(None); + stake_addresses.push(stake_address.clone()); stake_key_to_bech32.insert(hash, bech32); } let msg = Arc::new(Message::StateQuery(StateQuery::Accounts( - AccountsStateQuery::GetAccountsUtxoValuesMap { stake_keys }, + AccountsStateQuery::GetAccountsUtxoValuesMap { stake_addresses }, ))); let raw_msg = diff --git a/modules/rest_blockfrost/src/handlers/pools.rs b/modules/rest_blockfrost/src/handlers/pools.rs index 53cdacd6..eaa27403 100644 --- a/modules/rest_blockfrost/src/handlers/pools.rs +++ b/modules/rest_blockfrost/src/handlers/pools.rs @@ -664,7 +664,11 @@ async fn handle_pools_spo_blockfrost( // Query owner accounts balance sum from accounts_state let live_pledge_msg = Arc::new(Message::StateQuery(StateQuery::Accounts( AccountsStateQuery::GetAccountsUtxoValuesSum { - stake_keys: pool_info.pool_owners.clone(), + stake_addresses: pool_info + .pool_owners + .iter() + .map(|owner| owner.to_stake_address(None)) + .collect(), }, ))); @@ -699,7 +703,7 @@ async fn handle_pools_spo_blockfrost( let pool_owners = pool_info .pool_owners .iter() - .map(|owner| owner.to_bech32_with_hrp("stake")) + .map(|owner| owner.to_stake_bech32()) .collect::, _>>(); let Ok(pool_owners) = pool_owners else { return Ok(RESTResponse::with_text(404, "Invalid Pool Owners")); diff --git a/modules/spo_state/src/historical_spo_state.rs b/modules/spo_state/src/historical_spo_state.rs index 51c63ddc..d6c2e9bd 100644 --- a/modules/spo_state/src/historical_spo_state.rs +++ b/modules/spo_state/src/historical_spo_state.rs @@ -1,6 +1,4 @@ -use acropolis_common::{ - queries::governance::VoteRecord, KeyHash, PoolRegistration, PoolUpdateEvent, -}; +use acropolis_common::{queries::governance::VoteRecord, PoolRegistration, PoolUpdateEvent, StakeAddress}; use imbl::{HashSet, OrdMap, Vector}; use serde::{Deserialize, Serialize}; @@ -14,7 +12,7 @@ pub struct HistoricalSPOState { pub updates: Option>, // SPO's delegators - pub delegators: Option>, + pub delegators: Option>, // SPO's votes pub votes: Option>, @@ -51,13 +49,13 @@ impl HistoricalSPOState { }) } - pub fn add_delegator(&mut self, delegator: &KeyHash) -> Option { + pub fn add_delegator(&mut self, delegator: &StakeAddress) -> Option { self.delegators .as_mut() .and_then(|delegators| Some(delegators.insert(delegator.clone()).is_some())) } - pub fn remove_delegator(&mut self, delegator: &KeyHash) -> Option { + pub fn remove_delegator(&mut self, delegator: &StakeAddress) -> Option { self.delegators.as_mut().and_then(|delegators| Some(delegators.remove(delegator).is_some())) } diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 45d734bc..059ff70b 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -1,19 +1,9 @@ //! Acropolis SPOState: State storage -use acropolis_common::{ - crypto::keyhash_224, - ledger_state::SPOState, - messages::{ - CardanoMessage, Message, SPOStateMessage, StakeAddressDeltasMessage, - StakeRewardDeltasMessage, TxCertificatesMessage, WithdrawalsMessage, - }, - params::TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH, - queries::governance::VoteRecord, - stake_addresses::StakeAddressMap, - BlockInfo, KeyHash, PoolMetadata, PoolRegistration, PoolRegistrationWithPos, PoolRetirement, - PoolRetirementWithPos, PoolUpdateEvent, Relay, StakeCredential, TxCertificate, TxHash, Voter, - VotingProcedures, -}; +use acropolis_common::{crypto::keyhash_224, ledger_state::SPOState, messages::{ + CardanoMessage, Message, SPOStateMessage, StakeAddressDeltasMessage, + StakeRewardDeltasMessage, TxCertificatesMessage, WithdrawalsMessage, +}, params::TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH, queries::governance::VoteRecord, stake_addresses::StakeAddressMap, BlockInfo, KeyHash, PoolMetadata, PoolRegistration, PoolRegistrationWithPos, PoolRetirement, PoolRetirementWithPos, PoolUpdateEvent, Relay, StakeAddress, StakeCredential, TxCertificate, TxHash, Voter, VotingProcedures}; use anyhow::Result; use imbl::HashMap; use std::sync::{Arc, Mutex}; @@ -190,7 +180,7 @@ impl State { .get(pool_operator) .map(|s| s.delegators.clone()) .flatten() - .map(|s| s.into_iter().collect::>()); + .map(|s| s.into_iter().collect::>()); let Some(delegators) = delegators else { return None; }; @@ -459,7 +449,7 @@ impl State { if let Some(old_spo) = old_spo.as_ref() { // remove delegators from old_spo if let Some(historical_spo) = historical_spos.get_mut(old_spo) { - if let Some(removed) = historical_spo.remove_delegator(&credential.get_hash()) { + if let Some(removed) = historical_spo.remove_delegator(&credential.to_stake_address(None)) { if !removed { error!( "Historical SPO state for {} does not contain delegator {}", @@ -492,7 +482,7 @@ impl State { if let Some(old_spo) = old_spo.as_ref() { match historical_spos.get_mut(old_spo) { Some(historical_spo) => { - if let Some(removed) = historical_spo.remove_delegator(&hash) { + if let Some(removed) = historical_spo.remove_delegator(&stake_address) { if !removed { error!( "Historical SPO state for {} does not contain delegator {}", @@ -512,7 +502,7 @@ impl State { let historical_spo = historical_spos .entry(spo.clone()) .or_insert_with(|| HistoricalSPOState::new(&self.store_config)); - if let Some(added) = historical_spo.add_delegator(&hash) { + if let Some(added) = historical_spo.add_delegator(&stake_address) { if !added { error!( "Historical SPO state for {} already contains delegator {}", @@ -682,10 +672,34 @@ mod tests { use crate::test_utils::*; use acropolis_common::{ state_history::{StateHistory, StateHistoryStore}, - PoolRetirement, Ratio, StakeAddress, TxCertificate, TxHash, + Credential, PoolRetirement, Ratio, StakeAddress, TxCertificate, TxHash, }; use tokio::sync::Mutex; + fn pool_owners_from_bytes(owners: Vec>) -> Vec { + owners + .into_iter() + .map(|bytes| Credential::AddrKeyHash(bytes)) + .collect() + } + + fn default_pool_registration(operator: Vec) -> PoolRegistration { + PoolRegistration { + operator: operator.clone(), + vrf_key_hash: vec![0], + pledge: 0, + cost: 0, + margin: Ratio { + numerator: 0, + denominator: 0, + }, + reward_account: StakeAddress::default(), + pool_owners: pool_owners_from_bytes(vec![operator]), + relays: vec![], + pool_metadata: None, + } + } + #[test] fn get_returns_none_on_empty_state() { let state = State::default(); @@ -713,20 +727,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { - operator: vec![0], - vrf_key_hash: vec![0], - pledge: 0, - cost: 0, - margin: Ratio { - numerator: 0, - denominator: 0, - }, - reward_account: StakeAddress::default(), - pool_owners: vec![vec![0]], - relays: vec![], - pool_metadata: None, - }, + reg: default_pool_registration(vec![0]), tx_hash: TxHash::default(), cert_index: 1, }, @@ -862,20 +863,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { - operator: vec![0], - vrf_key_hash: vec![0], - pledge: 0, - cost: 0, - margin: Ratio { - numerator: 0, - denominator: 0, - }, - reward_account: StakeAddress::default(), - pool_owners: vec![vec![0]], - relays: vec![], - pool_metadata: None, - }, + reg: default_pool_registration(vec![0]), tx_hash: TxHash::default(), cert_index: 0, }, @@ -918,20 +906,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { - operator: vec![0], - vrf_key_hash: vec![0], - pledge: 0, - cost: 0, - margin: Ratio { - numerator: 0, - denominator: 0, - }, - reward_account: StakeAddress::default(), - pool_owners: vec![vec![0]], - relays: vec![], - pool_metadata: None, - }, + reg: default_pool_registration(vec![0]), tx_hash: TxHash::default(), cert_index: 0, }, @@ -1036,20 +1011,7 @@ mod tests { let spo_id = keyhash_224(&vec![1 as u8]); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { - operator: spo_id.clone(), - vrf_key_hash: keyhash_224(&vec![0]), - pledge: 0, - cost: 0, - margin: Ratio { - numerator: 0, - denominator: 0, - }, - reward_account: StakeAddress::default(), - pool_owners: vec![vec![0]], - relays: vec![], - pool_metadata: None, - }, + reg: default_pool_registration(spo_id.clone()), tx_hash: TxHash::default(), cert_index: 0, }, @@ -1086,20 +1048,7 @@ mod tests { let spo_id = keyhash_224(&vec![1 as u8]); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { - operator: spo_id.clone(), - vrf_key_hash: keyhash_224(&vec![0]), - pledge: 0, - cost: 0, - margin: Ratio { - numerator: 0, - denominator: 0, - }, - reward_account: StakeAddress::default(), - pool_owners: vec![vec![0]], - relays: vec![], - pool_metadata: None, - }, + reg: default_pool_registration(spo_id.clone()), tx_hash: TxHash::default(), cert_index: 0, }, diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index 630c0e8c..1439479e 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -246,7 +246,7 @@ pub fn map_certificate( denominator: margin.denominator, }, reward_account: StakeAddress::from_binary(reward_account)?, - pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), + pool_owners: pool_owners.into_iter().map(|v| Credential::AddrKeyHash(v.to_vec())).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { Nullable::Some(md) => Some(PoolMetadata { @@ -348,7 +348,7 @@ pub fn map_certificate( denominator: margin.denominator, }, reward_account: StakeAddress::from_binary(reward_account)?, - pool_owners: pool_owners.into_iter().map(|v| v.to_vec()).collect(), + pool_owners: pool_owners.into_iter().map(|v| Credential::AddrKeyHash(v.to_vec())).collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { Nullable::Some(md) => Some(PoolMetadata { From 32045afd3276ad6be1ed7947f523d3e839d76b6c Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Mon, 20 Oct 2025 21:02:54 -0700 Subject: [PATCH 21/27] Refactor: Simplify stake address handling and remove unnecessary encoding - Remove `From for AddressNetwork` implementation for cleanup. - Simplify logging by directly using `StakeAddress` instead of `hex::encode(...get_hash())`. - Add `TODO` comments for handling network in relevant functions. - Minor readability improvements in account state management and registration functions. --- common/src/types.rs | 10 --------- modules/accounts_state/src/state.rs | 29 ++++++++++++-------------- modules/accounts_state/src/verifier.rs | 2 +- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/common/src/types.rs b/common/src/types.rs index 96f66239..c0cb1aa6 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -26,16 +26,6 @@ pub enum NetworkId { Mainnet, } -// TODO: Would really like to consolidate NetworkId and AddressNetwork at some point -impl From for AddressNetwork { - fn from(network_id: NetworkId) -> Self { - match network_id { - NetworkId::Mainnet => AddressNetwork::Main, - NetworkId::Testnet => AddressNetwork::Test, - } - } -} - /// Protocol era #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index caaee723..b4a9dad3 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -386,12 +386,8 @@ impl State { ); if tracing::enabled!(Level::DEBUG) { - registrations - .iter() - .for_each(|addr| debug!("Registration {}", hex::encode(addr.get_hash()))); - deregistrations - .iter() - .for_each(|addr| debug!("Deregistration {}", hex::encode(addr.get_hash()))); + registrations.iter().for_each(|addr| debug!("Registration {}", addr)); + deregistrations.iter().for_each(|addr| debug!("Deregistration {}", addr)); } // Calculate reward payouts for previous epoch @@ -490,7 +486,7 @@ impl State { } else { warn!( "SPO reward account {} deregistered - paying refund to treasury", - hex::encode(stake_address.get_hash()) + stake_address ); self.pots.treasury += deposit; } @@ -566,10 +562,7 @@ impl State { delta: *value, }); if let Err(e) = update_value_with_delta(&mut sas.rewards, *value) { - error!( - "MIR to stake address {}: {e}", - hex::encode(stake_address.get_hash()) - ); + error!("MIR to stake address {}: {e}", stake_address); } // Update the source @@ -783,7 +776,7 @@ impl State { debug!( "SPO {} has retired - refunding their deposit to {}", hex::encode(id), - hex::encode(retired_spo.reward_account.get_hash()) + retired_spo.reward_account ); self.pool_refunds.push(retired_spo.reward_account.clone()); // Store full StakeAddress } @@ -799,7 +792,8 @@ impl State { /// Register a stake address, with a specified deposit if known fn register_stake_address(&mut self, credential: &StakeCredential, deposit: Option) { - let stake_address = credential.to_stake_address(None); // Convert credential to full address + // TODO: Handle network + let stake_address = credential.to_stake_address(None); // Stake addresses can be registered after being used in UTXOs let mut stake_addresses = self.stake_addresses.lock().unwrap(); @@ -829,7 +823,8 @@ impl State { /// Deregister a stake address, with specified refund if known fn deregister_stake_address(&mut self, credential: &StakeCredential, refund: Option) { - let stake_address = credential.to_stake_address(None); // Convert credential to full address + // TODO: Handle network + let stake_address = credential.to_stake_address(None); // Check if it existed let mut stake_addresses = self.stake_addresses.lock().unwrap(); @@ -865,7 +860,8 @@ impl State { /// Record a stake delegation fn record_stake_delegation(&mut self, credential: &StakeCredential, spo: &KeyHash) { - let stake_address = credential.to_stake_address(None); // Convert credential to full address + // TODO: Handle network + let stake_address = credential.to_stake_address(None); let mut stake_addresses = self.stake_addresses.lock().unwrap(); stake_addresses.record_stake_delegation(&stake_address, spo); } @@ -878,7 +874,8 @@ impl State { /// record a drep delegation fn record_drep_delegation(&mut self, credential: &StakeCredential, drep: &DRepChoice) { - let stake_address = credential.to_stake_address(None); // Convert credential to full address + // TODO: Handle network + let stake_address = credential.to_stake_address(None); let mut stake_addresses = self.stake_addresses.lock().unwrap(); stake_addresses.record_drep_delegation(&stake_address, drep); } diff --git a/modules/accounts_state/src/verifier.rs b/modules/accounts_state/src/verifier.rs index 7e21661d..f34924af 100644 --- a/modules/accounts_state/src/verifier.rs +++ b/modules/accounts_state/src/verifier.rs @@ -114,7 +114,7 @@ impl Verifier { match (&left.rtype, &right.rtype) { (RewardType::Leader, RewardType::Member) => Ordering::Less, (RewardType::Member, RewardType::Leader) => Ordering::Greater, - _ => left.account.get_hash().cmp(&right.account.get_hash()), + _ => left.account.get_credential().cmp(&right.account.get_credential()), } } From fe8c1c9164580accbdbb86a4a79b7b828db2078d Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Tue, 21 Oct 2025 21:31:11 -0700 Subject: [PATCH 22/27] ### Description Resolves issue #163 This adds `NetworkId` into the `BlockInfo` struct, and introduces this identifier into the account state, drep state, epochs state and rest_blockfrost as these all rely on querying the `StakeAddressMap`. --- common/src/address.rs | 37 +++++--------- common/src/stake_addresses.rs | 2 +- common/src/types.rs | 17 ++++++- modules/accounts_state/src/snapshot.rs | 6 +-- modules/accounts_state/src/state.rs | 28 ++++++----- modules/drep_state/src/state.rs | 7 +-- modules/epochs_state/src/epochs_history.rs | 3 +- modules/epochs_state/src/state.rs | 5 +- .../src/genesis_bootstrapper.rs | 5 +- .../src/alonzo_babbage_voting.rs | 3 +- .../src/mithril_snapshot_fetcher.rs | 10 +++- .../src/handlers/governance.rs | 23 ++++++--- modules/rest_blockfrost/src/handlers/pools.rs | 2 +- .../rest_blockfrost/src/handlers_config.rs | 10 ++++ modules/spo_state/src/state.rs | 49 +++++++++++++------ modules/spo_state/src/test_utils.rs | 10 ++-- modules/stake_delta_filter/src/utils.rs | 6 ++- .../src/body_fetcher.rs | 7 ++- .../src/upstream_cache.rs | 6 +-- modules/upstream_chain_fetcher/src/utils.rs | 13 +++-- modules/utxo_state/src/state.rs | 3 +- processes/golden_tests/src/test_module.rs | 13 ++--- processes/omnibus/omnibus.toml | 3 ++ 23 files changed, 164 insertions(+), 104 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 225a148e..837a3bd8 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -4,12 +4,10 @@ use crate::cip19::{VarIntDecoder, VarIntEncoder}; use crate::types::{KeyHash, ScriptHash}; -use crate::Credential; +use crate::{Credential, NetworkId}; use anyhow::{anyhow, bail, Result}; use serde_with::{hex::Hex, serde_as}; -use std::borrow::Borrow; use std::fmt::{Display, Formatter}; -use std::hash::{Hash, Hasher}; /// a Byron-era address #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -28,6 +26,15 @@ pub enum AddressNetwork { Test, } +impl From for AddressNetwork { + fn from(network: NetworkId) -> Self { + match network { + NetworkId::Mainnet => Self::Main, + NetworkId::Testnet => Self::Test, + } + } +} + impl Default for AddressNetwork { fn default() -> Self { Self::Main @@ -201,7 +208,7 @@ impl StakeAddressPayload { } /// A stake address -#[derive(Debug, Clone, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] pub struct StakeAddress { /// Network id pub network: AddressNetwork, @@ -211,7 +218,7 @@ pub struct StakeAddress { } impl StakeAddress { - pub fn new(network: AddressNetwork, payload: StakeAddressPayload) -> Self { + pub fn new(payload: StakeAddressPayload, network: AddressNetwork) -> Self { StakeAddress { network, payload } } @@ -305,26 +312,6 @@ impl Display for StakeAddress { } } -impl Hash for StakeAddress { - fn hash(&self, state: &mut H) { - self.network.hash(state); - std::mem::discriminant(&self.payload).hash(state); - self.get_hash().hash(state); - } -} - -impl Borrow<[u8]> for StakeAddress { - fn borrow(&self) -> &[u8] { - self.get_hash() - } -} - -impl PartialEq for StakeAddress { - fn eq(&self, other: &Self) -> bool { - self.network == other.network && self.payload == other.payload - } -} - impl minicbor::Encode for StakeAddress { fn encode( &self, diff --git a/common/src/stake_addresses.rs b/common/src/stake_addresses.rs index 945aac4b..2189fc03 100644 --- a/common/src/stake_addresses.rs +++ b/common/src/stake_addresses.rs @@ -528,10 +528,10 @@ mod tests { fn create_stake_address(hash: &[u8]) -> StakeAddress { StakeAddress::new( - AddressNetwork::Main, StakeAddressPayload::StakeKeyHash( hash.to_vec().try_into().expect("Invalid hash length"), ), + AddressNetwork::Main, ) } diff --git a/common/src/types.rs b/common/src/types.rs index c0cb1aa6..3f31598c 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -26,6 +26,16 @@ pub enum NetworkId { Mainnet, } +impl From for NetworkId { + fn from(s: String) -> Self { + match s.as_str() { + "testnet" => NetworkId::Testnet, + "mainnet" => NetworkId::Mainnet, + _ => NetworkId::Mainnet, + } + } +} + /// Protocol era #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, @@ -120,6 +130,9 @@ pub struct BlockInfo { #[serde(default)] pub timestamp: u64, + /// Network ID + pub network_id: NetworkId, + /// Protocol era pub era: Era, } @@ -614,7 +627,7 @@ impl Credential { pub type StakeCredential = Credential; impl StakeCredential { - pub fn to_stake_address(&self, network: Option) -> StakeAddress { + pub fn to_stake_address(&self, network: AddressNetwork) -> StakeAddress { let payload = match self { StakeCredential::AddrKeyHash(hash) => StakeAddressPayload::StakeKeyHash( hash.clone().try_into().expect("Invalid hash length"), @@ -623,7 +636,7 @@ impl StakeCredential { hash.clone().try_into().expect("Invalid hash length"), ), }; - StakeAddress::new(network.unwrap_or(AddressNetwork::Main), payload) + StakeAddress::new(payload, network) } } diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index c4720d3c..b9ecfe1b 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -165,12 +165,12 @@ impl Snapshot { return 0; }; - let addr_set: std::collections::HashSet<_> = addresses.iter().collect(); + let address_set: std::collections::HashSet<_> = addresses.iter().collect(); snapshot_spo .delegators .iter() - .filter_map(|(addr, amount)| { - if addr_set.contains(&addr.get_credential()) { + .filter_map(|(address, amount)| { + if address_set.contains(&address.get_credential()) { Some(*amount) } else { None diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index b4a9dad3..086035fa 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -14,9 +14,9 @@ use acropolis_common::{ protocol_params::ProtocolParams, stake_addresses::{StakeAddressMap, StakeAddressState}, BlockInfo, DRepChoice, DRepCredential, DelegatedStake, InstantaneousRewardSource, - InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, PoolLiveStakeInfo, - PoolRegistration, Pot, SPORewards, StakeAddress, StakeCredential, StakeRewardDelta, - TxCertificate, + InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, NetworkId, + PoolLiveStakeInfo, PoolRegistration, Pot, SPORewards, StakeAddress, StakeCredential, + StakeRewardDelta, TxCertificate, }; use anyhow::Result; use imbl::OrdMap; @@ -95,6 +95,9 @@ pub struct State { /// List of SPOs (by operator ID) retiring in the current epoch retiring_spos: Vec, + /// Network ID + network_id: NetworkId, + /// Map of staking address values /// Wrapped in an Arc so it doesn't get cloned in full by StateHistory stake_addresses: Arc>, @@ -550,7 +553,8 @@ impl State { // Transfer to (in theory also from) stake addresses from (to) a pot let mut total_value: u64 = 0; for (credential, value) in deltas.iter() { - let stake_address = credential.to_stake_address(None); // Need to convert credential to address + let stake_address = + credential.to_stake_address(self.network_id.clone().into()); // Get old stake address state, or create one let mut stake_addresses = self.stake_addresses.lock().unwrap(); @@ -792,8 +796,8 @@ impl State { /// Register a stake address, with a specified deposit if known fn register_stake_address(&mut self, credential: &StakeCredential, deposit: Option) { - // TODO: Handle network - let stake_address = credential.to_stake_address(None); + + let stake_address = credential.to_stake_address(self.network_id.clone().into()); // Stake addresses can be registered after being used in UTXOs let mut stake_addresses = self.stake_addresses.lock().unwrap(); @@ -823,8 +827,8 @@ impl State { /// Deregister a stake address, with specified refund if known fn deregister_stake_address(&mut self, credential: &StakeCredential, refund: Option) { - // TODO: Handle network - let stake_address = credential.to_stake_address(None); + + let stake_address = credential.to_stake_address(self.network_id.clone().into()); // Check if it existed let mut stake_addresses = self.stake_addresses.lock().unwrap(); @@ -860,8 +864,8 @@ impl State { /// Record a stake delegation fn record_stake_delegation(&mut self, credential: &StakeCredential, spo: &KeyHash) { - // TODO: Handle network - let stake_address = credential.to_stake_address(None); + + let stake_address = credential.to_stake_address(self.network_id.clone().into()); let mut stake_addresses = self.stake_addresses.lock().unwrap(); stake_addresses.record_stake_delegation(&stake_address, spo); } @@ -874,8 +878,8 @@ impl State { /// record a drep delegation fn record_drep_delegation(&mut self, credential: &StakeCredential, drep: &DRepChoice) { - // TODO: Handle network - let stake_address = credential.to_stake_address(None); + + let stake_address = credential.to_stake_address(self.network_id.clone().into()); let mut stake_addresses = self.stake_addresses.lock().unwrap(); stake_addresses.record_drep_delegation(&stake_address, drep); } diff --git a/modules/drep_state/src/state.rs b/modules/drep_state/src/state.rs index 89fe9a5c..7a2c7296 100644 --- a/modules/drep_state/src/state.rs +++ b/modules/drep_state/src/state.rs @@ -4,7 +4,7 @@ use acropolis_common::{messages::{Message, StateQuery, StateQueryResponse}, quer accounts::{AccountsStateQuery, AccountsStateQueryResponse, DEFAULT_ACCOUNTS_QUERY_TOPIC}, get_query_topic, governance::{DRepActionUpdate, DRepUpdateEvent, VoteRecord}, -}, Anchor, Credential, DRepChoice, DRepCredential, KeyHash, Lovelace, StakeAddress, StakeCredential, TxCertificate, TxHash, Voter, VotingProcedures}; +}, Anchor, Credential, DRepChoice, DRepCredential, KeyHash, Lovelace, NetworkId, StakeAddress, StakeCredential, TxCertificate, TxHash, Voter, VotingProcedures}; use anyhow::{anyhow, Result}; use caryatid_sdk::Context; use serde_with::serde_as; @@ -89,6 +89,7 @@ impl DRepStorageConfig { pub struct State { pub config: DRepStorageConfig, pub dreps: HashMap, + pub network_id: NetworkId, pub historical_dreps: Option>, } @@ -97,6 +98,7 @@ impl State { Self { config, dreps: HashMap::new(), + network_id: NetworkId::default(), historical_dreps: if config.any_enabled() { Some(HashMap::new()) } else { @@ -470,8 +472,7 @@ impl State { delegators: Vec<(&StakeCredential, &DRepChoice)>, ) -> Result<()> { let stake_keys: Vec = delegators.iter().map(|(sc, _)| sc.get_hash()).collect(); - // TODO: USE NETWORK ID - let stake_addresses: Vec = delegators.iter().map(|(k, _) | k.to_stake_address(None) ).collect(); + let stake_addresses: Vec = delegators.iter().map(|(k, _) | k.to_stake_address(self.network_id.clone().into()) ).collect(); let stake_key_to_input: HashMap = delegators .iter() .zip(&stake_keys) diff --git a/modules/epochs_state/src/epochs_history.rs b/modules/epochs_state/src/epochs_history.rs index dc37b56a..145a309b 100644 --- a/modules/epochs_state/src/epochs_history.rs +++ b/modules/epochs_state/src/epochs_history.rs @@ -78,7 +78,7 @@ impl EpochsHistoryState { #[cfg(test)] mod tests { use super::*; - use acropolis_common::{BlockHash, BlockStatus, Era}; + use acropolis_common::{BlockHash, BlockStatus, Era, NetworkId}; fn make_block(epoch: u64) -> BlockInfo { BlockInfo { @@ -89,6 +89,7 @@ mod tests { epoch, epoch_slot: 99, new_epoch: false, + network_id: NetworkId::default(), timestamp: 99999, era: Era::Conway, } diff --git a/modules/epochs_state/src/state.rs b/modules/epochs_state/src/state.rs index 0a46c07a..6fbd40f3 100644 --- a/modules/epochs_state/src/state.rs +++ b/modules/epochs_state/src/state.rs @@ -269,7 +269,7 @@ mod tests { crypto::keyhash_224, protocol_params::{Nonce, NonceHash}, state_history::{StateHistory, StateHistoryStore}, - BlockHash, BlockInfo, BlockStatus, Era, + BlockHash, BlockInfo, BlockStatus, Era, NetworkId, }; use tokio::sync::Mutex; @@ -281,6 +281,7 @@ mod tests { hash: BlockHash::default(), epoch, epoch_slot: 99, + network_id: NetworkId::default(), new_epoch: false, timestamp: 99999, era: Era::Shelley, @@ -295,6 +296,7 @@ mod tests { hash: BlockHash::default(), epoch, epoch_slot: 99, + network_id: NetworkId::default(), new_epoch: true, timestamp: 99999, era: Era::Shelley, @@ -309,6 +311,7 @@ mod tests { hash: BlockHash::default(), epoch, epoch_slot: 99, + network_id: NetworkId::default(), new_epoch: false, timestamp: 99999, era: Era::Conway, diff --git a/modules/genesis_bootstrapper/src/genesis_bootstrapper.rs b/modules/genesis_bootstrapper/src/genesis_bootstrapper.rs index b256954c..54e7a224 100644 --- a/modules/genesis_bootstrapper/src/genesis_bootstrapper.rs +++ b/modules/genesis_bootstrapper/src/genesis_bootstrapper.rs @@ -7,8 +7,8 @@ use acropolis_common::{ CardanoMessage, GenesisCompleteMessage, GenesisUTxOsMessage, Message, PotDeltasMessage, UTXODeltasMessage, }, - Address, BlockHash, BlockInfo, BlockStatus, ByronAddress, Era, Lovelace, LovelaceDelta, Pot, - PotDelta, TxIdentifier, TxOutRef, TxOutput, UTXODelta, UTxOIdentifier, Value, + Address, BlockHash, BlockInfo, BlockStatus, ByronAddress, Era, Lovelace, LovelaceDelta, + NetworkId, Pot, PotDelta, TxIdentifier, TxOutRef, TxOutput, UTXODelta, UTxOIdentifier, Value, }; use anyhow::Result; use blake2::{digest::consts::U32, Blake2b, Digest}; @@ -128,6 +128,7 @@ impl GenesisBootstrapper { hash: BlockHash::default(), epoch: 0, epoch_slot: 0, + network_id: NetworkId::from(network_name), new_epoch: false, timestamp: byron_genesis.start_time, era: Era::Byron, diff --git a/modules/governance_state/src/alonzo_babbage_voting.rs b/modules/governance_state/src/alonzo_babbage_voting.rs index bc90a45b..b20a403c 100644 --- a/modules/governance_state/src/alonzo_babbage_voting.rs +++ b/modules/governance_state/src/alonzo_babbage_voting.rs @@ -113,7 +113,7 @@ mod tests { use crate::alonzo_babbage_voting::AlonzoBabbageVoting; use acropolis_common::{ rational_number::rational_number_from_f32, AlonzoBabbageUpdateProposal, - AlonzoBabbageVotingOutcome, BlockHash, BlockInfo, BlockStatus, GenesisKeyhash, + AlonzoBabbageVotingOutcome, BlockHash, BlockInfo, BlockStatus, GenesisKeyhash, NetworkId, ProtocolParamUpdate, }; use anyhow::Result; @@ -154,6 +154,7 @@ mod tests { epoch, epoch_slot: 0, era: era.try_into()?, + network_id: NetworkId::default(), new_epoch: new_epoch != 0, timestamp: 0, hash: BlockHash::default(), diff --git a/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs b/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs index 20804f2f..7e6fafe7 100644 --- a/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs +++ b/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs @@ -4,7 +4,7 @@ use acropolis_common::{ genesis_values::GenesisValues, messages::{BlockBodyMessage, BlockHeaderMessage, CardanoMessage, Message}, - BlockInfo, BlockStatus, Era, + BlockInfo, BlockStatus, Era, NetworkId, }; use anyhow::{anyhow, bail, Result}; use caryatid_sdk::{module, Context, Module}; @@ -45,6 +45,7 @@ const DEFAULT_PAUSE: (&str, PauseType) = ("pause", PauseType::NoPause); const DEFAULT_DOWNLOAD_MAX_AGE: &str = "download-max-age"; const DEFAULT_DIRECTORY: &str = "downloads"; const SNAPSHOT_METADATA_FILE: &str = "snapshot_metadata.json"; +const DEFAULT_NETWORK_ID: &str = "mainnet"; /// Mithril feedback receiver struct FeedbackLogger { @@ -307,6 +308,12 @@ impl MithrilSnapshotFetcher { let timestamp = genesis.slot_to_timestamp(slot); + let network_id = NetworkId::from( + config + .get_string("network-id") + .unwrap_or(DEFAULT_NETWORK_ID.to_string()), + ); + let era = match block.era() { PallasEra::Byron => Era::Byron, PallasEra::Shelley => Era::Shelley, @@ -328,6 +335,7 @@ impl MithrilSnapshotFetcher { epoch, epoch_slot, new_epoch, + network_id, timestamp, era, }; diff --git a/modules/rest_blockfrost/src/handlers/governance.rs b/modules/rest_blockfrost/src/handlers/governance.rs index 0563ddc8..5d0ef2d6 100644 --- a/modules/rest_blockfrost/src/handlers/governance.rs +++ b/modules/rest_blockfrost/src/handlers/governance.rs @@ -4,10 +4,14 @@ use crate::types::{ DRepInfoREST, DRepMetadataREST, DRepUpdateREST, DRepVoteREST, DRepsListREST, ProposalVoteREST, VoterRoleREST, }; -use acropolis_common::{messages::{Message, RESTResponse, StateQuery, StateQueryResponse}, queries::{ - accounts::{AccountsStateQuery, AccountsStateQueryResponse}, - governance::{GovernanceStateQuery, GovernanceStateQueryResponse}, -}, Credential, GovActionId, StakeAddress, TxHash, Voter}; +use acropolis_common::{ + messages::{Message, RESTResponse, StateQuery, StateQueryResponse}, + queries::{ + accounts::{AccountsStateQuery, AccountsStateQueryResponse}, + governance::{GovernanceStateQuery, GovernanceStateQueryResponse}, + }, + Credential, GovActionId, StakeAddress, TxHash, Voter, +}; use anyhow::Result; use caryatid_sdk::Context; use reqwest::Client; @@ -95,8 +99,11 @@ pub async fn handle_single_drep_blockfrost( )) => { let active = !response.info.retired && !response.info.expired; - let stake_addresses = - response.delegators.iter().map(|addr| addr.to_stake_address(None)).collect(); + let stake_addresses = response + .delegators + .iter() + .map(|addr| addr.to_stake_address(handlers_config.network_id.clone().into())) + .collect(); let sum_msg = Arc::new(Message::StateQuery(StateQuery::Accounts( AccountsStateQuery::GetAccountsBalancesSum { stake_addresses }, @@ -203,8 +210,8 @@ pub async fn handle_drep_delegators_blockfrost( }; let hash = addr.get_hash(); - // TODO: NETWORK ID - let stake_address = addr.to_stake_address(None); + let stake_address = + addr.to_stake_address(handlers_config.network_id.clone().into()); stake_addresses.push(stake_address.clone()); stake_key_to_bech32.insert(hash, bech32); } diff --git a/modules/rest_blockfrost/src/handlers/pools.rs b/modules/rest_blockfrost/src/handlers/pools.rs index eaa27403..bad519a4 100644 --- a/modules/rest_blockfrost/src/handlers/pools.rs +++ b/modules/rest_blockfrost/src/handlers/pools.rs @@ -667,7 +667,7 @@ async fn handle_pools_spo_blockfrost( stake_addresses: pool_info .pool_owners .iter() - .map(|owner| owner.to_stake_address(None)) + .map(|owner| owner.to_stake_address(handlers_config.network_id.clone().into())) .collect(), }, ))); diff --git a/modules/rest_blockfrost/src/handlers_config.rs b/modules/rest_blockfrost/src/handlers_config.rs index 53ea6b79..0dbf22fc 100644 --- a/modules/rest_blockfrost/src/handlers_config.rs +++ b/modules/rest_blockfrost/src/handlers_config.rs @@ -9,10 +9,13 @@ use acropolis_common::queries::{ pools::DEFAULT_POOLS_QUERY_TOPIC, spdd::DEFAULT_SPDD_QUERY_TOPIC, }; +use acropolis_common::NetworkId; use config::Config; const DEFAULT_EXTERNAL_API_TIMEOUT: (&str, i64) = ("external_api_timeout", 3); // 3 seconds +const DEFAULT_NETWORK_ID: (&str, &str) = ("network", "mainnet"); + #[derive(Clone)] pub struct HandlersConfig { pub accounts_query_topic: String, @@ -25,6 +28,7 @@ pub struct HandlersConfig { pub parameters_query_topic: String, pub external_api_timeout: u64, pub offchain_token_registry_url: String, + pub network_id: NetworkId, } impl From> for HandlersConfig { @@ -69,6 +73,11 @@ impl From> for HandlersConfig { .get_string(DEFAULT_OFFCHAIN_TOKEN_REGISTRY_URL.0) .unwrap_or(DEFAULT_OFFCHAIN_TOKEN_REGISTRY_URL.1.to_string()); + let network_id = config + .get_string(DEFAULT_NETWORK_ID.0) + .unwrap_or(DEFAULT_NETWORK_ID.1.to_string()) + .into(); + Self { accounts_query_topic, assets_query_topic, @@ -80,6 +89,7 @@ impl From> for HandlersConfig { parameters_query_topic, external_api_timeout, offchain_token_registry_url, + network_id, } } } diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 059ff70b..de8a2c05 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -1,9 +1,19 @@ //! Acropolis SPOState: State storage -use acropolis_common::{crypto::keyhash_224, ledger_state::SPOState, messages::{ - CardanoMessage, Message, SPOStateMessage, StakeAddressDeltasMessage, - StakeRewardDeltasMessage, TxCertificatesMessage, WithdrawalsMessage, -}, params::TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH, queries::governance::VoteRecord, stake_addresses::StakeAddressMap, BlockInfo, KeyHash, PoolMetadata, PoolRegistration, PoolRegistrationWithPos, PoolRetirement, PoolRetirementWithPos, PoolUpdateEvent, Relay, StakeAddress, StakeCredential, TxCertificate, TxHash, Voter, VotingProcedures}; +use acropolis_common::{ + crypto::keyhash_224, + ledger_state::SPOState, + messages::{ + CardanoMessage, Message, SPOStateMessage, StakeAddressDeltasMessage, + StakeRewardDeltasMessage, TxCertificatesMessage, WithdrawalsMessage, + }, + params::TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH, + queries::governance::VoteRecord, + stake_addresses::StakeAddressMap, + BlockInfo, KeyHash, NetworkId, PoolMetadata, PoolRegistration, PoolRegistrationWithPos, + PoolRetirement, PoolRetirementWithPos, PoolUpdateEvent, Relay, StakeAddress, StakeCredential, + TxCertificate, TxHash, Voter, VotingProcedures, +}; use anyhow::Result; use imbl::HashMap; use std::sync::{Arc, Mutex}; @@ -20,6 +30,8 @@ pub struct State { epoch: u64, + network_id: NetworkId, + spos: HashMap, PoolRegistration>, pending_updates: HashMap, PoolRegistration>, @@ -48,6 +60,7 @@ impl State { pending_updates: HashMap::new(), pending_deregistrations: HashMap::new(), total_blocks_minted: HashMap::new(), + network_id: NetworkId::default(), historical_spos: if config.store_historical_state() { Some(HashMap::new()) } else { @@ -98,6 +111,7 @@ impl From for State { store_config: StoreConfig::default(), block: 0, epoch: 0, + network_id: NetworkId::default(), spos, pending_updates: value.updates.into(), pending_deregistrations, @@ -432,16 +446,18 @@ impl State { return; }; let mut stake_addresses = stake_addresses.lock().unwrap(); - stake_addresses.register_stake_address(&credential.to_stake_address(None)); + stake_addresses + .register_stake_address(&credential.to_stake_address(self.network_id.clone().into())); } fn deregister_stake_address(&mut self, credential: &StakeCredential) { let Some(stake_addresses) = self.stake_addresses.as_ref() else { return; }; - let stake_address = credential.to_stake_address(None); + let stake_address = credential.to_stake_address(self.network_id.clone().into()); let mut stake_addresses = stake_addresses.lock().unwrap(); - let old_spo = stake_addresses.get(&stake_address).map(|s| s.delegated_spo.clone()).flatten(); + let old_spo = + stake_addresses.get(&stake_address).map(|s| s.delegated_spo.clone()).flatten(); if stake_addresses.deregister_stake_address(&stake_address) { // update historical_spos @@ -449,7 +465,9 @@ impl State { if let Some(old_spo) = old_spo.as_ref() { // remove delegators from old_spo if let Some(historical_spo) = historical_spos.get_mut(old_spo) { - if let Some(removed) = historical_spo.remove_delegator(&credential.to_stake_address(None)) { + if let Some(removed) = historical_spo.remove_delegator( + &credential.to_stake_address(self.network_id.clone().into()), + ) { if !removed { error!( "Historical SPO state for {} does not contain delegator {}", @@ -471,9 +489,10 @@ impl State { return; }; let hash = credential.get_hash(); - let stake_address = credential.to_stake_address(None); + let stake_address = credential.to_stake_address(self.network_id.clone().into()); let mut stake_addresses = stake_addresses.lock().unwrap(); - let old_spo = stake_addresses.get(&stake_address).map(|s| s.delegated_spo.clone()).flatten(); + let old_spo = + stake_addresses.get(&stake_address).map(|s| s.delegated_spo.clone()).flatten(); if stake_addresses.record_stake_delegation(&stake_address, spo) { // update historical_spos @@ -653,7 +672,10 @@ impl State { for delta in reward_deltas_msg.deltas.iter() { let mut stake_addresses = stake_addresses.lock().unwrap(); if let Err(e) = stake_addresses.update_reward(&delta.stake_address, delta.delta) { - error!("Updating reward account {}: {e}", hex::encode(&delta.stake_address.get_hash())); + error!( + "Updating reward account {}: {e}", + hex::encode(&delta.stake_address.get_hash()) + ); } } @@ -677,10 +699,7 @@ mod tests { use tokio::sync::Mutex; fn pool_owners_from_bytes(owners: Vec>) -> Vec { - owners - .into_iter() - .map(|bytes| Credential::AddrKeyHash(bytes)) - .collect() + owners.into_iter().map(|bytes| Credential::AddrKeyHash(bytes)).collect() } fn default_pool_registration(operator: Vec) -> PoolRegistration { diff --git a/modules/spo_state/src/test_utils.rs b/modules/spo_state/src/test_utils.rs index 2e8772d8..92be798f 100644 --- a/modules/spo_state/src/test_utils.rs +++ b/modules/spo_state/src/test_utils.rs @@ -1,9 +1,6 @@ -use acropolis_common::{ - messages::{ - EpochActivityMessage, SPORewardsMessage, SPOStakeDistributionMessage, TxCertificatesMessage, - }, - BlockHash, BlockInfo, BlockStatus, Era, TxCertificate, -}; +use acropolis_common::{messages::{ + EpochActivityMessage, SPORewardsMessage, SPOStakeDistributionMessage, TxCertificatesMessage, +}, BlockHash, BlockInfo, BlockStatus, Era, NetworkId, TxCertificate}; use crate::store_config::StoreConfig; @@ -68,6 +65,7 @@ pub fn new_block(epoch: u64) -> BlockInfo { epoch, epoch_slot: 0, new_epoch: true, + network_id: NetworkId::default(), timestamp: epoch, era: Era::Byron, } diff --git a/modules/stake_delta_filter/src/utils.rs b/modules/stake_delta_filter/src/utils.rs index 334df1cd..7ce5b96e 100644 --- a/modules/stake_delta_filter/src/utils.rs +++ b/modules/stake_delta_filter/src/utils.rs @@ -401,8 +401,9 @@ mod test { use crate::*; use acropolis_common::{ messages::AddressDeltasMessage, Address, AddressDelta, BlockHash, BlockInfo, BlockStatus, - ByronAddress, Era, ShelleyAddress, ShelleyAddressDelegationPart, ShelleyAddressPaymentPart, - ShelleyAddressPointer, StakeAddress, StakeAddressPayload, ValueDelta, + ByronAddress, Era, NetworkId, ShelleyAddress, ShelleyAddressDelegationPart, + ShelleyAddressPaymentPart, ShelleyAddressPointer, StakeAddress, StakeAddressPayload, + ValueDelta, }; use bech32::{Bech32, Hrp}; @@ -541,6 +542,7 @@ mod test { epoch: 1, epoch_slot: 14243, new_epoch: true, + network_id: NetworkId::default(), timestamp: 2498243, era: Era::Conway, }; diff --git a/modules/upstream_chain_fetcher/src/body_fetcher.rs b/modules/upstream_chain_fetcher/src/body_fetcher.rs index afa2a5ee..21a2ed95 100644 --- a/modules/upstream_chain_fetcher/src/body_fetcher.rs +++ b/modules/upstream_chain_fetcher/src/body_fetcher.rs @@ -1,10 +1,7 @@ //! Acropolis Miniprotocols module for Caryatid //! Multi-connection, block body fetching part of the client (in separate thread). -use acropolis_common::{ - messages::{BlockBodyMessage, BlockHeaderMessage}, - BlockInfo, BlockStatus, Era, -}; +use acropolis_common::{messages::{BlockBodyMessage, BlockHeaderMessage}, BlockInfo, BlockStatus, Era, NetworkId}; use anyhow::{bail, Result}; use crossbeam::channel::{Receiver, TryRecvError}; use pallas::{ @@ -111,6 +108,7 @@ impl BodyFetcher { None => true, }; let timestamp = self.cfg.slot_to_timestamp(slot); + let network_id = NetworkId::from(self.cfg.network_id.clone()); Ok(BlockInfo { status: if rolled_back { @@ -123,6 +121,7 @@ impl BodyFetcher { hash, epoch, epoch_slot, + network_id, new_epoch, timestamp, era, diff --git a/modules/upstream_chain_fetcher/src/upstream_cache.rs b/modules/upstream_chain_fetcher/src/upstream_cache.rs index 9281a358..8be89694 100644 --- a/modules/upstream_chain_fetcher/src/upstream_cache.rs +++ b/modules/upstream_chain_fetcher/src/upstream_cache.rs @@ -170,10 +170,7 @@ impl Storage for FileStorage { #[cfg(test)] mod test { use crate::upstream_cache::{Storage, UpstreamCacheImpl, UpstreamCacheRecord}; - use acropolis_common::{ - messages::{BlockBodyMessage, BlockHeaderMessage}, - BlockHash, BlockInfo, BlockStatus, Era, - }; + use acropolis_common::{messages::{BlockBodyMessage, BlockHeaderMessage}, BlockHash, BlockInfo, BlockStatus, Era, NetworkId}; use anyhow::Result; use std::{collections::HashMap, sync::Arc}; @@ -186,6 +183,7 @@ mod test { epoch: 0, epoch_slot: n, new_epoch: false, + network_id: NetworkId::default(), timestamp: n, era: Era::default(), } diff --git a/modules/upstream_chain_fetcher/src/utils.rs b/modules/upstream_chain_fetcher/src/utils.rs index dd3ee50b..1010a695 100644 --- a/modules/upstream_chain_fetcher/src/utils.rs +++ b/modules/upstream_chain_fetcher/src/utils.rs @@ -1,6 +1,7 @@ use crate::UpstreamCacheRecord; use acropolis_common::genesis_values::GenesisValues; use acropolis_common::messages::{CardanoMessage, Message}; +use acropolis_common::NetworkId; use anyhow::{anyhow, bail, Result}; use caryatid_sdk::Context; use config::Config; @@ -19,6 +20,7 @@ const DEFAULT_GENESIS_COMPLETION_TOPIC: (&str, &str) = const DEFAULT_NODE_ADDRESS: (&str, &str) = ("node-address", "backbone.cardano.iog.io:3001"); const DEFAULT_MAGIC_NUMBER: (&str, u64) = ("magic-number", 764824073); +const DEFAULT_NETWORK_ID: (&str, &str) = ("network-id", "mainnet"); const DEFAULT_SYNC_POINT: (&str, SyncPoint) = ("sync-point", SyncPoint::Snapshot); const DEFAULT_CACHE_DIR: (&str, &str) = ("cache-dir", "upstream-cache"); @@ -49,6 +51,7 @@ pub struct FetcherConfig { pub genesis_completion_topic: String, pub node_address: String, pub magic_number: u64, + pub network_id: String, pub cache_dir: String, pub genesis_values: Option, @@ -108,6 +111,9 @@ impl FetcherConfig { magic_number: config .get::(DEFAULT_MAGIC_NUMBER.0) .unwrap_or(DEFAULT_MAGIC_NUMBER.1), + network_id: config + .get_string(DEFAULT_NETWORK_ID.0) + .unwrap_or(DEFAULT_NETWORK_ID.1.to_string()), node_address: Self::conf(&config, DEFAULT_NODE_ADDRESS), cache_dir: Self::conf(&config, DEFAULT_CACHE_DIR), genesis_values: Self::conf_genesis(&config), @@ -140,8 +146,8 @@ pub async fn publish_message(cfg: Arc, record: &UpstreamCacheReco pub async fn peer_connect(cfg: Arc, role: &str) -> Result> { info!( - "Connecting {role} to {} ({}) ...", - cfg.node_address, cfg.magic_number + "Connecting {role} to {} ({})-({}) ...", + cfg.node_address, cfg.network_id, cfg.magic_number ); match PeerClient::connect(cfg.node_address.clone(), cfg.magic_number).await { @@ -156,8 +162,9 @@ pub async fn peer_connect(cfg: Arc, role: &str) -> Result bail!( - "Cannot connect {role} to {} ({}): {e}", + "Cannot connect {role} to {} ({})-({}): {e}", cfg.node_address, + cfg.network_id, cfg.magic_number ), } diff --git a/modules/utxo_state/src/state.rs b/modules/utxo_state/src/state.rs index b3e03d55..0c703a90 100644 --- a/modules/utxo_state/src/state.rs +++ b/modules/utxo_state/src/state.rs @@ -373,7 +373,7 @@ impl State { mod tests { use super::*; use crate::InMemoryImmutableUTXOStore; - use acropolis_common::{AssetName, BlockHash, ByronAddress, Era, NativeAsset, Value}; + use acropolis_common::{AssetName, BlockHash, ByronAddress, Era, NativeAsset, NetworkId, Value}; use config::Config; use tokio::sync::Mutex; @@ -394,6 +394,7 @@ mod tests { epoch_slot: slot, new_epoch: false, timestamp: slot, + network_id: NetworkId::default(), era: Era::Byron, } } diff --git a/processes/golden_tests/src/test_module.rs b/processes/golden_tests/src/test_module.rs index 61377e6b..f12e1ed6 100644 --- a/processes/golden_tests/src/test_module.rs +++ b/processes/golden_tests/src/test_module.rs @@ -1,11 +1,7 @@ -use acropolis_common::{ - ledger_state::LedgerState, - messages::{ - CardanoMessage, Message, RawTxsMessage, SnapshotDumpMessage, SnapshotMessage, - SnapshotStateMessage, - }, - BlockHash, BlockInfo, BlockStatus, Era, -}; +use acropolis_common::{ledger_state::LedgerState, messages::{ + CardanoMessage, Message, RawTxsMessage, SnapshotDumpMessage, SnapshotMessage, + SnapshotStateMessage, +}, BlockHash, BlockInfo, BlockStatus, Era, NetworkId}; use anyhow::{Context as AnyhowContext, Result}; use caryatid_sdk::{module, Context, Module}; use config::Config; @@ -47,6 +43,7 @@ impl TestModule { slot: 1, number: 1, hash: BlockHash::default(), + network_id: NetworkId::default(), epoch: 1, epoch_slot: 1, new_epoch: false, diff --git a/processes/omnibus/omnibus.toml b/processes/omnibus/omnibus.toml index 4fa7998e..6861a9c9 100644 --- a/processes/omnibus/omnibus.toml +++ b/processes/omnibus/omnibus.toml @@ -9,15 +9,18 @@ genesis-key = "5b3139312c36362c3134302c3138352c3133382c31312c3233372c3230372c323 download-max-age = "never" # Pause constraint E.g. "epoch:100", "block:1200" pause = "none" +network = "mainnet" [module.upstream-chain-fetcher] sync-point = "snapshot" node-address = "backbone.cardano.iog.io:3001" magic-number = 764824073 +network = "mainnet" [module.block-unpacker] [module.rest-blockfrost] +network = "mainnet" [module.tx-unpacker] publish-utxo-deltas-topic = "cardano.utxo.deltas" From f8a97b4bd72c06cc18e63d95d2d9145837c71ae8 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 22 Oct 2025 12:51:13 -0700 Subject: [PATCH 23/27] Refactor: Remove `NetworkId` from `BlockInfo` and standardize `StakeAddress` handling in tx_unpacker.rs - Removed `NetworkId` from structures and functions across modules. - Replaced `KeyHash` and `Credential` usage with `StakeAddress` for consistency. --- common/src/queries/governance.rs | 6 +- common/src/state_history.rs | 2 +- common/src/types.rs | 43 +++---- modules/accounts_state/src/rewards.rs | 3 +- modules/accounts_state/src/snapshot.rs | 16 +-- modules/accounts_state/src/state.rs | 114 ++++++++---------- modules/drep_state/src/drep_state.rs | 1 + modules/drep_state/src/state.rs | 60 +++++---- modules/epochs_state/src/epochs_history.rs | 3 +- modules/epochs_state/src/state.rs | 5 +- .../src/genesis_bootstrapper.rs | 5 +- .../src/alonzo_babbage_voting.rs | 3 +- .../src/mithril_snapshot_fetcher.rs | 10 +- .../src/handlers/governance.rs | 46 +++---- modules/rest_blockfrost/src/handlers/pools.rs | 8 +- .../rest_blockfrost/src/handlers_config.rs | 10 -- modules/spo_state/src/state.rs | 74 ++++++------ modules/spo_state/src/test_utils.rs | 3 +- modules/stake_delta_filter/src/state.rs | 15 +-- modules/stake_delta_filter/src/utils.rs | 3 +- modules/tx_unpacker/src/map_parameters.rs | 65 +++++++--- modules/tx_unpacker/src/tx_unpacker.rs | 5 +- .../src/body_fetcher.rs | 4 +- .../src/upstream_cache.rs | 9 +- modules/upstream_chain_fetcher/src/utils.rs | 1 - modules/utxo_state/src/state.rs | 3 +- processes/golden_tests/src/test_module.rs | 3 +- processes/omnibus/omnibus.toml | 4 +- 28 files changed, 247 insertions(+), 277 deletions(-) diff --git a/common/src/queries/governance.rs b/common/src/queries/governance.rs index be6b1240..678b35a9 100644 --- a/common/src/queries/governance.rs +++ b/common/src/queries/governance.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{Anchor, Credential, DRepCredential, GovActionId, Lovelace, ProposalProcedure, TxHash, Vote, Voter, VotingProcedure}; +use crate::{Anchor, DRepCredential, GovActionId, Lovelace, ProposalProcedure, StakeAddress, TxHash, Vote, Voter, VotingProcedure}; pub const DEFAULT_DREPS_QUERY_TOPIC: (&str, &str) = ("drep-state-query-topic", "cardano.query.dreps"); @@ -58,12 +58,12 @@ pub struct DRepInfo { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DRepInfoWithDelegators { pub info: DRepInfo, - pub delegators: Vec, + pub delegators: Vec, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DRepDelegatorAddresses { - pub addresses: Vec, + pub addresses: Vec, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] diff --git a/common/src/state_history.rs b/common/src/state_history.rs index 9e19a4c7..c8cf2979 100644 --- a/common/src/state_history.rs +++ b/common/src/state_history.rs @@ -54,7 +54,7 @@ impl StateHistory { /// Get the current state assuming any rollback has been done /// Cloned for modification - call commit() when done - pub fn get_current_state(&self) -> S { + pub fn get_current_state(&self) -> S { self.history.back().map(|entry| entry.state.clone()).unwrap_or_default() } diff --git a/common/src/types.rs b/common/src/types.rs index 3f31598c..592f6204 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -130,9 +130,6 @@ pub struct BlockInfo { #[serde(default)] pub timestamp: u64, - /// Network ID - pub network_id: NetworkId, - /// Protocol era pub era: Era, } @@ -752,7 +749,7 @@ pub struct PoolRegistration { /// Pool owners by their key hash // #[serde_as(as = "Vec")] #[n(6)] - pub pool_owners: Vec, + pub pool_owners: Vec, // Relays #[n(7)] @@ -837,8 +834,8 @@ pub struct PoolEpochState { /// Stake delegation data #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeDelegation { - /// Stake credential - pub credential: StakeCredential, + /// Stake address + pub stake_address: StakeAddress, /// Pool ID to delegate to pub operator: KeyHash, @@ -907,8 +904,8 @@ pub struct MoveInstantaneousReward { /// Register stake (Conway version) = 'reg_cert' #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Registration { - /// Stake credential - pub credential: StakeCredential, + /// Stake address + pub stake_address: StakeAddress, /// Deposit paid pub deposit: Lovelace, @@ -917,8 +914,8 @@ pub struct Registration { /// Deregister stake (Conway version) = 'unreg_cert' #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Deregistration { - /// Stake credential - pub credential: StakeCredential, + /// Stake address + pub stake_address: StakeAddress, /// Deposit to be refunded pub refund: Lovelace, @@ -943,8 +940,8 @@ pub enum DRepChoice { /// Vote delegation (simple, existing registration) = vote_deleg_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct VoteDelegation { - /// Stake credential - pub credential: StakeCredential, + /// Stake address + pub stake_address: StakeAddress, // DRep choice pub drep: DRepChoice, @@ -953,8 +950,8 @@ pub struct VoteDelegation { /// Stake+vote delegation (to SPO and DRep) = stake_vote_deleg_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeAndVoteDelegation { - /// Stake credential - pub credential: StakeCredential, + /// Stake address + pub stake_address: StakeAddress, /// Pool pub operator: KeyHash, @@ -966,8 +963,8 @@ pub struct StakeAndVoteDelegation { /// Stake delegation to SPO + registration = stake_reg_deleg_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeRegistrationAndDelegation { - /// Stake credential - pub credential: StakeCredential, + /// Stake address + pub stake_address: StakeAddress, /// Pool pub operator: KeyHash, @@ -979,8 +976,8 @@ pub struct StakeRegistrationAndDelegation { /// Vote delegation to DRep + registration = vote_reg_deleg_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeRegistrationAndVoteDelegation { - /// Stake credential - pub credential: StakeCredential, + /// Stake address + pub stake_address: StakeAddress, /// DRep choice pub drep: DRepChoice, @@ -995,7 +992,7 @@ pub struct StakeRegistrationAndVoteDelegation { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeRegistrationAndStakeAndVoteDelegation { /// Stake credential - pub credential: StakeCredential, + pub stake_address: StakeAddress, /// Pool pub operator: KeyHash, @@ -1673,8 +1670,8 @@ pub struct GovernanceOutcome { } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct StakeCredentialWithPos { - pub stake_credential: StakeCredential, +pub struct StakeAddressWithPos { + pub stake_address: StakeAddress, pub tx_index: u64, pub cert_index: u64, } @@ -1686,10 +1683,10 @@ pub enum TxCertificate { None(()), /// Stake registration - StakeRegistration(StakeCredentialWithPos), + StakeRegistration(StakeAddressWithPos), /// Stake de-registration - StakeDeregistration(StakeCredential), + StakeDeregistration(StakeAddress), /// Stake Delegation to a pool StakeDelegation(StakeDelegation), diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index 4b9bd8dd..06528a86 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -335,7 +335,6 @@ fn calculate_spo_rewards( if !to_delegators.is_zero() { let total_stake = BigDecimal::from(spo.total_stake); for (delegator_stake_address, stake) in &spo.delegators { - let delegator_credential = delegator_stake_address.get_credential(); let proportion = BigDecimal::from(stake) / &total_stake; // and hence how much of the total reward they get @@ -346,7 +345,7 @@ fn calculate_spo_rewards( delegator_stake_address); // Pool owners don't get member rewards (seems unfair!) - if spo.pool_owners.contains(&delegator_credential) { + if spo.pool_owners.contains(&delegator_stake_address) { debug!( "Skipping pool owner reward account {}, losing {to_pay}", delegator_stake_address diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index b9ecfe1b..81e09f46 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -1,7 +1,7 @@ //! Acropolis AccountsState: snapshot for rewards calculations use crate::state::{Pots, RegistrationChange}; -use acropolis_common::{stake_addresses::StakeAddressMap, Credential, KeyHash, Lovelace, PoolRegistration, Ratio, StakeAddress}; +use acropolis_common::{stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, StakeAddress}; use imbl::OrdMap; use std::collections::HashMap; use std::sync::Arc; @@ -35,7 +35,7 @@ pub struct SnapshotSPO { pub two_previous_reward_account_is_registered: bool, /// Pool owners - pub pool_owners: Vec, + pub pool_owners: Vec, } /// Snapshot of stake distribution taken at the end of an particular epoch @@ -159,7 +159,7 @@ impl Snapshot { pub fn get_stake_delegated_to_spo_by_addresses( &self, spo: &KeyHash, - addresses: &[Credential], + addresses: &[StakeAddress], ) -> Lovelace { let Some(snapshot_spo) = self.spos.get(spo) else { return 0; @@ -170,7 +170,7 @@ impl Snapshot { .delegators .iter() .filter_map(|(address, amount)| { - if address_set.contains(&address.get_credential()) { + if address_set.contains(&address) { Some(*amount) } else { None @@ -321,9 +321,9 @@ mod tests { // Extract key hashes from stake addresses for the API call let addresses = vec![ - addr2.get_credential(), - addr3.get_credential(), - addr4.get_credential(), + addr2, + addr3, + addr4, ]; let result = snapshot.get_stake_delegated_to_spo_by_addresses(&spo1, &addresses); assert_eq!(result, 500); @@ -347,7 +347,7 @@ mod tests { ); // Extract key hash from stake address for the API call - let addresses = vec![addr_x.get_credential()]; + let addresses = vec![addr_x]; let result = snapshot.get_stake_delegated_to_spo_by_addresses(&spo1, &addresses); assert_eq!(result, 0); } diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 086035fa..51010c9d 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -15,8 +15,8 @@ use acropolis_common::{ stake_addresses::{StakeAddressMap, StakeAddressState}, BlockInfo, DRepChoice, DRepCredential, DelegatedStake, InstantaneousRewardSource, InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, NetworkId, - PoolLiveStakeInfo, PoolRegistration, Pot, SPORewards, StakeAddress, StakeCredential, - StakeRewardDelta, TxCertificate, + PoolLiveStakeInfo, PoolRegistration, Pot, SPORewards, StakeAddress, StakeRewardDelta, + TxCertificate, }; use anyhow::Result; use imbl::OrdMap; @@ -795,10 +795,7 @@ impl State { } /// Register a stake address, with a specified deposit if known - fn register_stake_address(&mut self, credential: &StakeCredential, deposit: Option) { - - let stake_address = credential.to_stake_address(self.network_id.clone().into()); - + fn register_stake_address(&mut self, stake_address: &StakeAddress, deposit: Option) { // Stake addresses can be registered after being used in UTXOs let mut stake_addresses = self.stake_addresses.lock().unwrap(); if stake_addresses.register_stake_address(&stake_address) { @@ -820,16 +817,13 @@ impl State { // Add to registration changes self.current_epoch_registration_changes.lock().unwrap().push(RegistrationChange { - address: stake_address, + address: stake_address.clone(), kind: RegistrationChangeKind::Registered, }); } /// Deregister a stake address, with specified refund if known - fn deregister_stake_address(&mut self, credential: &StakeCredential, refund: Option) { - - let stake_address = credential.to_stake_address(self.network_id.clone().into()); - + fn deregister_stake_address(&mut self, stake_address: &StakeAddress, refund: Option) { // Check if it existed let mut stake_addresses = self.stake_addresses.lock().unwrap(); if stake_addresses.deregister_stake_address(&stake_address) { @@ -852,7 +846,7 @@ impl State { // Add to registration changes self.current_epoch_registration_changes.lock().unwrap().push(RegistrationChange { - address: stake_address, + address: stake_address.clone(), kind: RegistrationChangeKind::Deregistered, }); } @@ -863,9 +857,7 @@ impl State { } /// Record a stake delegation - fn record_stake_delegation(&mut self, credential: &StakeCredential, spo: &KeyHash) { - - let stake_address = credential.to_stake_address(self.network_id.clone().into()); + fn record_stake_delegation(&mut self, stake_address: &StakeAddress, spo: &KeyHash) { let mut stake_addresses = self.stake_addresses.lock().unwrap(); stake_addresses.record_stake_delegation(&stake_address, spo); } @@ -877,9 +869,7 @@ impl State { } /// record a drep delegation - fn record_drep_delegation(&mut self, credential: &StakeCredential, drep: &DRepChoice) { - - let stake_address = credential.to_stake_address(self.network_id.clone().into()); + fn record_drep_delegation(&mut self, stake_address: &StakeAddress, drep: &DRepChoice) { let mut stake_addresses = self.stake_addresses.lock().unwrap(); stake_addresses.record_drep_delegation(&stake_address, drep); } @@ -889,12 +879,12 @@ impl State { // Handle certificates for tx_cert in tx_certs_msg.certificates.iter() { match tx_cert { - TxCertificate::StakeRegistration(sc_with_pos) => { - self.register_stake_address(&sc_with_pos.stake_credential, None); + TxCertificate::StakeRegistration(stake_address_with_pos) => { + self.register_stake_address(&stake_address_with_pos.stake_address, None); } - TxCertificate::StakeDeregistration(sc) => { - self.deregister_stake_address(&sc, None); + TxCertificate::StakeDeregistration(stake_address) => { + self.deregister_stake_address(&stake_address, None); } TxCertificate::MoveInstantaneousReward(mir) => { @@ -902,40 +892,49 @@ impl State { } TxCertificate::Registration(reg) => { - self.register_stake_address(®.credential, Some(reg.deposit)); + self.register_stake_address(®.stake_address, Some(reg.deposit)); } TxCertificate::Deregistration(dreg) => { - self.deregister_stake_address(&dreg.credential, Some(dreg.refund)); + self.deregister_stake_address(&dreg.stake_address, Some(dreg.refund)); } TxCertificate::StakeDelegation(delegation) => { - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); } TxCertificate::VoteDelegation(delegation) => { - self.record_drep_delegation(&delegation.credential, &delegation.drep); + self.record_drep_delegation(&delegation.stake_address, &delegation.drep); } TxCertificate::StakeAndVoteDelegation(delegation) => { - self.record_stake_delegation(&delegation.credential, &delegation.operator); - self.record_drep_delegation(&delegation.credential, &delegation.drep); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); + self.record_drep_delegation(&delegation.stake_address, &delegation.drep); } TxCertificate::StakeRegistrationAndDelegation(delegation) => { - self.register_stake_address(&delegation.credential, Some(delegation.deposit)); - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.register_stake_address( + &delegation.stake_address, + Some(delegation.deposit), + ); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); } TxCertificate::StakeRegistrationAndVoteDelegation(delegation) => { - self.register_stake_address(&delegation.credential, Some(delegation.deposit)); - self.record_drep_delegation(&delegation.credential, &delegation.drep); + self.register_stake_address( + &delegation.stake_address, + Some(delegation.deposit), + ); + self.record_drep_delegation(&delegation.stake_address, &delegation.drep); } TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(delegation) => { - self.register_stake_address(&delegation.credential, Some(delegation.deposit)); - self.record_stake_delegation(&delegation.credential, &delegation.operator); - self.record_drep_delegation(&delegation.credential, &delegation.drep); + self.register_stake_address( + &delegation.stake_address, + Some(delegation.deposit), + ); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); + self.record_drep_delegation(&delegation.stake_address, &delegation.drep); } _ => (), @@ -995,8 +994,8 @@ mod tests { use super::*; use acropolis_common::{ protocol_params::ConwayParams, rational_number::RationalNumber, AddressNetwork, Anchor, - Committee, Constitution, CostModel, Credential, DRepVotingThresholds, PoolVotingThresholds, - Pot, PotDelta, Ratio, Registration, StakeAddress, StakeAddressDelta, StakeAddressPayload, + Committee, Constitution, CostModel, DRepVotingThresholds, PoolVotingThresholds, Pot, + PotDelta, Ratio, Registration, StakeAddress, StakeAddressDelta, StakeAddressPayload, StakeAndVoteDelegation, StakeRegistrationAndStakeAndVoteDelegation, StakeRegistrationAndVoteDelegation, VoteDelegation, Withdrawal, }; @@ -1011,10 +1010,6 @@ mod tests { } } - fn create_stake_credential(hash: &[u8]) -> StakeCredential { - StakeCredential::AddrKeyHash(hash.to_vec()) - } - const STAKE_KEY_HASH: [u8; 3] = [0x99, 0x0f, 0x00]; const DREP_HASH: [u8; 4] = [0xca, 0xfe, 0xd0, 0x0d]; @@ -1024,7 +1019,7 @@ mod tests { let stake_address = create_address(&STAKE_KEY_HASH); // Register first - state.register_stake_address(&StakeCredential::AddrKeyHash(STAKE_KEY_HASH.to_vec()), None); + state.register_stake_address(&stake_address, None); { let stake_addresses = state.stake_addresses.lock().unwrap(); @@ -1111,14 +1106,12 @@ mod tests { // Delegate let addr1 = create_address(&[0x11]); - let cred1 = Credential::AddrKeyHash(addr1.get_hash().to_vec()); - state.register_stake_address(&cred1, None); - state.record_stake_delegation(&cred1, &spo1); + state.register_stake_address(&addr1, None); + state.record_stake_delegation(&addr1, &spo1); let addr2 = create_address(&[0x12]); - let cred2 = Credential::AddrKeyHash(addr2.get_hash().to_vec()); - state.register_stake_address(&cred2, None); - state.record_stake_delegation(&cred2, &spo2); + state.register_stake_address(&addr2, None); + state.record_stake_delegation(&addr2, &spo2); // Put some value in let msg1 = StakeAddressDeltasMessage { @@ -1224,13 +1217,13 @@ mod tests { fn mir_transfers_to_stake_addresses() { let mut state = State::default(); let stake_address = create_address(&STAKE_KEY_HASH); - let stake_credential = create_stake_credential(stake_address.get_hash()); + let stake_credential = stake_address.get_credential(); // Bootstrap with some in reserves state.pots.reserves = 100; // Set up one stake address - state.register_stake_address(&stake_credential, None); + state.register_stake_address(&stake_address, None); let msg = StakeAddressDeltasMessage { deltas: vec![StakeAddressDelta { @@ -1273,13 +1266,13 @@ mod tests { fn withdrawal_transfers_from_stake_addresses() { let mut state = State::default(); let stake_address = create_address(&STAKE_KEY_HASH); - let stake_credential = create_stake_credential(stake_address.get_hash()); + let stake_credential = stake_address.get_credential(); // Bootstrap with some in reserves state.pots.reserves = 100; // Set up one stake address - state.register_stake_address(&stake_credential, None); + state.register_stake_address(&stake_address, None); let msg = StakeAddressDeltasMessage { deltas: vec![StakeAddressDelta { address: stake_address.clone(), @@ -1379,38 +1372,33 @@ mod tests { let spo3 = create_address(&[0x03]); let spo4 = create_address(&[0x04]); - let spo1_credential = create_stake_credential(spo1.get_hash()); - let spo2_credential = create_stake_credential(spo2.get_hash()); - let spo3_credential = create_stake_credential(spo3.get_hash()); - let spo4_credential = create_stake_credential(spo4.get_hash()); - let certificates = vec![ // register the first two SPOs separately from their delegation TxCertificate::Registration(Registration { - credential: spo1_credential.clone(), + stake_address: spo1.clone(), deposit: 1, }), TxCertificate::Registration(Registration { - credential: spo2_credential.clone(), + stake_address: spo2.clone(), deposit: 1, }), TxCertificate::VoteDelegation(VoteDelegation { - credential: spo1_credential.clone(), + stake_address: spo1.clone(), drep: DRepChoice::Key(DREP_HASH.to_vec()), }), TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { - credential: spo2_credential.clone(), + stake_address: spo2.clone(), operator: spo1.get_hash().to_vec(), drep: DRepChoice::Script(DREP_HASH.to_vec()), }), TxCertificate::StakeRegistrationAndVoteDelegation(StakeRegistrationAndVoteDelegation { - credential: spo3_credential.clone(), + stake_address: spo3.clone(), drep: DRepChoice::Abstain, deposit: 1, }), TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( StakeRegistrationAndStakeAndVoteDelegation { - credential: spo4_credential, + stake_address: spo4.clone(), operator: spo1.get_hash().to_vec(), drep: DRepChoice::NoConfidence, deposit: 1, diff --git a/modules/drep_state/src/drep_state.rs b/modules/drep_state/src/drep_state.rs index eaaaf208..ccb97901 100644 --- a/modules/drep_state/src/drep_state.rs +++ b/modules/drep_state/src/drep_state.rs @@ -141,6 +141,7 @@ impl DRepState { context.clone(), &tx_certs_msg.certificates, block_info.epoch, + ) .await .inspect_err(|e| error!("Certificates handling error: {e:#}")) diff --git a/modules/drep_state/src/state.rs b/modules/drep_state/src/state.rs index 7a2c7296..db7a89b1 100644 --- a/modules/drep_state/src/state.rs +++ b/modules/drep_state/src/state.rs @@ -1,10 +1,15 @@ //! Acropolis DRepState: State storage -use acropolis_common::{messages::{Message, StateQuery, StateQueryResponse}, queries::{ - accounts::{AccountsStateQuery, AccountsStateQueryResponse, DEFAULT_ACCOUNTS_QUERY_TOPIC}, - get_query_topic, - governance::{DRepActionUpdate, DRepUpdateEvent, VoteRecord}, -}, Anchor, Credential, DRepChoice, DRepCredential, KeyHash, Lovelace, NetworkId, StakeAddress, StakeCredential, TxCertificate, TxHash, Voter, VotingProcedures}; +use acropolis_common::{ + messages::{Message, StateQuery, StateQueryResponse}, + queries::{ + accounts::{AccountsStateQuery, AccountsStateQueryResponse, DEFAULT_ACCOUNTS_QUERY_TOPIC}, + get_query_topic, + governance::{DRepActionUpdate, DRepUpdateEvent, VoteRecord}, + }, + Anchor, DRepChoice, DRepCredential, KeyHash, Lovelace, + StakeAddress, TxCertificate, TxHash, Voter, VotingProcedures, +}; use anyhow::{anyhow, Result}; use caryatid_sdk::Context; use serde_with::serde_as; @@ -33,7 +38,7 @@ pub struct HistoricalDRepState { // - StakeAndVoteDelegation // - StakeRegistrationAndVoteDelegation // - StakeRegistrationAndStakeAndVoteDelegation - pub delegators: Option>, + pub delegators: Option>, // Populated from voting_procedures in GovernanceProceduresMessage pub votes: Option>, @@ -89,7 +94,6 @@ impl DRepStorageConfig { pub struct State { pub config: DRepStorageConfig, pub dreps: HashMap, - pub network_id: NetworkId, pub historical_dreps: Option>, } @@ -98,7 +102,6 @@ impl State { Self { config, dreps: HashMap::new(), - network_id: NetworkId::default(), historical_dreps: if config.any_enabled() { Some(HashMap::new()) } else { @@ -149,7 +152,7 @@ impl State { pub fn get_drep_delegators( &self, credential: &DRepCredential, - ) -> Result>, &'static str> { + ) -> Result>, &'static str> { let hist = self .historical_dreps .as_ref() @@ -225,7 +228,9 @@ impl State { for tx_cert in tx_certs { if store_delegators { - if let Some((cred, drep)) = Self::extract_delegation_fields(tx_cert) { + if let Some((cred, drep)) = + Self::extract_delegation_fields(tx_cert) + { batched_delegators.push((cred, drep)); continue; } @@ -469,15 +474,12 @@ impl State { async fn update_delegators( &mut self, context: &Arc>, - delegators: Vec<(&StakeCredential, &DRepChoice)>, + delegators: Vec<(StakeAddress, &DRepChoice)>, ) -> Result<()> { - let stake_keys: Vec = delegators.iter().map(|(sc, _)| sc.get_hash()).collect(); - let stake_addresses: Vec = delegators.iter().map(|(k, _) | k.to_stake_address(self.network_id.clone().into()) ).collect(); - let stake_key_to_input: HashMap = delegators - .iter() - .zip(&stake_keys) - .map(|((sc, drep), key)| (key.clone(), (*sc, *drep))) - .collect(); + let stake_addresses = delegators.iter().map(|(addr, _)| (addr).clone()).collect(); + + let stake_key_to_input: HashMap = + delegators.iter().map(|(addr, drep)| (addr.get_hash().to_vec(), (addr, *drep))).collect(); let msg = Arc::new(Message::StateQuery(StateQuery::Accounts( AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_addresses }, @@ -513,7 +515,7 @@ impl State { if old_drep_cred != new_drep_cred { self.update_historical(&old_drep_cred, false, |entry| { if let Some(delegators) = entry.delegators.as_mut() { - delegators.retain(|s| s != delegator); + delegators.retain(|s| s.to_binary() != delegator.to_binary()); } })?; } @@ -523,7 +525,7 @@ impl State { // Add delegator to new DRep match self.update_historical(&new_drep_cred, true, |entry| { if let Some(delegators) = entry.delegators.as_mut() { - if !delegators.contains(delegator) { + if !delegators.contains(&delegator) { delegators.push(delegator.clone()); } } @@ -536,13 +538,21 @@ impl State { Ok(()) } - fn extract_delegation_fields(cert: &TxCertificate) -> Option<(&StakeCredential, &DRepChoice)> { + fn extract_delegation_fields( + cert: &TxCertificate, + ) -> Option<(StakeAddress, &DRepChoice)> { match cert { - TxCertificate::VoteDelegation(d) => Some((&d.credential, &d.drep)), - TxCertificate::StakeAndVoteDelegation(d) => Some((&d.credential, &d.drep)), - TxCertificate::StakeRegistrationAndVoteDelegation(d) => Some((&d.credential, &d.drep)), + TxCertificate::VoteDelegation(d) => { + Some((d.stake_address.clone(), &d.drep)) + } + TxCertificate::StakeAndVoteDelegation(d) => { + Some((d.stake_address.clone(), &d.drep)) + } + TxCertificate::StakeRegistrationAndVoteDelegation(d) => { + Some((d.stake_address.clone(), &d.drep)) + } TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(d) => { - Some((&d.credential, &d.drep)) + Some((d.stake_address.clone(), &d.drep)) } _ => None, } diff --git a/modules/epochs_state/src/epochs_history.rs b/modules/epochs_state/src/epochs_history.rs index 145a309b..dc37b56a 100644 --- a/modules/epochs_state/src/epochs_history.rs +++ b/modules/epochs_state/src/epochs_history.rs @@ -78,7 +78,7 @@ impl EpochsHistoryState { #[cfg(test)] mod tests { use super::*; - use acropolis_common::{BlockHash, BlockStatus, Era, NetworkId}; + use acropolis_common::{BlockHash, BlockStatus, Era}; fn make_block(epoch: u64) -> BlockInfo { BlockInfo { @@ -89,7 +89,6 @@ mod tests { epoch, epoch_slot: 99, new_epoch: false, - network_id: NetworkId::default(), timestamp: 99999, era: Era::Conway, } diff --git a/modules/epochs_state/src/state.rs b/modules/epochs_state/src/state.rs index 6fbd40f3..0a46c07a 100644 --- a/modules/epochs_state/src/state.rs +++ b/modules/epochs_state/src/state.rs @@ -269,7 +269,7 @@ mod tests { crypto::keyhash_224, protocol_params::{Nonce, NonceHash}, state_history::{StateHistory, StateHistoryStore}, - BlockHash, BlockInfo, BlockStatus, Era, NetworkId, + BlockHash, BlockInfo, BlockStatus, Era, }; use tokio::sync::Mutex; @@ -281,7 +281,6 @@ mod tests { hash: BlockHash::default(), epoch, epoch_slot: 99, - network_id: NetworkId::default(), new_epoch: false, timestamp: 99999, era: Era::Shelley, @@ -296,7 +295,6 @@ mod tests { hash: BlockHash::default(), epoch, epoch_slot: 99, - network_id: NetworkId::default(), new_epoch: true, timestamp: 99999, era: Era::Shelley, @@ -311,7 +309,6 @@ mod tests { hash: BlockHash::default(), epoch, epoch_slot: 99, - network_id: NetworkId::default(), new_epoch: false, timestamp: 99999, era: Era::Conway, diff --git a/modules/genesis_bootstrapper/src/genesis_bootstrapper.rs b/modules/genesis_bootstrapper/src/genesis_bootstrapper.rs index 54e7a224..b256954c 100644 --- a/modules/genesis_bootstrapper/src/genesis_bootstrapper.rs +++ b/modules/genesis_bootstrapper/src/genesis_bootstrapper.rs @@ -7,8 +7,8 @@ use acropolis_common::{ CardanoMessage, GenesisCompleteMessage, GenesisUTxOsMessage, Message, PotDeltasMessage, UTXODeltasMessage, }, - Address, BlockHash, BlockInfo, BlockStatus, ByronAddress, Era, Lovelace, LovelaceDelta, - NetworkId, Pot, PotDelta, TxIdentifier, TxOutRef, TxOutput, UTXODelta, UTxOIdentifier, Value, + Address, BlockHash, BlockInfo, BlockStatus, ByronAddress, Era, Lovelace, LovelaceDelta, Pot, + PotDelta, TxIdentifier, TxOutRef, TxOutput, UTXODelta, UTxOIdentifier, Value, }; use anyhow::Result; use blake2::{digest::consts::U32, Blake2b, Digest}; @@ -128,7 +128,6 @@ impl GenesisBootstrapper { hash: BlockHash::default(), epoch: 0, epoch_slot: 0, - network_id: NetworkId::from(network_name), new_epoch: false, timestamp: byron_genesis.start_time, era: Era::Byron, diff --git a/modules/governance_state/src/alonzo_babbage_voting.rs b/modules/governance_state/src/alonzo_babbage_voting.rs index b20a403c..bc90a45b 100644 --- a/modules/governance_state/src/alonzo_babbage_voting.rs +++ b/modules/governance_state/src/alonzo_babbage_voting.rs @@ -113,7 +113,7 @@ mod tests { use crate::alonzo_babbage_voting::AlonzoBabbageVoting; use acropolis_common::{ rational_number::rational_number_from_f32, AlonzoBabbageUpdateProposal, - AlonzoBabbageVotingOutcome, BlockHash, BlockInfo, BlockStatus, GenesisKeyhash, NetworkId, + AlonzoBabbageVotingOutcome, BlockHash, BlockInfo, BlockStatus, GenesisKeyhash, ProtocolParamUpdate, }; use anyhow::Result; @@ -154,7 +154,6 @@ mod tests { epoch, epoch_slot: 0, era: era.try_into()?, - network_id: NetworkId::default(), new_epoch: new_epoch != 0, timestamp: 0, hash: BlockHash::default(), diff --git a/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs b/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs index 7e6fafe7..20804f2f 100644 --- a/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs +++ b/modules/mithril_snapshot_fetcher/src/mithril_snapshot_fetcher.rs @@ -4,7 +4,7 @@ use acropolis_common::{ genesis_values::GenesisValues, messages::{BlockBodyMessage, BlockHeaderMessage, CardanoMessage, Message}, - BlockInfo, BlockStatus, Era, NetworkId, + BlockInfo, BlockStatus, Era, }; use anyhow::{anyhow, bail, Result}; use caryatid_sdk::{module, Context, Module}; @@ -45,7 +45,6 @@ const DEFAULT_PAUSE: (&str, PauseType) = ("pause", PauseType::NoPause); const DEFAULT_DOWNLOAD_MAX_AGE: &str = "download-max-age"; const DEFAULT_DIRECTORY: &str = "downloads"; const SNAPSHOT_METADATA_FILE: &str = "snapshot_metadata.json"; -const DEFAULT_NETWORK_ID: &str = "mainnet"; /// Mithril feedback receiver struct FeedbackLogger { @@ -308,12 +307,6 @@ impl MithrilSnapshotFetcher { let timestamp = genesis.slot_to_timestamp(slot); - let network_id = NetworkId::from( - config - .get_string("network-id") - .unwrap_or(DEFAULT_NETWORK_ID.to_string()), - ); - let era = match block.era() { PallasEra::Byron => Era::Byron, PallasEra::Shelley => Era::Shelley, @@ -335,7 +328,6 @@ impl MithrilSnapshotFetcher { epoch, epoch_slot, new_epoch, - network_id, timestamp, era, }; diff --git a/modules/rest_blockfrost/src/handlers/governance.rs b/modules/rest_blockfrost/src/handlers/governance.rs index 5d0ef2d6..241fb5e4 100644 --- a/modules/rest_blockfrost/src/handlers/governance.rs +++ b/modules/rest_blockfrost/src/handlers/governance.rs @@ -10,9 +10,9 @@ use acropolis_common::{ accounts::{AccountsStateQuery, AccountsStateQueryResponse}, governance::{GovernanceStateQuery, GovernanceStateQueryResponse}, }, - Credential, GovActionId, StakeAddress, TxHash, Voter, + Credential, GovActionId, KeyHash, TxHash, Voter, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use caryatid_sdk::Context; use reqwest::Client; use serde_json::Value; @@ -99,11 +99,7 @@ pub async fn handle_single_drep_blockfrost( )) => { let active = !response.info.retired && !response.info.expired; - let stake_addresses = response - .delegators - .iter() - .map(|addr| addr.to_stake_address(handlers_config.network_id.clone().into())) - .collect(); + let stake_addresses = response.delegators.clone(); let sum_msg = Arc::new(Message::StateQuery(StateQuery::Accounts( AccountsStateQuery::GetAccountsBalancesSum { stake_addresses }, @@ -195,29 +191,23 @@ pub async fn handle_drep_delegators_blockfrost( Message::StateQueryResponse(StateQueryResponse::Governance( GovernanceStateQueryResponse::DRepDelegators(delegators), )) => { - let mut stake_addresses: Vec = Vec::new(); - let mut stake_key_to_bech32 = HashMap::new(); - - for addr in &delegators.addresses { - let bech32 = match addr.to_stake_bech32() { - Ok(b) => b, - Err(_) => { - return Ok(RESTResponse::with_text( - 500, - "Internal error: failed to encode stake address", - )); - } - }; - - let hash = addr.get_hash(); - let stake_address = - addr.to_stake_address(handlers_config.network_id.clone().into()); - stake_addresses.push(stake_address.clone()); - stake_key_to_bech32.insert(hash, bech32); - } + let stake_key_to_bech32: HashMap = delegators + .addresses + .iter() + .map(|addr| { + let bech32 = addr + .get_credential() + .to_stake_bech32() + .map_err(|_| anyhow!("Failed to encode stake address"))?; + let key_hash = addr.get_hash().to_vec(); + Ok((key_hash, bech32)) + }) + .collect::>>()?; let msg = Arc::new(Message::StateQuery(StateQuery::Accounts( - AccountsStateQuery::GetAccountsUtxoValuesMap { stake_addresses }, + AccountsStateQuery::GetAccountsUtxoValuesMap { + stake_addresses: delegators.addresses.clone(), + }, ))); let raw_msg = diff --git a/modules/rest_blockfrost/src/handlers/pools.rs b/modules/rest_blockfrost/src/handlers/pools.rs index bad519a4..9ae26994 100644 --- a/modules/rest_blockfrost/src/handlers/pools.rs +++ b/modules/rest_blockfrost/src/handlers/pools.rs @@ -664,11 +664,7 @@ async fn handle_pools_spo_blockfrost( // Query owner accounts balance sum from accounts_state let live_pledge_msg = Arc::new(Message::StateQuery(StateQuery::Accounts( AccountsStateQuery::GetAccountsUtxoValuesSum { - stake_addresses: pool_info - .pool_owners - .iter() - .map(|owner| owner.to_stake_address(handlers_config.network_id.clone().into())) - .collect(), + stake_addresses: pool_info.pool_owners.clone(), }, ))); @@ -703,7 +699,7 @@ async fn handle_pools_spo_blockfrost( let pool_owners = pool_info .pool_owners .iter() - .map(|owner| owner.to_stake_bech32()) + .map(|owner| owner.get_credential().to_stake_bech32()) .collect::, _>>(); let Ok(pool_owners) = pool_owners else { return Ok(RESTResponse::with_text(404, "Invalid Pool Owners")); diff --git a/modules/rest_blockfrost/src/handlers_config.rs b/modules/rest_blockfrost/src/handlers_config.rs index 0dbf22fc..53ea6b79 100644 --- a/modules/rest_blockfrost/src/handlers_config.rs +++ b/modules/rest_blockfrost/src/handlers_config.rs @@ -9,13 +9,10 @@ use acropolis_common::queries::{ pools::DEFAULT_POOLS_QUERY_TOPIC, spdd::DEFAULT_SPDD_QUERY_TOPIC, }; -use acropolis_common::NetworkId; use config::Config; const DEFAULT_EXTERNAL_API_TIMEOUT: (&str, i64) = ("external_api_timeout", 3); // 3 seconds -const DEFAULT_NETWORK_ID: (&str, &str) = ("network", "mainnet"); - #[derive(Clone)] pub struct HandlersConfig { pub accounts_query_topic: String, @@ -28,7 +25,6 @@ pub struct HandlersConfig { pub parameters_query_topic: String, pub external_api_timeout: u64, pub offchain_token_registry_url: String, - pub network_id: NetworkId, } impl From> for HandlersConfig { @@ -73,11 +69,6 @@ impl From> for HandlersConfig { .get_string(DEFAULT_OFFCHAIN_TOKEN_REGISTRY_URL.0) .unwrap_or(DEFAULT_OFFCHAIN_TOKEN_REGISTRY_URL.1.to_string()); - let network_id = config - .get_string(DEFAULT_NETWORK_ID.0) - .unwrap_or(DEFAULT_NETWORK_ID.1.to_string()) - .into(); - Self { accounts_query_topic, assets_query_topic, @@ -89,7 +80,6 @@ impl From> for HandlersConfig { parameters_query_topic, external_api_timeout, offchain_token_registry_url, - network_id, } } } diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index de8a2c05..115e9dda 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -10,9 +10,9 @@ use acropolis_common::{ params::TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH, queries::governance::VoteRecord, stake_addresses::StakeAddressMap, - BlockInfo, KeyHash, NetworkId, PoolMetadata, PoolRegistration, PoolRegistrationWithPos, - PoolRetirement, PoolRetirementWithPos, PoolUpdateEvent, Relay, StakeAddress, StakeCredential, - TxCertificate, TxHash, Voter, VotingProcedures, + BlockInfo, KeyHash, PoolMetadata, PoolRegistration, PoolRegistrationWithPos, PoolRetirement, + PoolRetirementWithPos, PoolUpdateEvent, Relay, StakeAddress, TxCertificate, TxHash, Voter, + VotingProcedures, }; use anyhow::Result; use imbl::HashMap; @@ -30,8 +30,6 @@ pub struct State { epoch: u64, - network_id: NetworkId, - spos: HashMap, PoolRegistration>, pending_updates: HashMap, PoolRegistration>, @@ -60,7 +58,6 @@ impl State { pending_updates: HashMap::new(), pending_deregistrations: HashMap::new(), total_blocks_minted: HashMap::new(), - network_id: NetworkId::default(), historical_spos: if config.store_historical_state() { Some(HashMap::new()) } else { @@ -111,7 +108,6 @@ impl From for State { store_config: StoreConfig::default(), block: 0, epoch: 0, - network_id: NetworkId::default(), spos, pending_updates: value.updates.into(), pending_deregistrations, @@ -441,20 +437,18 @@ impl State { } } - fn register_stake_address(&mut self, credential: &StakeCredential) { + fn register_stake_address(&mut self, stake_address: &StakeAddress) { let Some(stake_addresses) = self.stake_addresses.as_ref() else { return; }; let mut stake_addresses = stake_addresses.lock().unwrap(); - stake_addresses - .register_stake_address(&credential.to_stake_address(self.network_id.clone().into())); + stake_addresses.register_stake_address(stake_address); } - fn deregister_stake_address(&mut self, credential: &StakeCredential) { + fn deregister_stake_address(&mut self, stake_address: &StakeAddress) { let Some(stake_addresses) = self.stake_addresses.as_ref() else { return; }; - let stake_address = credential.to_stake_address(self.network_id.clone().into()); let mut stake_addresses = stake_addresses.lock().unwrap(); let old_spo = stake_addresses.get(&stake_address).map(|s| s.delegated_spo.clone()).flatten(); @@ -465,14 +459,12 @@ impl State { if let Some(old_spo) = old_spo.as_ref() { // remove delegators from old_spo if let Some(historical_spo) = historical_spos.get_mut(old_spo) { - if let Some(removed) = historical_spo.remove_delegator( - &credential.to_stake_address(self.network_id.clone().into()), - ) { + if let Some(removed) = historical_spo.remove_delegator(stake_address) { if !removed { error!( "Historical SPO state for {} does not contain delegator {}", hex::encode(old_spo), - hex::encode(&credential.get_hash()) + stake_address ); } } @@ -484,12 +476,10 @@ impl State { /// Record a stake delegation /// Update historical_spo_state's delegators - fn record_stake_delegation(&mut self, credential: &StakeCredential, spo: &KeyHash) { + fn record_stake_delegation(&mut self, stake_address: &StakeAddress, spo: &KeyHash) { let Some(stake_addresses) = self.stake_addresses.as_ref() else { return; }; - let hash = credential.get_hash(); - let stake_address = credential.to_stake_address(self.network_id.clone().into()); let mut stake_addresses = stake_addresses.lock().unwrap(); let old_spo = stake_addresses.get(&stake_address).map(|s| s.delegated_spo.clone()).flatten(); @@ -506,7 +496,7 @@ impl State { error!( "Historical SPO state for {} does not contain delegator {}", hex::encode(old_spo), - hex::encode(&hash) + stake_address ); } } @@ -526,7 +516,7 @@ impl State { error!( "Historical SPO state for {} already contains delegator {}", hex::encode(spo), - hex::encode(&hash) + stake_address ); } } @@ -558,38 +548,38 @@ impl State { } // for stake addresses - TxCertificate::StakeRegistration(sc_with_pos) => { - self.register_stake_address(&sc_with_pos.stake_credential); + TxCertificate::StakeRegistration(stake_address_with_pos) => { + self.register_stake_address(&stake_address_with_pos.stake_address); } - TxCertificate::StakeDeregistration(sc) => { - self.deregister_stake_address(&sc); + TxCertificate::StakeDeregistration(stake_address) => { + self.deregister_stake_address(&stake_address); } TxCertificate::Registration(reg) => { - self.register_stake_address(®.credential); + self.register_stake_address(®.stake_address); // we don't care deposite } TxCertificate::Deregistration(dreg) => { - self.deregister_stake_address(&dreg.credential); + self.deregister_stake_address(&dreg.stake_address); // we don't care refund } TxCertificate::StakeDelegation(delegation) => { - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); } TxCertificate::StakeAndVoteDelegation(delegation) => { - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); // don't care about vote delegation } TxCertificate::StakeRegistrationAndDelegation(delegation) => { - self.register_stake_address(&delegation.credential); - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.register_stake_address(&delegation.stake_address); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); } TxCertificate::StakeRegistrationAndVoteDelegation(delegation) => { - self.register_stake_address(&delegation.credential); + self.register_stake_address(&delegation.stake_address); // don't care about vote delegation } TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(delegation) => { - self.register_stake_address(&delegation.credential); - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.register_stake_address(&delegation.stake_address); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); // don't care about vote delegation } _ => (), @@ -694,12 +684,20 @@ mod tests { use crate::test_utils::*; use acropolis_common::{ state_history::{StateHistory, StateHistoryStore}, - Credential, PoolRetirement, Ratio, StakeAddress, TxCertificate, TxHash, + NetworkId, PoolRetirement, Ratio, StakeAddress, StakeAddressPayload, TxCertificate, TxHash, }; use tokio::sync::Mutex; - fn pool_owners_from_bytes(owners: Vec>) -> Vec { - owners.into_iter().map(|bytes| Credential::AddrKeyHash(bytes)).collect() + fn pool_owners_from_bytes(owners: Vec>) -> Vec { + owners + .into_iter() + .map(|bytes| { + StakeAddress::new( + StakeAddressPayload::StakeKeyHash(bytes), + NetworkId::default().into(), + ) + }) + .collect() } fn default_pool_registration(operator: Vec) -> PoolRegistration { @@ -713,7 +711,7 @@ mod tests { denominator: 0, }, reward_account: StakeAddress::default(), - pool_owners: pool_owners_from_bytes(vec![operator]), + pool_owners: pool_owners_from_bytes(vec![StakeAddress::default().get_hash().to_vec()]), relays: vec![], pool_metadata: None, } diff --git a/modules/spo_state/src/test_utils.rs b/modules/spo_state/src/test_utils.rs index 92be798f..b1fd1484 100644 --- a/modules/spo_state/src/test_utils.rs +++ b/modules/spo_state/src/test_utils.rs @@ -1,6 +1,6 @@ use acropolis_common::{messages::{ EpochActivityMessage, SPORewardsMessage, SPOStakeDistributionMessage, TxCertificatesMessage, -}, BlockHash, BlockInfo, BlockStatus, Era, NetworkId, TxCertificate}; +}, BlockHash, BlockInfo, BlockStatus, Era, TxCertificate}; use crate::store_config::StoreConfig; @@ -65,7 +65,6 @@ pub fn new_block(epoch: u64) -> BlockInfo { epoch, epoch_slot: 0, new_epoch: true, - network_id: NetworkId::default(), timestamp: epoch, era: Era::Byron, } diff --git a/modules/stake_delta_filter/src/state.rs b/modules/stake_delta_filter/src/state.rs index 59dbb56f..df90cc38 100644 --- a/modules/stake_delta_filter/src/state.rs +++ b/modules/stake_delta_filter/src/state.rs @@ -7,8 +7,7 @@ use acropolis_common::{ AddressDeltasMessage, CardanoMessage, Message, StakeAddressDeltasMessage, TxCertificatesMessage, }, - Address, BlockInfo, ShelleyAddressPointer, StakeAddress, StakeAddressPayload, StakeCredential, - TxCertificate, + Address, BlockInfo, ShelleyAddressPointer, TxCertificate, }; use anyhow::Result; use serde_with::serde_as; @@ -93,17 +92,7 @@ impl State { cert_index: reg.cert_index, }; - let stake_address = StakeAddress { - network: self.params.network.clone(), - payload: match ®.stake_credential { - StakeCredential::ScriptHash(h) => { - StakeAddressPayload::ScriptHash(h.clone()) - } - StakeCredential::AddrKeyHash(k) => { - StakeAddressPayload::StakeKeyHash(k.clone()) - } - }, - }; + let stake_address = reg.stake_address.clone(); // Sets pointer; updates max processed slot self.pointer_cache.set_pointer(ptr, stake_address, block.slot); diff --git a/modules/stake_delta_filter/src/utils.rs b/modules/stake_delta_filter/src/utils.rs index 7ce5b96e..d4919122 100644 --- a/modules/stake_delta_filter/src/utils.rs +++ b/modules/stake_delta_filter/src/utils.rs @@ -401,7 +401,7 @@ mod test { use crate::*; use acropolis_common::{ messages::AddressDeltasMessage, Address, AddressDelta, BlockHash, BlockInfo, BlockStatus, - ByronAddress, Era, NetworkId, ShelleyAddress, ShelleyAddressDelegationPart, + ByronAddress, Era, ShelleyAddress, ShelleyAddressDelegationPart, ShelleyAddressPaymentPart, ShelleyAddressPointer, StakeAddress, StakeAddressPayload, ValueDelta, }; @@ -542,7 +542,6 @@ mod test { epoch: 1, epoch_slot: 14243, new_epoch: true, - network_id: NetworkId::default(), timestamp: 2498243, era: Era::Conway, }; diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index 1439479e..74dfd557 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -93,6 +93,20 @@ pub fn map_stake_credential(cred: &PallasStakeCredential) -> StakeCredential { } } +/// Map a PallasStakeCredential to our StakeAddress +pub fn map_stake_address(cred: &PallasStakeCredential, network_id: NetworkId) -> StakeAddress { + let payload = match cred { + PallasStakeCredential::AddrKeyhash(key_hash) => { + StakeAddressPayload::StakeKeyHash(key_hash.to_vec()) + } + PallasStakeCredential::ScriptHash(script_hash) => { + StakeAddressPayload::ScriptHash(script_hash.to_vec()) + } + }; + + StakeAddress::new(payload, network_id.into()) +} + /// Map a Pallas DRep to our DRepChoice pub fn map_drep(drep: &conway::DRep) -> DRepChoice { match drep { @@ -203,24 +217,25 @@ pub fn map_certificate( tx_hash: TxHash, tx_index: u16, cert_index: usize, + network_id: NetworkId, ) -> Result { match cert { MultiEraCert::NotApplicable => Err(anyhow!("Not applicable cert!")), MultiEraCert::AlonzoCompatible(cert) => match cert.as_ref().as_ref() { alonzo::Certificate::StakeRegistration(cred) => { - Ok(TxCertificate::StakeRegistration(StakeCredentialWithPos { - stake_credential: map_stake_credential(cred), + Ok(TxCertificate::StakeRegistration(StakeAddressWithPos { + stake_address: map_stake_address(cred, network_id), tx_index: tx_index.try_into().unwrap(), cert_index: cert_index.try_into().unwrap(), })) } alonzo::Certificate::StakeDeregistration(cred) => Ok( - TxCertificate::StakeDeregistration(map_stake_credential(cred)), + TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)), ), alonzo::Certificate::StakeDelegation(cred, pool_key_hash) => { Ok(TxCertificate::StakeDelegation(StakeDelegation { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), })) } @@ -246,7 +261,15 @@ pub fn map_certificate( denominator: margin.denominator, }, reward_account: StakeAddress::from_binary(reward_account)?, - pool_owners: pool_owners.into_iter().map(|v| Credential::AddrKeyHash(v.to_vec())).collect(), + pool_owners: pool_owners + .into_iter() + .map(|v| { + StakeAddress::new( + StakeAddressPayload::StakeKeyHash(v.to_vec()), + network_id.clone().into(), + ) + }) + .collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { Nullable::Some(md) => Some(PoolMetadata { @@ -310,18 +333,18 @@ pub fn map_certificate( MultiEraCert::Conway(cert) => { match cert.as_ref().as_ref() { conway::Certificate::StakeRegistration(cred) => { - Ok(TxCertificate::StakeRegistration(StakeCredentialWithPos { - stake_credential: map_stake_credential(cred), + Ok(TxCertificate::StakeRegistration(StakeAddressWithPos { + stake_address: map_stake_address(cred, network_id), tx_index: tx_index.try_into().unwrap(), cert_index: cert_index.try_into().unwrap(), })) } conway::Certificate::StakeDeregistration(cred) => Ok( - TxCertificate::StakeDeregistration(map_stake_credential(cred)), + TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)), ), conway::Certificate::StakeDelegation(cred, pool_key_hash) => { Ok(TxCertificate::StakeDelegation(StakeDelegation { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), })) } @@ -348,7 +371,15 @@ pub fn map_certificate( denominator: margin.denominator, }, reward_account: StakeAddress::from_binary(reward_account)?, - pool_owners: pool_owners.into_iter().map(|v| Credential::AddrKeyHash(v.to_vec())).collect(), + pool_owners: pool_owners + .into_iter() + .map(|v| { + StakeAddress::new( + StakeAddressPayload::StakeKeyHash(v.to_vec()), + network_id.clone().into(), + ) + }) + .collect(), relays: relays.into_iter().map(|relay| map_relay(relay)).collect(), pool_metadata: match pool_metadata { Nullable::Some(md) => Some(PoolMetadata { @@ -375,28 +406,28 @@ pub fn map_certificate( conway::Certificate::Reg(cred, coin) => { Ok(TxCertificate::Registration(Registration { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), deposit: *coin, })) } conway::Certificate::UnReg(cred, coin) => { Ok(TxCertificate::Deregistration(Deregistration { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), refund: *coin, })) } conway::Certificate::VoteDeleg(cred, drep) => { Ok(TxCertificate::VoteDelegation(VoteDelegation { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), drep: map_drep(drep), })) } conway::Certificate::StakeVoteDeleg(cred, pool_key_hash, drep) => Ok( TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), drep: map_drep(drep), }), @@ -404,7 +435,7 @@ pub fn map_certificate( conway::Certificate::StakeRegDeleg(cred, pool_key_hash, coin) => Ok( TxCertificate::StakeRegistrationAndDelegation(StakeRegistrationAndDelegation { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), deposit: *coin, }), @@ -413,7 +444,7 @@ pub fn map_certificate( conway::Certificate::VoteRegDeleg(cred, drep, coin) => { Ok(TxCertificate::StakeRegistrationAndVoteDelegation( StakeRegistrationAndVoteDelegation { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), drep: map_drep(drep), deposit: *coin, }, @@ -423,7 +454,7 @@ pub fn map_certificate( conway::Certificate::StakeVoteRegDeleg(cred, pool_key_hash, drep, coin) => { Ok(TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( StakeRegistrationAndStakeAndVoteDelegation { - credential: map_stake_credential(cred), + stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), drep: map_drep(drep), deposit: *coin, diff --git a/modules/tx_unpacker/src/tx_unpacker.rs b/modules/tx_unpacker/src/tx_unpacker.rs index 03f28759..6bd246a0 100644 --- a/modules/tx_unpacker/src/tx_unpacker.rs +++ b/modules/tx_unpacker/src/tx_unpacker.rs @@ -103,6 +103,9 @@ impl TxUnpacker { info!("Publishing block txs on '{topic}'"); } + let network_id: NetworkId = + config.get_string("network-id").unwrap_or("mainnet".to_string()).into(); + // Initialize UTxORegistry let mut utxo_registry = UTxORegistry::default(); @@ -267,7 +270,7 @@ impl TxUnpacker { if publish_certificates_topic.is_some() { let tx_hash = tx.hash(); for ( cert_index, cert) in certs.iter().enumerate() { - match map_parameters::map_certificate(&cert, *tx_hash, tx_index, cert_index) { + match map_parameters::map_certificate(&cert, *tx_hash, tx_index, cert_index, network_id.clone()) { Ok(tx_cert) => { certificates.push(tx_cert); }, diff --git a/modules/upstream_chain_fetcher/src/body_fetcher.rs b/modules/upstream_chain_fetcher/src/body_fetcher.rs index 21a2ed95..d84104c0 100644 --- a/modules/upstream_chain_fetcher/src/body_fetcher.rs +++ b/modules/upstream_chain_fetcher/src/body_fetcher.rs @@ -1,7 +1,7 @@ //! Acropolis Miniprotocols module for Caryatid //! Multi-connection, block body fetching part of the client (in separate thread). -use acropolis_common::{messages::{BlockBodyMessage, BlockHeaderMessage}, BlockInfo, BlockStatus, Era, NetworkId}; +use acropolis_common::{messages::{BlockBodyMessage, BlockHeaderMessage}, BlockInfo, BlockStatus, Era}; use anyhow::{bail, Result}; use crossbeam::channel::{Receiver, TryRecvError}; use pallas::{ @@ -108,7 +108,6 @@ impl BodyFetcher { None => true, }; let timestamp = self.cfg.slot_to_timestamp(slot); - let network_id = NetworkId::from(self.cfg.network_id.clone()); Ok(BlockInfo { status: if rolled_back { @@ -121,7 +120,6 @@ impl BodyFetcher { hash, epoch, epoch_slot, - network_id, new_epoch, timestamp, era, diff --git a/modules/upstream_chain_fetcher/src/upstream_cache.rs b/modules/upstream_chain_fetcher/src/upstream_cache.rs index 8be89694..022b13c7 100644 --- a/modules/upstream_chain_fetcher/src/upstream_cache.rs +++ b/modules/upstream_chain_fetcher/src/upstream_cache.rs @@ -151,8 +151,7 @@ impl Storage for FileStorage { let file = File::open(&name)?; let reader = BufReader::new(file); - match serde_json::from_reader::, Vec>(reader) - { + match serde_json::from_reader::, Vec>(reader) { Ok(res) => Ok(res.clone()), Err(err) => Err(anyhow!( "Error reading upstream cache chunk JSON from {name}: '{err}'" @@ -170,7 +169,10 @@ impl Storage for FileStorage { #[cfg(test)] mod test { use crate::upstream_cache::{Storage, UpstreamCacheImpl, UpstreamCacheRecord}; - use acropolis_common::{messages::{BlockBodyMessage, BlockHeaderMessage}, BlockHash, BlockInfo, BlockStatus, Era, NetworkId}; + use acropolis_common::{ + messages::{BlockBodyMessage, BlockHeaderMessage}, + BlockHash, BlockInfo, BlockStatus, Era, + }; use anyhow::Result; use std::{collections::HashMap, sync::Arc}; @@ -183,7 +185,6 @@ mod test { epoch: 0, epoch_slot: n, new_epoch: false, - network_id: NetworkId::default(), timestamp: n, era: Era::default(), } diff --git a/modules/upstream_chain_fetcher/src/utils.rs b/modules/upstream_chain_fetcher/src/utils.rs index 1010a695..9ca3594c 100644 --- a/modules/upstream_chain_fetcher/src/utils.rs +++ b/modules/upstream_chain_fetcher/src/utils.rs @@ -1,7 +1,6 @@ use crate::UpstreamCacheRecord; use acropolis_common::genesis_values::GenesisValues; use acropolis_common::messages::{CardanoMessage, Message}; -use acropolis_common::NetworkId; use anyhow::{anyhow, bail, Result}; use caryatid_sdk::Context; use config::Config; diff --git a/modules/utxo_state/src/state.rs b/modules/utxo_state/src/state.rs index 0c703a90..b3e03d55 100644 --- a/modules/utxo_state/src/state.rs +++ b/modules/utxo_state/src/state.rs @@ -373,7 +373,7 @@ impl State { mod tests { use super::*; use crate::InMemoryImmutableUTXOStore; - use acropolis_common::{AssetName, BlockHash, ByronAddress, Era, NativeAsset, NetworkId, Value}; + use acropolis_common::{AssetName, BlockHash, ByronAddress, Era, NativeAsset, Value}; use config::Config; use tokio::sync::Mutex; @@ -394,7 +394,6 @@ mod tests { epoch_slot: slot, new_epoch: false, timestamp: slot, - network_id: NetworkId::default(), era: Era::Byron, } } diff --git a/processes/golden_tests/src/test_module.rs b/processes/golden_tests/src/test_module.rs index f12e1ed6..f0fd85de 100644 --- a/processes/golden_tests/src/test_module.rs +++ b/processes/golden_tests/src/test_module.rs @@ -1,7 +1,7 @@ use acropolis_common::{ledger_state::LedgerState, messages::{ CardanoMessage, Message, RawTxsMessage, SnapshotDumpMessage, SnapshotMessage, SnapshotStateMessage, -}, BlockHash, BlockInfo, BlockStatus, Era, NetworkId}; +}, BlockHash, BlockInfo, BlockStatus, Era}; use anyhow::{Context as AnyhowContext, Result}; use caryatid_sdk::{module, Context, Module}; use config::Config; @@ -43,7 +43,6 @@ impl TestModule { slot: 1, number: 1, hash: BlockHash::default(), - network_id: NetworkId::default(), epoch: 1, epoch_slot: 1, new_epoch: false, diff --git a/processes/omnibus/omnibus.toml b/processes/omnibus/omnibus.toml index 6861a9c9..d4f96049 100644 --- a/processes/omnibus/omnibus.toml +++ b/processes/omnibus/omnibus.toml @@ -9,18 +9,15 @@ genesis-key = "5b3139312c36362c3134302c3138352c3133382c31312c3233372c3230372c323 download-max-age = "never" # Pause constraint E.g. "epoch:100", "block:1200" pause = "none" -network = "mainnet" [module.upstream-chain-fetcher] sync-point = "snapshot" node-address = "backbone.cardano.iog.io:3001" magic-number = 764824073 -network = "mainnet" [module.block-unpacker] [module.rest-blockfrost] -network = "mainnet" [module.tx-unpacker] publish-utxo-deltas-topic = "cardano.utxo.deltas" @@ -29,6 +26,7 @@ publish-withdrawals-topic = "cardano.withdrawals" publish-certificates-topic = "cardano.certificates" publish-governance-topic = "cardano.governance" publish-block-txs-topic = "cardano.block.txs" +network-name = "mainnet" [module.utxo-state] store = "memory" # "memory", "dashmap", "fjall", "fjall-async", "sled", "sled-async", "fake" From de08f0a0ba2dd8e8ba87ba0698498889a6dcf389 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 22 Oct 2025 12:59:18 -0700 Subject: [PATCH 24/27] Refactor: Replace `StakeCredentials` with `StakeAddresses` for MIR targets and simplify handling --- common/src/types.rs | 16 +-------------- modules/accounts_state/src/state.rs | 22 +++++++-------------- modules/tx_unpacker/src/map_parameters.rs | 4 ++-- modules/upstream_chain_fetcher/src/utils.rs | 12 +++-------- 4 files changed, 13 insertions(+), 41 deletions(-) diff --git a/common/src/types.rs b/common/src/types.rs index 592f6204..40cbb38f 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -623,20 +623,6 @@ impl Credential { pub type StakeCredential = Credential; -impl StakeCredential { - pub fn to_stake_address(&self, network: AddressNetwork) -> StakeAddress { - let payload = match self { - StakeCredential::AddrKeyHash(hash) => StakeAddressPayload::StakeKeyHash( - hash.clone().try_into().expect("Invalid hash length"), - ), - StakeCredential::ScriptHash(hash) => StakeAddressPayload::ScriptHash( - hash.clone().try_into().expect("Invalid hash length"), - ), - }; - StakeAddress::new(payload, network) - } -} - /// Relay single host address #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)] pub struct SingleHostAddr { @@ -887,7 +873,7 @@ pub enum InstantaneousRewardSource { /// Target of a MIR #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum InstantaneousRewardTarget { - StakeCredentials(Vec<(StakeCredential, i64)>), + StakeAddresses(Vec<(StakeAddress, i64)>), OtherAccountingPot(u64), } diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 51010c9d..5dbb0278 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -14,7 +14,7 @@ use acropolis_common::{ protocol_params::ProtocolParams, stake_addresses::{StakeAddressMap, StakeAddressState}, BlockInfo, DRepChoice, DRepCredential, DelegatedStake, InstantaneousRewardSource, - InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, NetworkId, + InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, PoolLiveStakeInfo, PoolRegistration, Pot, SPORewards, StakeAddress, StakeRewardDelta, TxCertificate, }; @@ -95,9 +95,6 @@ pub struct State { /// List of SPOs (by operator ID) retiring in the current epoch retiring_spos: Vec, - /// Network ID - network_id: NetworkId, - /// Map of staking address values /// Wrapped in an Arc so it doesn't get cloned in full by StateHistory stake_addresses: Arc>, @@ -549,13 +546,10 @@ impl State { }; match &mir.target { - InstantaneousRewardTarget::StakeCredentials(deltas) => { + InstantaneousRewardTarget::StakeAddresses(deltas) => { // Transfer to (in theory also from) stake addresses from (to) a pot let mut total_value: u64 = 0; - for (credential, value) in deltas.iter() { - let stake_address = - credential.to_stake_address(self.network_id.clone().into()); - + for (stake_address, value) in deltas.iter() { // Get old stake address state, or create one let mut stake_addresses = self.stake_addresses.lock().unwrap(); let sas = stake_addresses.entry(stake_address.clone()).or_default(); @@ -1217,7 +1211,6 @@ mod tests { fn mir_transfers_to_stake_addresses() { let mut state = State::default(); let stake_address = create_address(&STAKE_KEY_HASH); - let stake_credential = stake_address.get_credential(); // Bootstrap with some in reserves state.pots.reserves = 100; @@ -1244,9 +1237,9 @@ mod tests { // Send in a MIR reserves->{47,-5}->stake let mir = MoveInstantaneousReward { source: InstantaneousRewardSource::Reserves, - target: InstantaneousRewardTarget::StakeCredentials(vec![ - (stake_credential.clone(), 47), - (stake_credential, -5), + target: InstantaneousRewardTarget::StakeAddresses(vec![ + (stake_address.clone(), 47), + (stake_address.clone(), -5), ]), }; @@ -1266,7 +1259,6 @@ mod tests { fn withdrawal_transfers_from_stake_addresses() { let mut state = State::default(); let stake_address = create_address(&STAKE_KEY_HASH); - let stake_credential = stake_address.get_credential(); // Bootstrap with some in reserves state.pots.reserves = 100; @@ -1294,7 +1286,7 @@ mod tests { // Send in a MIR reserves->42->stake let mir = MoveInstantaneousReward { source: InstantaneousRewardSource::Reserves, - target: InstantaneousRewardTarget::StakeCredentials(vec![(stake_credential, 42)]), + target: InstantaneousRewardTarget::StakeAddresses(vec![(stake_address.clone(), 42)]), }; state.handle_mir(&mir).unwrap(); diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index 74dfd557..1bd4f23f 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -314,10 +314,10 @@ pub fn map_certificate( }, target: match &mir.target { alonzo::InstantaneousRewardTarget::StakeCredentials(creds) => { - InstantaneousRewardTarget::StakeCredentials( + InstantaneousRewardTarget::StakeAddresses( creds .iter() - .map(|(sc, v)| (map_stake_credential(&sc), *v)) + .map(|(sc, v)| (map_stake_address(&sc, network_id.clone()), *v)) .collect(), ) } diff --git a/modules/upstream_chain_fetcher/src/utils.rs b/modules/upstream_chain_fetcher/src/utils.rs index 9ca3594c..dd3ee50b 100644 --- a/modules/upstream_chain_fetcher/src/utils.rs +++ b/modules/upstream_chain_fetcher/src/utils.rs @@ -19,7 +19,6 @@ const DEFAULT_GENESIS_COMPLETION_TOPIC: (&str, &str) = const DEFAULT_NODE_ADDRESS: (&str, &str) = ("node-address", "backbone.cardano.iog.io:3001"); const DEFAULT_MAGIC_NUMBER: (&str, u64) = ("magic-number", 764824073); -const DEFAULT_NETWORK_ID: (&str, &str) = ("network-id", "mainnet"); const DEFAULT_SYNC_POINT: (&str, SyncPoint) = ("sync-point", SyncPoint::Snapshot); const DEFAULT_CACHE_DIR: (&str, &str) = ("cache-dir", "upstream-cache"); @@ -50,7 +49,6 @@ pub struct FetcherConfig { pub genesis_completion_topic: String, pub node_address: String, pub magic_number: u64, - pub network_id: String, pub cache_dir: String, pub genesis_values: Option, @@ -110,9 +108,6 @@ impl FetcherConfig { magic_number: config .get::(DEFAULT_MAGIC_NUMBER.0) .unwrap_or(DEFAULT_MAGIC_NUMBER.1), - network_id: config - .get_string(DEFAULT_NETWORK_ID.0) - .unwrap_or(DEFAULT_NETWORK_ID.1.to_string()), node_address: Self::conf(&config, DEFAULT_NODE_ADDRESS), cache_dir: Self::conf(&config, DEFAULT_CACHE_DIR), genesis_values: Self::conf_genesis(&config), @@ -145,8 +140,8 @@ pub async fn publish_message(cfg: Arc, record: &UpstreamCacheReco pub async fn peer_connect(cfg: Arc, role: &str) -> Result> { info!( - "Connecting {role} to {} ({})-({}) ...", - cfg.node_address, cfg.network_id, cfg.magic_number + "Connecting {role} to {} ({}) ...", + cfg.node_address, cfg.magic_number ); match PeerClient::connect(cfg.node_address.clone(), cfg.magic_number).await { @@ -161,9 +156,8 @@ pub async fn peer_connect(cfg: Arc, role: &str) -> Result bail!( - "Cannot connect {role} to {} ({})-({}): {e}", + "Cannot connect {role} to {} ({}): {e}", cfg.node_address, - cfg.network_id, cfg.magic_number ), } From 39e0f1375e9b5c1077451f88ca41f032084675fc Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 22 Oct 2025 14:19:33 -0700 Subject: [PATCH 25/27] Refactor: Standardize variable names (e.g., `cred` -> `delegator`) for clarity. --- common/src/state_history.rs | 2 +- common/src/types.rs | 1 - modules/drep_state/src/state.rs | 11 +++++------ .../src/handlers/governance.rs | 19 ++++++++++++++----- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/common/src/state_history.rs b/common/src/state_history.rs index c8cf2979..9e19a4c7 100644 --- a/common/src/state_history.rs +++ b/common/src/state_history.rs @@ -54,7 +54,7 @@ impl StateHistory { /// Get the current state assuming any rollback has been done /// Cloned for modification - call commit() when done - pub fn get_current_state(&self) -> S { + pub fn get_current_state(&self) -> S { self.history.back().map(|entry| entry.state.clone()).unwrap_or_default() } diff --git a/common/src/types.rs b/common/src/types.rs index 40cbb38f..c1708627 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -6,7 +6,6 @@ use crate::{ address::{Address, ShelleyAddress, StakeAddress}, protocol_params, rational_number::RationalNumber, - AddressNetwork, StakeAddressPayload, }; use anyhow::{anyhow, bail, Error, Result}; use bech32::{Bech32, Hrp}; diff --git a/modules/drep_state/src/state.rs b/modules/drep_state/src/state.rs index db7a89b1..a97ded4e 100644 --- a/modules/drep_state/src/state.rs +++ b/modules/drep_state/src/state.rs @@ -228,10 +228,10 @@ impl State { for tx_cert in tx_certs { if store_delegators { - if let Some((cred, drep)) = + if let Some((delegator, drep)) = Self::extract_delegation_fields(tx_cert) { - batched_delegators.push((cred, drep)); + batched_delegators.push((delegator, drep)); continue; } } @@ -477,9 +477,8 @@ impl State { delegators: Vec<(StakeAddress, &DRepChoice)>, ) -> Result<()> { let stake_addresses = delegators.iter().map(|(addr, _)| (addr).clone()).collect(); - let stake_key_to_input: HashMap = - delegators.iter().map(|(addr, drep)| (addr.get_hash().to_vec(), (addr, *drep))).collect(); + delegators.iter().map(|(addr, drep)| (addr.get_credential().get_hash(), (addr, *drep))).collect(); let msg = Arc::new(Message::StateQuery(StateQuery::Accounts( AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_addresses }, @@ -515,7 +514,7 @@ impl State { if old_drep_cred != new_drep_cred { self.update_historical(&old_drep_cred, false, |entry| { if let Some(delegators) = entry.delegators.as_mut() { - delegators.retain(|s| s.to_binary() != delegator.to_binary()); + delegators.retain(|s| s != delegator); } })?; } @@ -525,7 +524,7 @@ impl State { // Add delegator to new DRep match self.update_historical(&new_drep_cred, true, |entry| { if let Some(delegators) = entry.delegators.as_mut() { - if !delegators.contains(&delegator) { + if !delegators.contains(delegator) { delegators.push(delegator.clone()); } } diff --git a/modules/rest_blockfrost/src/handlers/governance.rs b/modules/rest_blockfrost/src/handlers/governance.rs index 241fb5e4..13beff49 100644 --- a/modules/rest_blockfrost/src/handlers/governance.rs +++ b/modules/rest_blockfrost/src/handlers/governance.rs @@ -191,18 +191,27 @@ pub async fn handle_drep_delegators_blockfrost( Message::StateQueryResponse(StateQueryResponse::Governance( GovernanceStateQueryResponse::DRepDelegators(delegators), )) => { - let stake_key_to_bech32: HashMap = delegators + let stake_key_to_bech32: HashMap = match delegators .addresses .iter() .map(|addr| { - let bech32 = addr - .get_credential() + let credential = addr.get_credential(); + let bech32 = credential .to_stake_bech32() .map_err(|_| anyhow!("Failed to encode stake address"))?; - let key_hash = addr.get_hash().to_vec(); + let key_hash = credential.get_hash(); Ok((key_hash, bech32)) }) - .collect::>>()?; + .collect::>>() + { + Ok(map) => map, + Err(e) => { + return Ok(RESTResponse::with_text( + 500, + &format!("Internal error: {e}"), + )); + } + }; let msg = Arc::new(Message::StateQuery(StateQuery::Accounts( AccountsStateQuery::GetAccountsUtxoValuesMap { From 85f3354d3872f106ba6ff4e37a802ae82b6f0966 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 22 Oct 2025 15:39:37 -0700 Subject: [PATCH 26/27] Unstage files with simple import changes, use Display implementation vs hex encoding --- common/src/address.rs | 2 +- modules/rest_blockfrost/src/handlers/pools.rs | 2 +- modules/spo_state/src/state.rs | 38 +++++++------------ modules/spo_state/src/test_utils.rs | 9 +++-- modules/stake_delta_filter/src/state.rs | 4 +- modules/stake_delta_filter/src/utils.rs | 5 +-- .../src/body_fetcher.rs | 5 ++- .../src/upstream_cache.rs | 3 +- processes/golden_tests/src/test_module.rs | 12 ++++-- 9 files changed, 38 insertions(+), 42 deletions(-) diff --git a/common/src/address.rs b/common/src/address.rs index 837a3bd8..ca17d70a 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -308,7 +308,7 @@ impl StakeAddress { impl Display for StakeAddress { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.to_string().unwrap()) + write!(f, "{}", hex::encode(self.get_credential().get_hash())) } } diff --git a/modules/rest_blockfrost/src/handlers/pools.rs b/modules/rest_blockfrost/src/handlers/pools.rs index 9ae26994..17eb6b15 100644 --- a/modules/rest_blockfrost/src/handlers/pools.rs +++ b/modules/rest_blockfrost/src/handlers/pools.rs @@ -692,7 +692,7 @@ async fn handle_pools_spo_blockfrost( let live_pledge = live_pledge?; let pool_id = pool_info.operator.to_bech32_with_hrp("pool").unwrap(); - let reward_account = pool_info.reward_account.to_string(); + let reward_account = pool_info.reward_account.get_credential().to_stake_bech32(); let Ok(reward_account) = reward_account else { return Ok(RESTResponse::with_text(404, "Invalid Reward Account")); }; diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 115e9dda..195edf9d 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -662,10 +662,7 @@ impl State { for delta in reward_deltas_msg.deltas.iter() { let mut stake_addresses = stake_addresses.lock().unwrap(); if let Err(e) = stake_addresses.update_reward(&delta.stake_address, delta.delta) { - error!( - "Updating reward account {}: {e}", - hex::encode(&delta.stake_address.get_hash()) - ); + error!("Updating reward account {}: {e}", delta.stake_address); } } @@ -684,26 +681,17 @@ mod tests { use crate::test_utils::*; use acropolis_common::{ state_history::{StateHistory, StateHistoryStore}, - NetworkId, PoolRetirement, Ratio, StakeAddress, StakeAddressPayload, TxCertificate, TxHash, + PoolRetirement, Ratio, StakeAddress, TxCertificate, TxHash, }; use tokio::sync::Mutex; - fn pool_owners_from_bytes(owners: Vec>) -> Vec { - owners - .into_iter() - .map(|bytes| { - StakeAddress::new( - StakeAddressPayload::StakeKeyHash(bytes), - NetworkId::default().into(), - ) - }) - .collect() - } - - fn default_pool_registration(operator: Vec) -> PoolRegistration { + fn default_pool_registration( + operator: Vec, + vrf_key_hash: Option>, + ) -> PoolRegistration { PoolRegistration { operator: operator.clone(), - vrf_key_hash: vec![0], + vrf_key_hash: vrf_key_hash.unwrap_or_else(|| vec![0]), pledge: 0, cost: 0, margin: Ratio { @@ -711,7 +699,7 @@ mod tests { denominator: 0, }, reward_account: StakeAddress::default(), - pool_owners: pool_owners_from_bytes(vec![StakeAddress::default().get_hash().to_vec()]), + pool_owners: vec![StakeAddress::default()], relays: vec![], pool_metadata: None, } @@ -744,7 +732,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: default_pool_registration(vec![0]), + reg: default_pool_registration(vec![0], None), tx_hash: TxHash::default(), cert_index: 1, }, @@ -880,7 +868,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: default_pool_registration(vec![0]), + reg: default_pool_registration(vec![0], None), tx_hash: TxHash::default(), cert_index: 0, }, @@ -923,7 +911,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: default_pool_registration(vec![0]), + reg: default_pool_registration(vec![0], None), tx_hash: TxHash::default(), cert_index: 0, }, @@ -1028,7 +1016,7 @@ mod tests { let spo_id = keyhash_224(&vec![1 as u8]); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: default_pool_registration(spo_id.clone()), + reg: default_pool_registration(spo_id.clone(), None), tx_hash: TxHash::default(), cert_index: 0, }, @@ -1065,7 +1053,7 @@ mod tests { let spo_id = keyhash_224(&vec![1 as u8]); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: default_pool_registration(spo_id.clone()), + reg: default_pool_registration(spo_id.clone(), None), tx_hash: TxHash::default(), cert_index: 0, }, diff --git a/modules/spo_state/src/test_utils.rs b/modules/spo_state/src/test_utils.rs index b1fd1484..2e8772d8 100644 --- a/modules/spo_state/src/test_utils.rs +++ b/modules/spo_state/src/test_utils.rs @@ -1,6 +1,9 @@ -use acropolis_common::{messages::{ - EpochActivityMessage, SPORewardsMessage, SPOStakeDistributionMessage, TxCertificatesMessage, -}, BlockHash, BlockInfo, BlockStatus, Era, TxCertificate}; +use acropolis_common::{ + messages::{ + EpochActivityMessage, SPORewardsMessage, SPOStakeDistributionMessage, TxCertificatesMessage, + }, + BlockHash, BlockInfo, BlockStatus, Era, TxCertificate, +}; use crate::store_config::StoreConfig; diff --git a/modules/stake_delta_filter/src/state.rs b/modules/stake_delta_filter/src/state.rs index df90cc38..a45797aa 100644 --- a/modules/stake_delta_filter/src/state.rs +++ b/modules/stake_delta_filter/src/state.rs @@ -92,10 +92,8 @@ impl State { cert_index: reg.cert_index, }; - let stake_address = reg.stake_address.clone(); - // Sets pointer; updates max processed slot - self.pointer_cache.set_pointer(ptr, stake_address, block.slot); + self.pointer_cache.set_pointer(ptr, reg.stake_address.clone(), block.slot); } _ => (), } diff --git a/modules/stake_delta_filter/src/utils.rs b/modules/stake_delta_filter/src/utils.rs index d4919122..334df1cd 100644 --- a/modules/stake_delta_filter/src/utils.rs +++ b/modules/stake_delta_filter/src/utils.rs @@ -401,9 +401,8 @@ mod test { use crate::*; use acropolis_common::{ messages::AddressDeltasMessage, Address, AddressDelta, BlockHash, BlockInfo, BlockStatus, - ByronAddress, Era, ShelleyAddress, ShelleyAddressDelegationPart, - ShelleyAddressPaymentPart, ShelleyAddressPointer, StakeAddress, StakeAddressPayload, - ValueDelta, + ByronAddress, Era, ShelleyAddress, ShelleyAddressDelegationPart, ShelleyAddressPaymentPart, + ShelleyAddressPointer, StakeAddress, StakeAddressPayload, ValueDelta, }; use bech32::{Bech32, Hrp}; diff --git a/modules/upstream_chain_fetcher/src/body_fetcher.rs b/modules/upstream_chain_fetcher/src/body_fetcher.rs index d84104c0..afa2a5ee 100644 --- a/modules/upstream_chain_fetcher/src/body_fetcher.rs +++ b/modules/upstream_chain_fetcher/src/body_fetcher.rs @@ -1,7 +1,10 @@ //! Acropolis Miniprotocols module for Caryatid //! Multi-connection, block body fetching part of the client (in separate thread). -use acropolis_common::{messages::{BlockBodyMessage, BlockHeaderMessage}, BlockInfo, BlockStatus, Era}; +use acropolis_common::{ + messages::{BlockBodyMessage, BlockHeaderMessage}, + BlockInfo, BlockStatus, Era, +}; use anyhow::{bail, Result}; use crossbeam::channel::{Receiver, TryRecvError}; use pallas::{ diff --git a/modules/upstream_chain_fetcher/src/upstream_cache.rs b/modules/upstream_chain_fetcher/src/upstream_cache.rs index 022b13c7..9281a358 100644 --- a/modules/upstream_chain_fetcher/src/upstream_cache.rs +++ b/modules/upstream_chain_fetcher/src/upstream_cache.rs @@ -151,7 +151,8 @@ impl Storage for FileStorage { let file = File::open(&name)?; let reader = BufReader::new(file); - match serde_json::from_reader::, Vec>(reader) { + match serde_json::from_reader::, Vec>(reader) + { Ok(res) => Ok(res.clone()), Err(err) => Err(anyhow!( "Error reading upstream cache chunk JSON from {name}: '{err}'" diff --git a/processes/golden_tests/src/test_module.rs b/processes/golden_tests/src/test_module.rs index f0fd85de..61377e6b 100644 --- a/processes/golden_tests/src/test_module.rs +++ b/processes/golden_tests/src/test_module.rs @@ -1,7 +1,11 @@ -use acropolis_common::{ledger_state::LedgerState, messages::{ - CardanoMessage, Message, RawTxsMessage, SnapshotDumpMessage, SnapshotMessage, - SnapshotStateMessage, -}, BlockHash, BlockInfo, BlockStatus, Era}; +use acropolis_common::{ + ledger_state::LedgerState, + messages::{ + CardanoMessage, Message, RawTxsMessage, SnapshotDumpMessage, SnapshotMessage, + SnapshotStateMessage, + }, + BlockHash, BlockInfo, BlockStatus, Era, +}; use anyhow::{Context as AnyhowContext, Result}; use caryatid_sdk::{module, Context, Module}; use config::Config; From 966913951f9f4793df7c064008facb077996da02 Mon Sep 17 00:00:00 2001 From: Matthew Hounslow Date: Wed, 22 Oct 2025 16:38:23 -0700 Subject: [PATCH 27/27] Consolidated `#[derive(...)]` traits for `Credential` enum --- common/src/queries/governance.rs | 5 +++- common/src/stake_addresses.rs | 2 +- common/src/types.rs | 19 +++----------- modules/accounts_state/src/accounts_state.rs | 18 +++++++------ modules/accounts_state/src/rewards.rs | 4 +-- modules/accounts_state/src/snapshot.rs | 10 +++---- modules/accounts_state/src/state.rs | 5 ++-- modules/drep_state/src/drep_state.rs | 1 - modules/drep_state/src/state.rs | 26 +++++++------------ modules/spo_state/src/historical_spo_state.rs | 4 ++- 10 files changed, 39 insertions(+), 55 deletions(-) diff --git a/common/src/queries/governance.rs b/common/src/queries/governance.rs index 678b35a9..c0c3d033 100644 --- a/common/src/queries/governance.rs +++ b/common/src/queries/governance.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; -use crate::{Anchor, DRepCredential, GovActionId, Lovelace, ProposalProcedure, StakeAddress, TxHash, Vote, Voter, VotingProcedure}; +use crate::{ + Anchor, DRepCredential, GovActionId, Lovelace, ProposalProcedure, StakeAddress, TxHash, Vote, + Voter, VotingProcedure, +}; pub const DEFAULT_DREPS_QUERY_TOPIC: (&str, &str) = ("drep-state-query-topic", "cardano.query.dreps"); diff --git a/common/src/stake_addresses.rs b/common/src/stake_addresses.rs index f6c29839..b47484c1 100644 --- a/common/src/stake_addresses.rs +++ b/common/src/stake_addresses.rs @@ -330,7 +330,7 @@ impl StakeAddressMap { let mut result: HashMap> = HashMap::new(); for (spo, entry) in entries { - result.entry(spo).or_default().push(entry); + result.entry(spo).or_default().push((entry.0.get_credential().get_hash(), entry.1)); } result } diff --git a/common/src/types.rs b/common/src/types.rs index 558e3b6f..9559476c 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -601,26 +601,14 @@ pub struct PotDelta { } #[derive( - Debug, - Clone, - Ord, - Eq, - PartialEq, - PartialOrd, - Hash, - serde::Serialize, - serde::Deserialize, - minicbor::Decode, - minicbor::Encode, + Debug, Clone, Ord, Eq, PartialEq, PartialOrd, Hash, serde::Serialize, serde::Deserialize, )] pub enum Credential { /// Script hash. NOTE: Order matters when parsing Haskell Node Snapshot data. - #[n(0)] - ScriptHash(#[n(0)] KeyHash), + ScriptHash(KeyHash), /// Address key hash - #[n(1)] - AddrKeyHash(#[n(0)] KeyHash), + AddrKeyHash(KeyHash), } impl Credential { @@ -833,7 +821,6 @@ pub struct PoolRegistration { pub reward_account: StakeAddress, /// Pool owners by their key hash - // #[serde_as(as = "Vec")] #[n(6)] pub pool_owners: Vec, diff --git a/modules/accounts_state/src/accounts_state.rs b/modules/accounts_state/src/accounts_state.rs index 779b8c7d..91423ad1 100644 --- a/modules/accounts_state/src/accounts_state.rs +++ b/modules/accounts_state/src/accounts_state.rs @@ -541,14 +541,16 @@ impl AccountsState { }) } - AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_addresses } => match state - .get_drep_delegations_map(stake_addresses) - { - Some(map) => AccountsStateQueryResponse::AccountsDrepDelegationsMap(map), - None => AccountsStateQueryResponse::Error( - "Error retrieving DRep delegations map".to_string(), - ), - }, + AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_addresses } => { + match state.get_drep_delegations_map(stake_addresses) { + Some(map) => { + AccountsStateQueryResponse::AccountsDrepDelegationsMap(map) + } + None => AccountsStateQueryResponse::Error( + "Error retrieving DRep delegations map".to_string(), + ), + } + } AccountsStateQuery::GetOptimalPoolSizing => { AccountsStateQueryResponse::OptimalPoolSizing( diff --git a/modules/accounts_state/src/rewards.rs b/modules/accounts_state/src/rewards.rs index 06528a86..250f81f9 100644 --- a/modules/accounts_state/src/rewards.rs +++ b/modules/accounts_state/src/rewards.rs @@ -353,8 +353,8 @@ fn calculate_spo_rewards( continue; } - // Check pool's reward address - removing e1 prefix - if spo.reward_account == *delegator_stake_address { + // Check pool's reward address + if &spo.reward_account == delegator_stake_address { debug!( "Skipping pool reward account {}, losing {to_pay}", delegator_stake_address diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 81e09f46..a6f959c8 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -1,7 +1,9 @@ //! Acropolis AccountsState: snapshot for rewards calculations use crate::state::{Pots, RegistrationChange}; -use acropolis_common::{stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, StakeAddress}; +use acropolis_common::{ + stake_addresses::StakeAddressMap, KeyHash, Lovelace, PoolRegistration, Ratio, StakeAddress, +}; use imbl::OrdMap; use std::collections::HashMap; use std::sync::Arc; @@ -320,11 +322,7 @@ mod tests { ); // Extract key hashes from stake addresses for the API call - let addresses = vec![ - addr2, - addr3, - addr4, - ]; + let addresses = vec![addr2, addr3, addr4]; let result = snapshot.get_stake_delegated_to_spo_by_addresses(&spo1, &addresses); assert_eq!(result, 500); } diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 01978fae..0f7f1fc9 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -14,9 +14,8 @@ use acropolis_common::{ protocol_params::ProtocolParams, stake_addresses::{StakeAddressMap, StakeAddressState}, BlockInfo, DRepChoice, DRepCredential, DelegatedStake, InstantaneousRewardSource, - InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, - PoolLiveStakeInfo, PoolRegistration, Pot, SPORewards, StakeAddress, StakeRewardDelta, - TxCertificate, + InstantaneousRewardTarget, KeyHash, Lovelace, MoveInstantaneousReward, PoolLiveStakeInfo, + PoolRegistration, Pot, SPORewards, StakeAddress, StakeRewardDelta, TxCertificate, }; use anyhow::Result; use imbl::OrdMap; diff --git a/modules/drep_state/src/drep_state.rs b/modules/drep_state/src/drep_state.rs index ccb97901..eaaaf208 100644 --- a/modules/drep_state/src/drep_state.rs +++ b/modules/drep_state/src/drep_state.rs @@ -141,7 +141,6 @@ impl DRepState { context.clone(), &tx_certs_msg.certificates, block_info.epoch, - ) .await .inspect_err(|e| error!("Certificates handling error: {e:#}")) diff --git a/modules/drep_state/src/state.rs b/modules/drep_state/src/state.rs index a97ded4e..3f5a17c8 100644 --- a/modules/drep_state/src/state.rs +++ b/modules/drep_state/src/state.rs @@ -7,8 +7,8 @@ use acropolis_common::{ get_query_topic, governance::{DRepActionUpdate, DRepUpdateEvent, VoteRecord}, }, - Anchor, DRepChoice, DRepCredential, KeyHash, Lovelace, - StakeAddress, TxCertificate, TxHash, Voter, VotingProcedures, + Anchor, DRepChoice, DRepCredential, KeyHash, Lovelace, StakeAddress, TxCertificate, TxHash, + Voter, VotingProcedures, }; use anyhow::{anyhow, Result}; use caryatid_sdk::Context; @@ -228,9 +228,7 @@ impl State { for tx_cert in tx_certs { if store_delegators { - if let Some((delegator, drep)) = - Self::extract_delegation_fields(tx_cert) - { + if let Some((delegator, drep)) = Self::extract_delegation_fields(tx_cert) { batched_delegators.push((delegator, drep)); continue; } @@ -477,8 +475,10 @@ impl State { delegators: Vec<(StakeAddress, &DRepChoice)>, ) -> Result<()> { let stake_addresses = delegators.iter().map(|(addr, _)| (addr).clone()).collect(); - let stake_key_to_input: HashMap = - delegators.iter().map(|(addr, drep)| (addr.get_credential().get_hash(), (addr, *drep))).collect(); + let stake_key_to_input: HashMap = delegators + .iter() + .map(|(addr, drep)| (addr.get_credential().get_hash(), (addr, *drep))) + .collect(); let msg = Arc::new(Message::StateQuery(StateQuery::Accounts( AccountsStateQuery::GetAccountsDrepDelegationsMap { stake_addresses }, @@ -537,16 +537,10 @@ impl State { Ok(()) } - fn extract_delegation_fields( - cert: &TxCertificate, - ) -> Option<(StakeAddress, &DRepChoice)> { + fn extract_delegation_fields(cert: &TxCertificate) -> Option<(StakeAddress, &DRepChoice)> { match cert { - TxCertificate::VoteDelegation(d) => { - Some((d.stake_address.clone(), &d.drep)) - } - TxCertificate::StakeAndVoteDelegation(d) => { - Some((d.stake_address.clone(), &d.drep)) - } + TxCertificate::VoteDelegation(d) => Some((d.stake_address.clone(), &d.drep)), + TxCertificate::StakeAndVoteDelegation(d) => Some((d.stake_address.clone(), &d.drep)), TxCertificate::StakeRegistrationAndVoteDelegation(d) => { Some((d.stake_address.clone(), &d.drep)) } diff --git a/modules/spo_state/src/historical_spo_state.rs b/modules/spo_state/src/historical_spo_state.rs index d6c2e9bd..afc129ed 100644 --- a/modules/spo_state/src/historical_spo_state.rs +++ b/modules/spo_state/src/historical_spo_state.rs @@ -1,4 +1,6 @@ -use acropolis_common::{queries::governance::VoteRecord, PoolRegistration, PoolUpdateEvent, StakeAddress}; +use acropolis_common::{ + queries::governance::VoteRecord, PoolRegistration, PoolUpdateEvent, StakeAddress, +}; use imbl::{HashSet, OrdMap, Vector}; use serde::{Deserialize, Serialize};