From 9123fa388df98527545dcc7da88d2b7d10cc494e Mon Sep 17 00:00:00 2001 From: William Hankins Date: Wed, 22 Oct 2025 00:24:40 +0000 Subject: [PATCH 1/6] Add TxIdentifier to delegation and stake registration certificate messages Signed-off-by: William Hankins --- common/src/queries/governance.rs | 6 +- common/src/types.rs | 91 ++++++++--- modules/accounts_state/src/state.rs | 114 ++++++++----- modules/drep_state/src/state.rs | 99 ++++++------ .../src/immutable_historical_account_store.rs | 76 +++++---- .../historical_accounts_state/src/state.rs | 129 ++++++++++++++- .../src/volatile_historical_accounts.rs | 9 +- .../src/handlers/governance.rs | 2 +- modules/rest_blockfrost/src/handlers/pools.rs | 17 +- modules/rest_blockfrost/src/types.rs | 10 +- modules/spo_state/src/state.rs | 150 ++++++++++-------- modules/tx_unpacker/src/map_parameters.rs | 146 ++++++++++------- modules/tx_unpacker/src/tx_unpacker.rs | 3 +- 13 files changed, 561 insertions(+), 291 deletions(-) diff --git a/common/src/queries/governance.rs b/common/src/queries/governance.rs index ed0a6462..8b796c01 100644 --- a/common/src/queries/governance.rs +++ b/common/src/queries/governance.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use crate::{ - Anchor, Credential, DRepCredential, GovActionId, Lovelace, ProposalProcedure, TxHash, Vote, - Voter, VotingProcedure, + Anchor, Credential, DRepCredential, GovActionId, Lovelace, ProposalProcedure, TxHash, + TxIdentifier, Vote, Voter, VotingProcedure, }; pub const DEFAULT_DREPS_QUERY_TOPIC: (&str, &str) = @@ -76,7 +76,7 @@ pub struct DRepUpdates { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DRepUpdateEvent { - pub tx_hash: TxHash, + pub tx_identifier: TxIdentifier, pub cert_index: u64, pub action: DRepActionUpdate, } diff --git a/common/src/types.rs b/common/src/types.rs index 22d2558a..e19afa6d 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -760,8 +760,8 @@ pub type RewardAccount = Vec; /// Pool registration with position #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PoolRegistrationWithPos { - pub reg: PoolRegistration, - pub tx_hash: TxHash, + pub cert: PoolRegistration, + pub tx_identifier: TxIdentifier, pub cert_index: u64, } @@ -823,8 +823,8 @@ pub struct PoolRegistration { // Pool Retirment with position #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PoolRetirementWithPos { - pub ret: PoolRetirement, - pub tx_hash: TxHash, + pub cert: PoolRetirement, + pub tx_identifier: TxIdentifier, pub cert_index: u64, } @@ -848,23 +848,23 @@ pub enum PoolUpdateAction { /// Pool Update Event #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PoolUpdateEvent { - pub tx_hash: TxHash, + pub tx_identifier: TxIdentifier, pub cert_index: u64, pub action: PoolUpdateAction, } impl PoolUpdateEvent { - pub fn register_event(tx_hash: TxHash, cert_index: u64) -> Self { + pub fn register_event(tx_identifier: TxIdentifier, cert_index: u64) -> Self { Self { - tx_hash, + tx_identifier, cert_index, action: PoolUpdateAction::Registered, } } - pub fn retire_event(tx_hash: TxHash, cert_index: u64) -> Self { + pub fn retire_event(tx_identifier: TxIdentifier, cert_index: u64) -> Self { Self { - tx_hash, + tx_identifier, cert_index, action: PoolUpdateAction::Deregistered, } @@ -901,6 +901,12 @@ pub struct StakeDelegation { pub operator: KeyHash, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct StakeDelegationWithPos { + pub cert: StakeDelegation, + pub tx_identifier: TxIdentifier, +} + /// SPO total delegation data (for SPDD) #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq)] pub struct DelegatedStake { @@ -971,6 +977,13 @@ pub struct Registration { pub deposit: Lovelace, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct RegistrationWithPos { + pub cert: Registration, + pub tx_identifier: TxIdentifier, + pub cert_index: u64, +} + /// Deregister stake (Conway version) = 'unreg_cert' #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Deregistration { @@ -981,6 +994,13 @@ pub struct Deregistration { pub refund: Lovelace, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct DeregistrationWithPos { + pub cert: Deregistration, + pub tx_identifier: TxIdentifier, + pub cert_index: u64, +} + /// DRepChoice (=CDDL drep, badly named) #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub enum DRepChoice { @@ -1020,6 +1040,12 @@ pub struct StakeAndVoteDelegation { pub drep: DRepChoice, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct StakeAndVoteDelegationWithPos { + pub cert: StakeAndVoteDelegation, + pub tx_identifier: TxIdentifier, +} + /// Stake delegation to SPO + registration = stake_reg_deleg_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeRegistrationAndDelegation { @@ -1033,6 +1059,12 @@ pub struct StakeRegistrationAndDelegation { pub deposit: Lovelace, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct StakeRegistrationAndDelegationWithPos { + pub cert: StakeRegistrationAndDelegation, + pub tx_identifier: TxIdentifier, +} + /// Vote delegation to DRep + registration = vote_reg_deleg_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeRegistrationAndVoteDelegation { @@ -1046,6 +1078,12 @@ pub struct StakeRegistrationAndVoteDelegation { pub deposit: Lovelace, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct StakeRegistrationAndVoteDelegationWithPos { + pub cert: StakeRegistrationAndVoteDelegation, + pub tx_identifier: TxIdentifier, +} + /// All the trimmings: /// Vote delegation to DRep + Stake delegation to SPO + registration /// = stake_vote_reg_deleg_cert @@ -1064,6 +1102,12 @@ pub struct StakeRegistrationAndStakeAndVoteDelegation { pub deposit: Lovelace, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct StakeRegistrationAndStakeAndVoteDelegationWithPos { + pub cert: StakeRegistrationAndStakeAndVoteDelegation, + pub tx_identifier: TxIdentifier, +} + /// Anchor #[serde_as] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] @@ -1093,8 +1137,8 @@ pub struct DRepRegistration { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DRepRegistrationWithPos { - pub reg: DRepRegistration, - pub tx_hash: TxHash, + pub cert: DRepRegistration, + pub tx_identifier: TxIdentifier, pub cert_index: u64, } @@ -1110,8 +1154,8 @@ pub struct DRepDeregistration { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DRepDeregistrationWithPos { - pub reg: DRepDeregistration, - pub tx_hash: TxHash, + pub cert: DRepDeregistration, + pub tx_identifier: TxIdentifier, pub cert_index: u64, } @@ -1127,8 +1171,8 @@ pub struct DRepUpdate { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DRepUpdateWithPos { - pub reg: DRepUpdate, - pub tx_hash: TxHash, + pub cert: DRepUpdate, + pub tx_identifier: TxIdentifier, pub cert_index: u64, } @@ -1732,6 +1776,7 @@ pub struct GovernanceOutcome { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeCredentialWithPos { pub stake_credential: StakeCredential, + pub tx_identifier: TxIdentifier, pub tx_index: u64, pub cert_index: u64, } @@ -1746,10 +1791,10 @@ pub enum TxCertificate { StakeRegistration(StakeCredentialWithPos), /// Stake de-registration - StakeDeregistration(StakeCredential), + StakeDeregistration(StakeCredentialWithPos), /// Stake Delegation to a pool - StakeDelegation(StakeDelegation), + StakeDelegation(StakeDelegationWithPos), /// Pool registration PoolRegistrationWithPos(PoolRegistrationWithPos), @@ -1764,25 +1809,25 @@ pub enum TxCertificate { MoveInstantaneousReward(MoveInstantaneousReward), /// New stake registration - Registration(Registration), + Registration(RegistrationWithPos), /// Stake deregistration - Deregistration(Deregistration), + Deregistration(DeregistrationWithPos), /// Vote delegation VoteDelegation(VoteDelegation), /// Combined stake and vote delegation - StakeAndVoteDelegation(StakeAndVoteDelegation), + StakeAndVoteDelegation(StakeAndVoteDelegationWithPos), /// Stake registration and SPO delegation - StakeRegistrationAndDelegation(StakeRegistrationAndDelegation), + StakeRegistrationAndDelegation(StakeRegistrationAndDelegationWithPos), /// Stake registration and vote delegation - StakeRegistrationAndVoteDelegation(StakeRegistrationAndVoteDelegation), + StakeRegistrationAndVoteDelegation(StakeRegistrationAndVoteDelegationWithPos), /// Stake registration and combined SPO and vote delegation - StakeRegistrationAndStakeAndVoteDelegation(StakeRegistrationAndStakeAndVoteDelegation), + StakeRegistrationAndStakeAndVoteDelegation(StakeRegistrationAndStakeAndVoteDelegationWithPos), /// Authorise a committee hot credential AuthCommitteeHot(AuthCommitteeHot), diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index fe3384be..97ff27b1 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -888,7 +888,7 @@ impl State { } TxCertificate::StakeDeregistration(sc) => { - self.deregister_stake_address(&sc, None); + self.deregister_stake_address(&sc.stake_credential, None); } TxCertificate::MoveInstantaneousReward(mir) => { @@ -896,15 +896,18 @@ impl State { } TxCertificate::Registration(reg) => { - self.register_stake_address(®.credential, Some(reg.deposit)); + self.register_stake_address(®.cert.credential, Some(reg.cert.deposit)); } TxCertificate::Deregistration(dreg) => { - self.deregister_stake_address(&dreg.credential, Some(dreg.refund)); + self.deregister_stake_address(&dreg.cert.credential, Some(dreg.cert.refund)); } TxCertificate::StakeDelegation(delegation) => { - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.record_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + ); } TxCertificate::VoteDelegation(delegation) => { @@ -912,24 +915,42 @@ impl State { } TxCertificate::StakeAndVoteDelegation(delegation) => { - self.record_stake_delegation(&delegation.credential, &delegation.operator); - self.record_drep_delegation(&delegation.credential, &delegation.drep); + self.record_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + ); + self.record_drep_delegation(&delegation.cert.credential, &delegation.cert.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.cert.credential, + Some(delegation.cert.deposit), + ); + self.record_stake_delegation( + &delegation.cert.credential, + &delegation.cert.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.cert.credential, + Some(delegation.cert.deposit), + ); + self.record_drep_delegation(&delegation.cert.credential, &delegation.cert.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.cert.credential, + Some(delegation.cert.deposit), + ); + self.record_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + ); + self.record_drep_delegation(&delegation.cert.credential, &delegation.cert.drep); } _ => (), @@ -990,9 +1011,11 @@ mod tests { use acropolis_common::{ protocol_params::ConwayParams, rational_number::RationalNumber, AddressNetwork, Anchor, Committee, Constitution, CostModel, Credential, DRepVotingThresholds, PoolVotingThresholds, - Pot, PotDelta, Ratio, Registration, StakeAddress, StakeAddressDelta, StakeAddressPayload, - StakeAndVoteDelegation, StakeRegistrationAndStakeAndVoteDelegation, - StakeRegistrationAndVoteDelegation, VoteDelegation, Withdrawal, + Pot, PotDelta, Ratio, Registration, RegistrationWithPos, StakeAddress, StakeAddressDelta, + StakeAddressPayload, StakeAndVoteDelegation, StakeAndVoteDelegationWithPos, + StakeRegistrationAndStakeAndVoteDelegation, + StakeRegistrationAndStakeAndVoteDelegationWithPos, StakeRegistrationAndVoteDelegation, + StakeRegistrationAndVoteDelegationWithPos, TxIdentifier, VoteDelegation, Withdrawal, }; const STAKE_KEY_HASH: [u8; 3] = [0x99, 0x0f, 0x00]; @@ -1372,34 +1395,53 @@ mod tests { let certificates = vec![ // register the first two SPOs separately from their delegation - TxCertificate::Registration(Registration { - credential: Credential::AddrKeyHash(spo1.clone()), - deposit: 1, + TxCertificate::Registration(RegistrationWithPos { + cert: Registration { + credential: Credential::AddrKeyHash(spo1.clone()), + deposit: 1, + }, + tx_identifier: TxIdentifier::default(), + cert_index: 0, }), - TxCertificate::Registration(Registration { - credential: Credential::AddrKeyHash(spo2.clone()), - deposit: 1, + TxCertificate::Registration(RegistrationWithPos { + cert: Registration { + credential: Credential::AddrKeyHash(spo2.clone()), + deposit: 1, + }, + tx_identifier: TxIdentifier::default(), + cert_index: 0, }), TxCertificate::VoteDelegation(VoteDelegation { credential: Credential::AddrKeyHash(spo1.clone()), drep: DRepChoice::Key(DREP_HASH.to_vec()), }), - TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { - credential: Credential::AddrKeyHash(spo2.clone()), - operator: spo1.clone(), - drep: DRepChoice::Script(DREP_HASH.to_vec()), - }), - TxCertificate::StakeRegistrationAndVoteDelegation(StakeRegistrationAndVoteDelegation { - credential: Credential::AddrKeyHash(spo3.clone()), - drep: DRepChoice::Abstain, - deposit: 1, + TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegationWithPos { + cert: StakeAndVoteDelegation { + credential: Credential::AddrKeyHash(spo2.clone()), + operator: spo1.clone(), + drep: DRepChoice::Script(DREP_HASH.to_vec()), + }, + tx_identifier: TxIdentifier::default(), }), + TxCertificate::StakeRegistrationAndVoteDelegation( + StakeRegistrationAndVoteDelegationWithPos { + cert: StakeRegistrationAndVoteDelegation { + credential: Credential::AddrKeyHash(spo3.clone()), + drep: DRepChoice::Abstain, + deposit: 1, + }, + tx_identifier: TxIdentifier::default(), + }, + ), TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( - StakeRegistrationAndStakeAndVoteDelegation { - credential: Credential::AddrKeyHash(spo4.clone()), - operator: spo1.clone(), - drep: DRepChoice::NoConfidence, - deposit: 1, + StakeRegistrationAndStakeAndVoteDelegationWithPos { + cert: StakeRegistrationAndStakeAndVoteDelegation { + credential: Credential::AddrKeyHash(spo4.clone()), + operator: spo1.clone(), + drep: DRepChoice::NoConfidence, + deposit: 1, + }, + tx_identifier: TxIdentifier::default(), }, ), ]; diff --git a/modules/drep_state/src/state.rs b/modules/drep_state/src/state.rs index e21cf98d..8f36cd13 100644 --- a/modules/drep_state/src/state.rs +++ b/modules/drep_state/src/state.rs @@ -320,31 +320,31 @@ impl State { fn process_one_cert(&mut self, tx_cert: &TxCertificate, epoch: u64) -> Result { match tx_cert { TxCertificate::DRepRegistration(reg) => { - let new = match self.dreps.get_mut(®.reg.credential) { + let new = match self.dreps.get_mut(®.cert.credential) { Some(drep) => { - if reg.reg.deposit != 0 { + if reg.cert.deposit != 0 { return Err(anyhow!( "DRep registration {:?}: replacement requires deposit = 0, got {}", - reg.reg.credential, - reg.reg.deposit + reg.cert.credential, + reg.cert.deposit )); } - drep.anchor = reg.reg.anchor.clone(); + drep.anchor = reg.cert.anchor.clone(); false } None => { self.dreps.insert( - reg.reg.credential.clone(), - DRepRecord::new(reg.reg.deposit, reg.reg.anchor.clone()), + reg.cert.credential.clone(), + DRepRecord::new(reg.cert.deposit, reg.cert.anchor.clone()), ); true } }; if self.historical_dreps.is_some() { - if let Err(err) = self.update_historical(®.reg.credential, true, |entry| { + if let Err(err) = self.update_historical(®.cert.credential, true, |entry| { if let Some(info) = entry.info.as_mut() { - info.deposit = reg.reg.deposit; + info.deposit = reg.cert.deposit; info.expired = false; info.retired = false; info.active_epoch = Some(epoch); @@ -352,12 +352,12 @@ impl State { } if let Some(updates) = entry.updates.as_mut() { updates.push(DRepUpdateEvent { - tx_hash: reg.tx_hash, + tx_identifier: reg.tx_identifier, cert_index: reg.cert_index, action: DRepActionUpdate::Registered, }); } - if let Some(anchor) = ®.reg.anchor { + if let Some(anchor) = ®.cert.anchor { if let Some(inner) = entry.metadata.as_mut() { *inner = Some(anchor.clone()); } @@ -372,16 +372,16 @@ impl State { TxCertificate::DRepDeregistration(reg) => { // Update live state - if self.dreps.remove(®.reg.credential).is_none() { + if self.dreps.remove(®.cert.credential).is_none() { return Err(anyhow!( "DRep deregistration {:?}: credential not found", - reg.reg.credential + reg.cert.credential )); } // Update history if enabled if self.historical_dreps.is_some() { - if let Err(err) = self.update_historical(®.reg.credential, false, |entry| { + if let Err(err) = self.update_historical(®.cert.credential, false, |entry| { if let Some(info) = entry.info.as_mut() { info.deposit = 0; info.expired = false; @@ -391,7 +391,7 @@ impl State { } if let Some(updates) = entry.updates.as_mut() { updates.push(DRepUpdateEvent { - tx_hash: reg.tx_hash, + tx_identifier: reg.tx_identifier, cert_index: reg.cert_index, action: DRepActionUpdate::Deregistered, }); @@ -406,13 +406,16 @@ impl State { TxCertificate::DRepUpdate(reg) => { // Update live state - let drep = self.dreps.get_mut(®.reg.credential).ok_or_else(|| { - anyhow!("DRep update {:?}: credential not found", reg.reg.credential) + let drep = self.dreps.get_mut(®.cert.credential).ok_or_else(|| { + anyhow!( + "DRep update {:?}: credential not found", + reg.cert.credential + ) })?; - drep.anchor = reg.reg.anchor.clone(); + drep.anchor = reg.cert.anchor.clone(); // Update history if enabled - if let Err(err) = self.update_historical(®.reg.credential, false, |entry| { + if let Err(err) = self.update_historical(®.cert.credential, false, |entry| { if let Some(info) = entry.info.as_mut() { info.expired = false; info.retired = false; @@ -420,12 +423,12 @@ impl State { } if let Some(updates) = entry.updates.as_mut() { updates.push(DRepUpdateEvent { - tx_hash: reg.tx_hash, + tx_identifier: reg.tx_identifier, cert_index: reg.cert_index, action: DRepActionUpdate::Updated, }); } - if let Some(anchor) = ®.reg.anchor { + if let Some(anchor) = ®.cert.anchor { if let Some(inner) = entry.metadata.as_mut() { *inner = Some(anchor.clone()); } @@ -541,10 +544,12 @@ impl State { fn extract_delegation_fields(cert: &TxCertificate) -> Option<(&StakeCredential, &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::StakeAndVoteDelegation(d) => Some((&d.cert.credential, &d.cert.drep)), + TxCertificate::StakeRegistrationAndVoteDelegation(d) => { + Some((&d.cert.credential, &d.cert.drep)) + } TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(d) => { - Some((&d.credential, &d.drep)) + Some((&d.cert.credential, &d.cert.drep)) } _ => None, } @@ -564,7 +569,7 @@ mod tests { use crate::state::{DRepRecord, DRepStorageConfig, State}; use acropolis_common::{ Anchor, Credential, DRepDeregistration, DRepDeregistrationWithPos, DRepRegistration, - DRepRegistrationWithPos, DRepUpdate, DRepUpdateWithPos, TxCertificate, TxHash, + DRepRegistrationWithPos, DRepUpdate, DRepUpdateWithPos, TxCertificate, TxIdentifier, }; const CRED_1: [u8; 28] = [ @@ -580,12 +585,12 @@ mod tests { fn test_drep_process_one_certificate() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); let tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - reg: DRepRegistration { + cert: DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); let mut state = State::new(DRepStorageConfig::default()); @@ -605,24 +610,24 @@ mod tests { fn test_drep_do_not_replace_existing_certificate() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); let tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - reg: DRepRegistration { + cert: DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); let bad_tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - reg: DRepRegistration { + cert: DRepRegistration { credential: tx_cred.clone(), deposit: 600000000, anchor: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); assert!(state.process_one_cert(&bad_tx_cert, 1).is_err()); @@ -642,12 +647,12 @@ mod tests { fn test_drep_update_certificate() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); let tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - reg: DRepRegistration { + cert: DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); let mut state = State::new(DRepStorageConfig::default()); @@ -658,11 +663,11 @@ mod tests { data_hash: vec![0x13, 0x37], }; let update_anchor_tx_cert = TxCertificate::DRepUpdate(DRepUpdateWithPos { - reg: DRepUpdate { + cert: DRepUpdate { credential: tx_cred.clone(), anchor: Some(anchor.clone()), }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); @@ -686,12 +691,12 @@ mod tests { fn test_drep_do_not_update_nonexistent_certificate() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); let tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - reg: DRepRegistration { + cert: DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); let mut state = State::new(DRepStorageConfig::default()); @@ -702,11 +707,11 @@ mod tests { data_hash: vec![0x13, 0x37], }; let update_anchor_tx_cert = TxCertificate::DRepUpdate(DRepUpdateWithPos { - reg: DRepUpdate { + cert: DRepUpdate { credential: Credential::AddrKeyHash(CRED_2.to_vec()), anchor: Some(anchor.clone()), }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); assert!(state.process_one_cert(&update_anchor_tx_cert, 1).is_err()); @@ -726,23 +731,23 @@ mod tests { fn test_drep_deregister() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); let tx_cert = TxCertificate::DRepRegistration(acropolis_common::DRepRegistrationWithPos { - reg: DRepRegistration { + cert: DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); let unregister_tx_cert = TxCertificate::DRepDeregistration(DRepDeregistrationWithPos { - reg: DRepDeregistration { + cert: DRepDeregistration { credential: tx_cred.clone(), refund: 500000000, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); assert_eq!( @@ -757,23 +762,23 @@ mod tests { fn test_drep_do_not_deregister_nonexistent_cert() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); let tx_cert = TxCertificate::DRepRegistration(acropolis_common::DRepRegistrationWithPos { - reg: DRepRegistration { + cert: DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); let unregister_tx_cert = TxCertificate::DRepDeregistration(DRepDeregistrationWithPos { - reg: DRepDeregistration { + cert: DRepDeregistration { credential: Credential::AddrKeyHash(CRED_2.to_vec()), refund: 500000000, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }); assert!(state.process_one_cert(&unregister_tx_cert, 1).is_err()); diff --git a/modules/historical_accounts_state/src/immutable_historical_account_store.rs b/modules/historical_accounts_state/src/immutable_historical_account_store.rs index 40c807ed..fd384228 100644 --- a/modules/historical_accounts_state/src/immutable_historical_account_store.rs +++ b/modules/historical_accounts_state/src/immutable_historical_account_store.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, path::Path}; -use acropolis_common::{ShelleyAddress, StakeAddress}; +use acropolis_common::{ShelleyAddress, StakeCredential}; use anyhow::Result; use fjall::{Keyspace, Partition, PartitionCreateOptions}; use minicbor::{decode, to_vec}; @@ -30,7 +30,7 @@ pub struct ImmutableHistoricalAccountStore { mir_history: Partition, addresses: Partition, keyspace: Keyspace, - pub pending: Mutex>>, + pub pending: Mutex>>, } impl ImmutableHistoricalAccountStore { @@ -74,7 +74,8 @@ impl ImmutableHistoricalAccountStore { }) } - /// Persists volatile UTxOs, transactions, and totals into their respective Fjall partitions for an entire epoch. + /// Persists volatile registrations, delegations, MIRs, withdrawals, rewards, + /// and addresses into their respective Fjall partitions for an entire epoch. /// Skips any partitions that have already stored the given epoch. /// All writes are batched and committed atomically, preventing on-disk corruption in case of failure. pub async fn persist_epoch(&self, epoch: u64, config: &HistoricalAccountsConfig) -> Result<()> { @@ -152,7 +153,7 @@ impl ImmutableHistoricalAccountStore { for (account, entry) in block_map { change_count += 1; - let account_key = account.to_bytes_key()?; + let account_key = account.get_hash(); // Persist rewards if persist_rewards_history { @@ -190,39 +191,39 @@ impl ImmutableHistoricalAccountStore { batch.insert(&self.active_stake_history, &account_key, to_vec(&live)?); } - // Persist account delegation updates - if persist_delegation_history { - let mut live: Vec = self - .delegation_history + // Persist registrations + if persist_registration_history { + let mut live: Vec = self + .registration_history .get(&account_key)? .map(|bytes| decode(&bytes)) .transpose()? .unwrap_or_default(); - if let Some(updates) = &entry.delegation_history { + if let Some(updates) = &entry.registration_history { live.extend(updates.iter().cloned()); } - batch.insert(&self.delegation_history, &account_key, to_vec(&live)?); + batch.insert(&self.registration_history, &account_key, to_vec(&live)?); } - // Persist account registration updates - if persist_registration_history { - let mut live: Vec = self - .registration_history + // Persist delegations + if persist_delegation_history { + let mut live: Vec = self + .delegation_history .get(&account_key)? .map(|bytes| decode(&bytes)) .transpose()? .unwrap_or_default(); - if let Some(updates) = &entry.registration_history { + if let Some(updates) = &entry.delegation_history { live.extend(updates.iter().cloned()); } - batch.insert(&self.registration_history, &account_key, to_vec(&live)?); + batch.insert(&self.delegation_history, &account_key, to_vec(&live)?); } - // Persist withdrawal updates + // Persist withdrawals if persist_withdrawal_history { let mut live: Vec = self .withdrawal_history @@ -238,7 +239,7 @@ impl ImmutableHistoricalAccountStore { batch.insert(&self.withdrawal_history, &account_key, to_vec(&live)?); } - // Persist MIR updates + // Persist MIRs if persist_mir_history { let mut live: Vec = self .mir_history @@ -254,7 +255,7 @@ impl ImmutableHistoricalAccountStore { batch.insert(&self.mir_history, &account_key, to_vec(&live)?); } - // Persist address updates + // Persist new addresses if persist_addresses { let mut live: Vec = self .addresses @@ -273,6 +274,13 @@ impl ImmutableHistoricalAccountStore { } // Metadata markers + if persist_rewards_history { + batch.insert( + &self.rewards_history, + ACCOUNT_REWARDS_HISTORY_COUNTER, + &epoch.to_le_bytes(), + ); + } if persist_active_stake_history { batch.insert( &self.active_stake_history, @@ -328,16 +336,16 @@ impl ImmutableHistoricalAccountStore { } } - pub async fn update_immutable(&self, drained: Vec>) { + pub async fn update_immutable(&self, drained: Vec>) { let mut pending = self.pending.lock().await; pending.extend(drained); } pub async fn _get_rewards_history( &self, - account: &StakeAddress, + account: &StakeCredential, ) -> Result>> { - let key = account.to_bytes_key()?; + let key = account.get_hash(); let mut live: Vec = self .rewards_history @@ -367,9 +375,9 @@ impl ImmutableHistoricalAccountStore { pub async fn _get_active_stake_history( &self, - account: &StakeAddress, + account: &StakeCredential, ) -> Result>> { - let key = account.to_bytes_key()?; + let key = account.get_hash(); let mut live: Vec = self .active_stake_history @@ -399,9 +407,9 @@ impl ImmutableHistoricalAccountStore { pub async fn _get_delegation_history( &self, - account: &StakeAddress, + account: &StakeCredential, ) -> Result>> { - let key = account.to_bytes_key()?; + let key = account.get_hash(); let mut live: Vec = self .delegation_history .get(&key)? @@ -427,9 +435,9 @@ impl ImmutableHistoricalAccountStore { pub async fn _get_registration_history( &self, - account: &StakeAddress, + account: &StakeCredential, ) -> Result>> { - let key = account.to_bytes_key()?; + let key = account.get_hash(); let mut live: Vec = self .registration_history .get(&key)? @@ -455,9 +463,9 @@ impl ImmutableHistoricalAccountStore { pub async fn _get_withdrawal_history( &self, - account: &StakeAddress, + account: &StakeCredential, ) -> Result>> { - let key = account.to_bytes_key()?; + let key = account.get_hash(); let mut live: Vec = self .withdrawal_history .get(&key)? @@ -483,9 +491,9 @@ impl ImmutableHistoricalAccountStore { pub async fn _get_mir_history( &self, - account: &StakeAddress, + account: &StakeCredential, ) -> Result>> { - let key = account.to_bytes_key()?; + let key = account.get_hash(); let mut live: Vec = self .mir_history .get(&key)? @@ -511,9 +519,9 @@ impl ImmutableHistoricalAccountStore { pub async fn _get_addresses( &self, - account: &StakeAddress, + account: &StakeCredential, ) -> Result>> { - let key = account.to_bytes_key()?; + let key = account.get_hash(); let mut live: Vec = self .mir_history .get(&key)? diff --git a/modules/historical_accounts_state/src/state.rs b/modules/historical_accounts_state/src/state.rs index 17213b0f..d882dcf6 100644 --- a/modules/historical_accounts_state/src/state.rs +++ b/modules/historical_accounts_state/src/state.rs @@ -7,7 +7,8 @@ use acropolis_common::{ messages::{ AddressDeltasMessage, StakeRewardDeltasMessage, TxCertificatesMessage, WithdrawalsMessage, }, - BlockInfo, PoolId, ShelleyAddress, StakeAddress, TxIdentifier, + BlockInfo, MoveInstantaneousReward, PoolId, ShelleyAddress, StakeAddress, StakeCredential, + TxCertificate, TxIdentifier, }; use crate::{ @@ -65,7 +66,7 @@ pub struct DelegationUpdate { #[derive(Debug, Clone, minicbor::Decode, minicbor::Encode)] pub struct RegistrationUpdate { #[n(0)] - tx_hash: TxIdentifier, + tx_identifier: TxIdentifier, #[n(1)] deregistered: bool, } @@ -73,7 +74,7 @@ pub struct RegistrationUpdate { #[derive(Debug, Clone, minicbor::Decode, minicbor::Encode)] pub struct AccountWithdrawal { #[n(0)] - tx_hash: TxIdentifier, + tx_identifier: TxIdentifier, #[n(1)] amount: u64, } @@ -135,7 +136,78 @@ impl State { Ok(()) } - pub fn handle_tx_certificates(&mut self, _tx_certs: &TxCertificatesMessage) -> Result<()> { + pub fn handle_tx_certificates(&mut self, tx_certs: &TxCertificatesMessage) -> Result<()> { + // Handle certificates + for tx_cert in tx_certs.certificates.iter() { + match tx_cert { + // Pre-Conway stake registration/deregistration certs + TxCertificate::StakeRegistration(sc) => { + self.handle_stake_registration(&sc.stake_credential, &sc.tx_identifier); + } + TxCertificate::StakeDeregistration(sc) => { + self.handle_stake_deregistration(&sc.stake_credential, &sc.tx_identifier); + } + + // Post-Conway stake registration/deregistration certs + TxCertificate::Registration(reg) => { + self.handle_stake_registration(®.cert.credential, ®.tx_identifier); + } + TxCertificate::Deregistration(dreg) => { + self.handle_stake_deregistration(&dreg.cert.credential, &dreg.tx_identifier); + } + + // Registration and delegation certs + TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(delegation) => { + self.handle_stake_registration( + &delegation.cert.credential, + &delegation.tx_identifier, + ); + self.handle_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + &delegation.tx_identifier, + ); + } + TxCertificate::StakeRegistrationAndDelegation(delegation) => { + self.handle_stake_registration( + &delegation.cert.credential, + &delegation.tx_identifier, + ); + self.handle_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + &delegation.tx_identifier, + ); + } + + // Delegation certs + TxCertificate::StakeDelegation(delegation) => { + self.handle_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + &delegation.tx_identifier, + ); + } + TxCertificate::StakeAndVoteDelegation(delegation) => { + self.handle_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + &delegation.tx_identifier, + ); + } + TxCertificate::StakeRegistrationAndVoteDelegation(delegation) => { + self.handle_stake_registration( + &delegation.cert.credential, + &delegation.tx_identifier, + ); + } + + // MIR certs + TxCertificate::MoveInstantaneousReward(mir) => self.handle_mir(mir), + + _ => (), + }; + } Ok(()) } @@ -183,4 +255,53 @@ impl State { pub fn _get_addresses(&self, _account: StakeAddress) -> Result> { Ok(Vec::new()) } + + fn handle_stake_registration( + &mut self, + account: &StakeCredential, + tx_identifier: &TxIdentifier, + ) { + let volatile = self.volatile.window.back_mut().expect("window should never be empty"); + + let entry = volatile.entry(account.clone()).or_default(); + + if let Some(registration_history) = &mut entry.registration_history { + registration_history.push(RegistrationUpdate { + tx_identifier: *tx_identifier, + deregistered: false, + }); + } else { + entry.registration_history = Some(vec![RegistrationUpdate { + tx_identifier: *tx_identifier, + deregistered: false, + }]); + } + } + + fn handle_stake_deregistration( + &mut self, + account: &StakeCredential, + tx_identifier: &TxIdentifier, + ) { + let volatile = self.volatile.window.back_mut().expect("window should never be empty"); + + let entry = volatile.entry(account.clone()).or_default(); + + if let Some(mut registration_history) = entry.registration_history.clone() { + registration_history.push(RegistrationUpdate { + tx_identifier: *tx_identifier, + deregistered: true, + }) + } + } + + fn handle_stake_delegation( + &mut self, + _account: &StakeCredential, + _pool: &PoolId, + _tx_identifier: &TxIdentifier, + ) { + } + + fn handle_mir(&mut self, _mir: &MoveInstantaneousReward) {} } diff --git a/modules/historical_accounts_state/src/volatile_historical_accounts.rs b/modules/historical_accounts_state/src/volatile_historical_accounts.rs index d476627b..99d925b9 100644 --- a/modules/historical_accounts_state/src/volatile_historical_accounts.rs +++ b/modules/historical_accounts_state/src/volatile_historical_accounts.rs @@ -1,12 +1,11 @@ use std::collections::{HashMap, VecDeque}; -use acropolis_common::StakeAddress; - use crate::state::AccountEntry; +use acropolis_common::StakeCredential; #[derive(Debug, Clone)] pub struct VolatileHistoricalAccounts { - pub window: VecDeque>, + pub window: VecDeque>, pub start_block: u64, pub epoch_start_block: u64, pub last_persisted_epoch: Option, @@ -45,7 +44,7 @@ impl VolatileHistoricalAccounts { self.epoch_start_block = block_number; } - pub fn rollback_before(&mut self, block: u64) -> Vec<(StakeAddress, AccountEntry)> { + pub fn rollback_before(&mut self, block: u64) -> Vec<(StakeCredential, AccountEntry)> { let mut out = Vec::new(); while self.start_block + self.window.len() as u64 >= block { @@ -58,7 +57,7 @@ impl VolatileHistoricalAccounts { out } - pub fn prune_volatile(&mut self) -> Vec> { + pub fn prune_volatile(&mut self) -> Vec> { let epoch = self.last_persisted_epoch.map(|e| e + 1).unwrap_or(0); let blocks_to_drain = (self.epoch_start_block - self.start_block) as usize; diff --git a/modules/rest_blockfrost/src/handlers/governance.rs b/modules/rest_blockfrost/src/handlers/governance.rs index ed0eff5e..6d24748d 100644 --- a/modules/rest_blockfrost/src/handlers/governance.rs +++ b/modules/rest_blockfrost/src/handlers/governance.rs @@ -411,7 +411,7 @@ pub async fn handle_drep_updates_blockfrost( .updates .iter() .map(|event| DRepUpdateREST { - tx_hash: hex::encode(event.tx_hash), + tx_hash: "TxHash lookup not yet implemented".to_string(), cert_index: event.cert_index, action: event.action.clone(), }) diff --git a/modules/rest_blockfrost/src/handlers/pools.rs b/modules/rest_blockfrost/src/handlers/pools.rs index 74cf52bf..e33c7616 100644 --- a/modules/rest_blockfrost/src/handlers/pools.rs +++ b/modules/rest_blockfrost/src/handlers/pools.rs @@ -9,7 +9,7 @@ use acropolis_common::{ }, rest_helper::ToCheckedF64, serialization::Bech32WithHrp, - PoolRetirement, PoolUpdateAction, StakeCredential, TxHash, + PoolRetirement, PoolUpdateAction, StakeCredential, TxIdentifier, }; use anyhow::Result; use caryatid_sdk::Context; @@ -595,24 +595,25 @@ async fn handle_pools_spo_blockfrost( return Ok(RESTResponse::with_json(404, "Pool Not Found")); }; let pool_updates = pool_updates?; - let registrations: Option> = pool_updates.as_ref().map(|updates| { + // TODO: Query TxHash from chainstore module for registrations and retirements + let _registrations: Option> = pool_updates.as_ref().map(|updates| { updates .iter() .filter_map(|update| { if update.action == PoolUpdateAction::Registered { - Some(update.tx_hash) + Some(update.tx_identifier) } else { None } }) .collect() }); - let retirements: Option> = pool_updates.as_ref().map(|updates| { + let _retirements: Option> = pool_updates.as_ref().map(|updates| { updates .iter() .filter_map(|update| { if update.action == PoolUpdateAction::Deregistered { - Some(update.tx_hash) + Some(update.tx_identifier) } else { None } @@ -727,8 +728,8 @@ async fn handle_pools_spo_blockfrost( fixed_cost: pool_info.cost, reward_account, pool_owners, - registration: registrations, - retirement: retirements, + registration: "TxHash lookup not yet implemented".to_string(), + retirement: "TxHash lookup not yet implemented".to_string(), }; match serde_json::to_string(&pool_info_rest) { @@ -1143,7 +1144,7 @@ pub async fn handle_pool_updates_blockfrost( let pool_updates_rest = pool_updates .into_iter() .map(|u| PoolUpdateEventRest { - tx_hash: u.tx_hash, + tx_hash: "TxHash lookup not yet implemented".to_string(), cert_index: u.cert_index, action: u.action, }) diff --git a/modules/rest_blockfrost/src/types.rs b/modules/rest_blockfrost/src/types.rs index 1976db48..cb59862b 100644 --- a/modules/rest_blockfrost/src/types.rs +++ b/modules/rest_blockfrost/src/types.rs @@ -403,8 +403,7 @@ impl From for PoolRelayRest { #[serde_as] #[derive(Serialize)] pub struct PoolUpdateEventRest { - #[serde_as(as = "Hex")] - pub tx_hash: TxHash, + pub tx_hash: String, pub cert_index: u64, pub action: PoolUpdateAction, } @@ -447,10 +446,9 @@ pub struct PoolInfoRest { pub fixed_cost: u64, pub reward_account: String, pub pool_owners: Vec, - #[serde_as(as = "Option>")] - pub registration: Option>, - #[serde_as(as = "Option>")] - pub retirement: Option>, + // TODO: Query chain store module to retrieve TxHash from TxIdentifier + pub registration: String, + pub retirement: String, } // REST response structure for protocol params diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 5c4dcb77..4b172699 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -328,37 +328,37 @@ impl State { reg_with_pos: &PoolRegistrationWithPos, ) { let PoolRegistrationWithPos { - reg, - tx_hash, + cert, + tx_identifier, cert_index, } = reg_with_pos; - if self.spos.contains_key(®.operator) { + if self.spos.contains_key(&cert.operator) { debug!( block = block.number, "New pending SPO update {} {:?}", - hex::encode(®.operator), - reg + hex::encode(&cert.operator), + cert ); - self.pending_updates.insert(reg.operator.clone(), reg.clone()); + self.pending_updates.insert(cert.operator.clone(), cert.clone()); } else { debug!( block = block.number, "Registering SPO {} {:?}", - hex::encode(®.operator), - reg + hex::encode(&cert.operator), + cert ); - self.spos.insert(reg.operator.clone(), reg.clone()); + self.spos.insert(cert.operator.clone(), cert.clone()); } // Remove any existing queued deregistrations for (epoch, deregistrations) in &mut self.pending_deregistrations.iter_mut() { let old_len = deregistrations.len(); - deregistrations.retain(|d| *d != reg.operator); + deregistrations.retain(|d| *d != cert.operator); if deregistrations.len() != old_len { debug!( "Removed pending deregistration of SPO {} from epoch {}", - hex::encode(®.operator), + hex::encode(&cert.operator), epoch ); } @@ -369,11 +369,11 @@ impl State { // Don't check there was registration already or not // because we don't remove registration when pool is retired. let historical_spo = historical_spos - .entry(reg.operator.clone()) + .entry(cert.operator.clone()) .or_insert_with(|| HistoricalSPOState::new(&self.store_config)); - historical_spo.add_pool_registration(reg); + historical_spo.add_pool_registration(cert); historical_spo.add_pool_updates(PoolUpdateEvent::register_event( - tx_hash.clone(), + tx_identifier.clone(), *cert_index, )); } @@ -381,42 +381,42 @@ impl State { fn handle_pool_retirement(&mut self, block: &BlockInfo, ret_with_pos: &PoolRetirementWithPos) { let PoolRetirementWithPos { - ret, - tx_hash, + cert, + tx_identifier, cert_index, } = ret_with_pos; debug!( "SPO {} wants to retire at the end of epoch {} (cert in block number {})", - hex::encode(&ret.operator), - ret.epoch, + hex::encode(&cert.operator), + cert.epoch, block.number ); - if ret.epoch <= self.epoch { + if cert.epoch <= self.epoch { error!( "SPO retirement received for current or past epoch {} for SPO {}", - ret.epoch, - hex::encode(&ret.operator) + cert.epoch, + hex::encode(&cert.operator) ); - } else if ret.epoch > self.epoch + TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH { + } else if cert.epoch > self.epoch + TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH { error!( "SPO retirement received for epoch {} that exceeds future limit for SPO {}", - ret.epoch, - hex::encode(&ret.operator) + cert.epoch, + hex::encode(&cert.operator) ); } else { // Replace any existing queued deregistrations for (epoch, deregistrations) in &mut self.pending_deregistrations.iter_mut() { let old_len = deregistrations.len(); - deregistrations.retain(|d| *d != ret.operator); + deregistrations.retain(|d| *d != cert.operator); if deregistrations.len() != old_len { debug!( "Replaced pending deregistration of SPO {} from epoch {}", - hex::encode(&ret.operator), + hex::encode(&cert.operator), epoch ); } } - self.pending_deregistrations.entry(ret.epoch).or_default().push(ret.operator.clone()); + self.pending_deregistrations.entry(cert.epoch).or_default().push(cert.operator.clone()); // Note: not removing pending updates - the deregistation may happen many // epochs later than the update, and we apply updates before deregistrations @@ -425,13 +425,13 @@ impl State { // update historical spos if let Some(historical_spos) = self.historical_spos.as_mut() { - if let Some(historical_spo) = historical_spos.get_mut(&ret.operator) { + if let Some(historical_spo) = historical_spos.get_mut(&cert.operator) { historical_spo - .add_pool_updates(PoolUpdateEvent::retire_event(tx_hash.clone(), *cert_index)); + .add_pool_updates(PoolUpdateEvent::retire_event(*tx_identifier, *cert_index)); } else { error!( "Historical SPO for {} not registered when try to retire it", - hex::encode(&ret.operator) + hex::encode(&cert.operator) ); } } @@ -552,34 +552,46 @@ impl State { self.register_stake_address(&sc_with_pos.stake_credential); } TxCertificate::StakeDeregistration(sc) => { - self.deregister_stake_address(&sc); + self.deregister_stake_address(&sc.stake_credential); } TxCertificate::Registration(reg) => { - self.register_stake_address(®.credential); + self.register_stake_address(®.cert.credential); // we don't care deposite } TxCertificate::Deregistration(dreg) => { - self.deregister_stake_address(&dreg.credential); + self.deregister_stake_address(&dreg.cert.credential); // we don't care refund } TxCertificate::StakeDelegation(delegation) => { - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.record_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + ); } TxCertificate::StakeAndVoteDelegation(delegation) => { - self.record_stake_delegation(&delegation.credential, &delegation.operator); + self.record_stake_delegation( + &delegation.cert.credential, + &delegation.cert.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.cert.credential); + self.record_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + ); } TxCertificate::StakeRegistrationAndVoteDelegation(delegation) => { - self.register_stake_address(&delegation.credential); + self.register_stake_address(&delegation.cert.credential); // 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.cert.credential); + self.record_stake_delegation( + &delegation.cert.credential, + &delegation.cert.operator, + ); // don't care about vote delegation } _ => (), @@ -681,7 +693,7 @@ mod tests { use crate::test_utils::*; use acropolis_common::{ state_history::{StateHistory, StateHistoryStore}, - PoolRetirement, Ratio, TxCertificate, TxHash, + PoolRetirement, Ratio, TxCertificate, TxIdentifier, }; use tokio::sync::Mutex; @@ -712,7 +724,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { + cert: PoolRegistration { operator: vec![0], vrf_key_hash: vec![0], pledge: 0, @@ -726,7 +738,7 @@ mod tests { relays: vec![], pool_metadata: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 1, }, )); @@ -743,11 +755,11 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![0], epoch: 1, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -769,11 +781,11 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![0], epoch: 2, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -783,11 +795,11 @@ mod tests { msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![1], epoch: 2, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -814,11 +826,11 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![0], epoch: 2, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -830,11 +842,11 @@ mod tests { msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![1], epoch: 2, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -861,7 +873,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { + cert: PoolRegistration { operator: vec![0], vrf_key_hash: vec![0], pledge: 0, @@ -875,7 +887,7 @@ mod tests { relays: vec![], pool_metadata: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -889,11 +901,11 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![0], epoch: 1, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -917,7 +929,7 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { + cert: PoolRegistration { operator: vec![0], vrf_key_hash: vec![0], pledge: 0, @@ -931,7 +943,7 @@ mod tests { relays: vec![], pool_metadata: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -946,11 +958,11 @@ mod tests { msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![0], epoch: 1, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -988,11 +1000,11 @@ mod tests { let mut msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![0], epoch: 2, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -1002,11 +1014,11 @@ mod tests { msg = new_certs_msg(); msg.certificates.push(TxCertificate::PoolRetirementWithPos( PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: vec![1], epoch: 3, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -1035,7 +1047,7 @@ mod tests { let spo_id = keyhash_224(&vec![1 as u8]); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { + cert: PoolRegistration { operator: spo_id.clone(), vrf_key_hash: keyhash_224(&vec![0]), pledge: 0, @@ -1049,7 +1061,7 @@ mod tests { relays: vec![], pool_metadata: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); @@ -1085,7 +1097,7 @@ mod tests { let spo_id = keyhash_224(&vec![1 as u8]); msg.certificates.push(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { + cert: PoolRegistration { operator: spo_id.clone(), vrf_key_hash: keyhash_224(&vec![0]), pledge: 0, @@ -1099,7 +1111,7 @@ mod tests { relays: vec![], pool_metadata: None, }, - tx_hash: TxHash::default(), + tx_identifier: TxIdentifier::default(), cert_index: 0, }, )); diff --git a/modules/tx_unpacker/src/map_parameters.rs b/modules/tx_unpacker/src/map_parameters.rs index d7ae1fd8..baa7207b 100644 --- a/modules/tx_unpacker/src/map_parameters.rs +++ b/modules/tx_unpacker/src/map_parameters.rs @@ -200,7 +200,7 @@ fn map_relay(relay: &PallasRelay) -> Relay { /// Derive our TxCertificate from a Pallas Certificate pub fn map_certificate( cert: &MultiEraCert, - tx_hash: TxHash, + tx_identifier: TxIdentifier, tx_index: u16, cert_index: usize, ) -> Result { @@ -211,17 +211,26 @@ pub fn map_certificate( alonzo::Certificate::StakeRegistration(cred) => { Ok(TxCertificate::StakeRegistration(StakeCredentialWithPos { stake_credential: map_stake_credential(cred), + tx_identifier, + tx_index: tx_index.try_into().unwrap(), + cert_index: cert_index.try_into().unwrap(), + })) + } + alonzo::Certificate::StakeDeregistration(cred) => { + Ok(TxCertificate::StakeDeregistration(StakeCredentialWithPos { + stake_credential: map_stake_credential(cred), + tx_identifier, 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)), - ), alonzo::Certificate::StakeDelegation(cred, pool_key_hash) => { - Ok(TxCertificate::StakeDelegation(StakeDelegation { - credential: map_stake_credential(cred), - operator: pool_key_hash.to_vec(), + Ok(TxCertificate::StakeDelegation(StakeDelegationWithPos { + cert: StakeDelegation { + credential: map_stake_credential(cred), + operator: pool_key_hash.to_vec(), + }, + tx_identifier, })) } alonzo::Certificate::PoolRegistration { @@ -236,7 +245,7 @@ pub fn map_certificate( pool_metadata, } => Ok(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { + cert: PoolRegistration { operator: operator.to_vec(), vrf_key_hash: vrf_keyhash.to_vec(), pledge: *pledge, @@ -256,17 +265,17 @@ pub fn map_certificate( _ => None, }, }, - tx_hash, + tx_identifier, cert_index: cert_index as u64, }, )), alonzo::Certificate::PoolRetirement(pool_key_hash, epoch) => Ok( TxCertificate::PoolRetirementWithPos(PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: pool_key_hash.to_vec(), epoch: *epoch, }, - tx_hash, + tx_identifier, cert_index: cert_index as u64, }), ), @@ -312,17 +321,26 @@ pub fn map_certificate( conway::Certificate::StakeRegistration(cred) => { Ok(TxCertificate::StakeRegistration(StakeCredentialWithPos { stake_credential: map_stake_credential(cred), + tx_identifier, + tx_index: tx_index.try_into().unwrap(), + cert_index: cert_index.try_into().unwrap(), + })) + } + conway::Certificate::StakeDeregistration(cred) => { + Ok(TxCertificate::StakeDeregistration(StakeCredentialWithPos { + stake_credential: map_stake_credential(cred), + tx_identifier, 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)), - ), conway::Certificate::StakeDelegation(cred, pool_key_hash) => { - Ok(TxCertificate::StakeDelegation(StakeDelegation { - credential: map_stake_credential(cred), - operator: pool_key_hash.to_vec(), + Ok(TxCertificate::StakeDelegation(StakeDelegationWithPos { + cert: StakeDelegation { + credential: map_stake_credential(cred), + operator: pool_key_hash.to_vec(), + }, + tx_identifier, })) } conway::Certificate::PoolRegistration { @@ -338,7 +356,7 @@ pub fn map_certificate( pool_metadata, } => Ok(TxCertificate::PoolRegistrationWithPos( PoolRegistrationWithPos { - reg: PoolRegistration { + cert: PoolRegistration { operator: operator.to_vec(), vrf_key_hash: vrf_keyhash.to_vec(), pledge: *pledge, @@ -358,32 +376,40 @@ pub fn map_certificate( _ => None, }, }, - tx_hash, + tx_identifier, cert_index: cert_index as u64, }, )), conway::Certificate::PoolRetirement(pool_key_hash, epoch) => Ok( TxCertificate::PoolRetirementWithPos(PoolRetirementWithPos { - ret: PoolRetirement { + cert: PoolRetirement { operator: pool_key_hash.to_vec(), epoch: *epoch, }, - tx_hash, + tx_identifier, cert_index: cert_index as u64, }), ), conway::Certificate::Reg(cred, coin) => { - Ok(TxCertificate::Registration(Registration { - credential: map_stake_credential(cred), - deposit: *coin, + Ok(TxCertificate::Registration(RegistrationWithPos { + cert: Registration { + credential: map_stake_credential(cred), + deposit: *coin, + }, + tx_identifier, + cert_index: cert_index as u64, })) } conway::Certificate::UnReg(cred, coin) => { - Ok(TxCertificate::Deregistration(Deregistration { - credential: map_stake_credential(cred), - refund: *coin, + Ok(TxCertificate::Deregistration(DeregistrationWithPos { + cert: Deregistration { + credential: map_stake_credential(cred), + refund: *coin, + }, + tx_identifier, + cert_index: cert_index as u64, })) } @@ -395,38 +421,52 @@ pub fn map_certificate( } conway::Certificate::StakeVoteDeleg(cred, pool_key_hash, drep) => Ok( - TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { - credential: map_stake_credential(cred), - operator: pool_key_hash.to_vec(), - drep: map_drep(drep), + TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegationWithPos { + cert: StakeAndVoteDelegation { + credential: map_stake_credential(cred), + operator: pool_key_hash.to_vec(), + drep: map_drep(drep), + }, + tx_identifier, }), ), - conway::Certificate::StakeRegDeleg(cred, pool_key_hash, coin) => Ok( - TxCertificate::StakeRegistrationAndDelegation(StakeRegistrationAndDelegation { - credential: map_stake_credential(cred), - operator: pool_key_hash.to_vec(), - deposit: *coin, - }), - ), + conway::Certificate::StakeRegDeleg(cred, pool_key_hash, coin) => { + Ok(TxCertificate::StakeRegistrationAndDelegation( + StakeRegistrationAndDelegationWithPos { + cert: StakeRegistrationAndDelegation { + credential: map_stake_credential(cred), + operator: pool_key_hash.to_vec(), + deposit: *coin, + }, + tx_identifier, + }, + )) + } conway::Certificate::VoteRegDeleg(cred, drep, coin) => { Ok(TxCertificate::StakeRegistrationAndVoteDelegation( - StakeRegistrationAndVoteDelegation { - credential: map_stake_credential(cred), - drep: map_drep(drep), - deposit: *coin, + StakeRegistrationAndVoteDelegationWithPos { + cert: StakeRegistrationAndVoteDelegation { + credential: map_stake_credential(cred), + drep: map_drep(drep), + deposit: *coin, + }, + tx_identifier, }, )) } conway::Certificate::StakeVoteRegDeleg(cred, pool_key_hash, drep, coin) => { Ok(TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( - StakeRegistrationAndStakeAndVoteDelegation { - credential: map_stake_credential(cred), - operator: pool_key_hash.to_vec(), - drep: map_drep(drep), - deposit: *coin, + StakeRegistrationAndStakeAndVoteDelegationWithPos { + cert: StakeRegistrationAndStakeAndVoteDelegation { + credential: map_stake_credential(cred), + operator: pool_key_hash.to_vec(), + drep: map_drep(drep), + deposit: *coin, + }, + tx_identifier, }, )) } @@ -447,34 +487,34 @@ pub fn map_certificate( conway::Certificate::RegDRepCert(cred, coin, anchor) => { Ok(TxCertificate::DRepRegistration(DRepRegistrationWithPos { - reg: DRepRegistration { + cert: DRepRegistration { credential: map_stake_credential(cred), deposit: *coin, anchor: map_nullable_anchor(&anchor), }, - tx_hash, + tx_identifier, cert_index: cert_index as u64, })) } conway::Certificate::UnRegDRepCert(cred, coin) => Ok( TxCertificate::DRepDeregistration(DRepDeregistrationWithPos { - reg: DRepDeregistration { + cert: DRepDeregistration { credential: map_stake_credential(cred), refund: *coin, }, - tx_hash, + tx_identifier, cert_index: cert_index as u64, }), ), conway::Certificate::UpdateDRepCert(cred, anchor) => { Ok(TxCertificate::DRepUpdate(DRepUpdateWithPos { - reg: DRepUpdate { + cert: DRepUpdate { credential: map_stake_credential(cred), anchor: map_nullable_anchor(&anchor), }, - tx_hash, + tx_identifier, cert_index: cert_index as u64, })) } diff --git a/modules/tx_unpacker/src/tx_unpacker.rs b/modules/tx_unpacker/src/tx_unpacker.rs index 03f28759..f4d3c340 100644 --- a/modules/tx_unpacker/src/tx_unpacker.rs +++ b/modules/tx_unpacker/src/tx_unpacker.rs @@ -265,9 +265,8 @@ 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_identifier, tx_index, cert_index) { Ok(tx_cert) => { certificates.push(tx_cert); }, From 63c4e2966b8f785d95a75b2580372d81509d1696 Mon Sep 17 00:00:00 2001 From: William Hankins Date: Wed, 22 Oct 2025 22:09:05 +0000 Subject: [PATCH 2/6] prepare registration, delegation, and mir state for query handling Signed-off-by: William Hankins --- codec/src/map_parameters.rs | 45 ++-- common/src/queries/accounts.rs | 53 +++-- common/src/types.rs | 8 +- modules/accounts_state/src/state.rs | 2 +- .../src/historical_accounts_state.rs | 35 ++- .../src/immutable_historical_account_store.rs | 64 ++--- .../historical_accounts_state/src/state.rs | 225 +++++++++++------- .../src/volatile_historical_accounts.rs | 1 - processes/omnibus/omnibus.toml | 7 + 9 files changed, 279 insertions(+), 161 deletions(-) diff --git a/codec/src/map_parameters.rs b/codec/src/map_parameters.rs index 43c970cc..8ec307a7 100644 --- a/codec/src/map_parameters.rs +++ b/codec/src/map_parameters.rs @@ -289,28 +289,31 @@ pub fn map_certificate( vrf_key_hash: vrf_key_hash.to_vec(), })), alonzo::Certificate::MoveInstantaneousRewardsCert(mir) => Ok( - TxCertificate::MoveInstantaneousReward(MoveInstantaneousReward { - source: match mir.source { - alonzo::InstantaneousRewardSource::Reserves => { - InstantaneousRewardSource::Reserves - } - alonzo::InstantaneousRewardSource::Treasury => { - InstantaneousRewardSource::Treasury - } - }, - target: match &mir.target { - alonzo::InstantaneousRewardTarget::StakeCredentials(creds) => { - InstantaneousRewardTarget::StakeCredentials( - creds - .iter() - .map(|(sc, v)| (map_stake_credential(&sc), *v)) - .collect(), - ) - } - alonzo::InstantaneousRewardTarget::OtherAccountingPot(n) => { - InstantaneousRewardTarget::OtherAccountingPot(*n) - } + TxCertificate::MoveInstantaneousReward(MoveInstantaneousRewardWithPos { + cert: MoveInstantaneousReward { + source: match mir.source { + alonzo::InstantaneousRewardSource::Reserves => { + InstantaneousRewardSource::Reserves + } + alonzo::InstantaneousRewardSource::Treasury => { + InstantaneousRewardSource::Treasury + } + }, + target: match &mir.target { + alonzo::InstantaneousRewardTarget::StakeCredentials(creds) => { + InstantaneousRewardTarget::StakeCredentials( + creds + .iter() + .map(|(sc, v)| (map_stake_credential(&sc), *v)) + .collect(), + ) + } + alonzo::InstantaneousRewardTarget::OtherAccountingPot(n) => { + InstantaneousRewardTarget::OtherAccountingPot(*n) + } + }, }, + tx_identifier, }), ), }, diff --git a/common/src/queries/accounts.rs b/common/src/queries/accounts.rs index a6eef24b..47080bc1 100644 --- a/common/src/queries/accounts.rs +++ b/common/src/queries/accounts.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{DRepChoice, KeyHash, PoolLiveStakeInfo}; +use crate::{DRepChoice, KeyHash, PoolId, PoolLiveStakeInfo, StakeCredential, TxIdentifier}; pub const DEFAULT_ACCOUNTS_QUERY_TOPIC: (&str, &str) = ("accounts-state-query-topic", "cardano.query.accounts"); @@ -15,10 +15,10 @@ pub enum AccountsStateQuery { GetAccountInfo { stake_key: Vec }, GetAccountRewardHistory { stake_key: Vec }, GetAccountHistory { stake_key: Vec }, - GetAccountDelegationHistory { stake_key: Vec }, - GetAccountRegistrationHistory { stake_key: Vec }, + GetAccountRegistrationHistory { account: StakeCredential }, + GetAccountDelegationHistory { account: StakeCredential }, + GetAccountMIRHistory { account: StakeCredential }, GetAccountWithdrawalHistory { stake_key: Vec }, - GetAccountMIRHistory { stake_key: Vec }, GetAccountAssociatedAddresses { stake_key: Vec }, GetAccountAssets { stake_key: Vec }, GetAccountAssetsTotals { stake_key: Vec }, @@ -49,10 +49,10 @@ pub enum AccountsStateQueryResponse { AccountInfo(AccountInfo), AccountRewardHistory(AccountRewardHistory), AccountHistory(AccountHistory), - AccountDelegationHistory(AccountDelegationHistory), - AccountRegistrationHistory(AccountRegistrationHistory), + AccountRegistrationHistory(Vec), + AccountDelegationHistory(Vec), + AccountMIRHistory(Vec), AccountWithdrawalHistory(AccountWithdrawalHistory), - AccountMIRHistory(AccountMIRHistory), AccountAssociatedAddresses(AccountAssociatedAddresses), AccountAssets(AccountAssets), AccountAssetsTotals(AccountAssetsTotals), @@ -97,17 +97,42 @@ pub struct AccountRewardHistory {} #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AccountHistory {} -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AccountDelegationHistory {} +#[derive( + Debug, Clone, serde::Serialize, serde::Deserialize, minicbor::Decode, minicbor::Encode, +)] +pub struct DelegationUpdate { + #[n(0)] + pub active_epoch: u32, + #[n(1)] + pub tx_identifier: TxIdentifier, + #[n(2)] + pub amount: u64, + #[n(3)] + pub pool: PoolId, +} -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AccountRegistrationHistory {} +#[derive( + Debug, Clone, serde::Serialize, serde::Deserialize, minicbor::Decode, minicbor::Encode, +)] +pub struct RegistrationUpdate { + #[n(0)] + pub tx_identifier: TxIdentifier, + #[n(1)] + pub deregistered: bool, +} -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AccountWithdrawalHistory {} +#[derive( + Debug, Clone, serde::Serialize, serde::Deserialize, minicbor::Decode, minicbor::Encode, +)] +pub struct AccountWithdrawal { + #[n(0)] + pub tx_identifier: TxIdentifier, + #[n(1)] + pub amount: u64, +} #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AccountMIRHistory {} +pub struct AccountWithdrawalHistory {} #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AccountAssociatedAddresses {} diff --git a/common/src/types.rs b/common/src/types.rs index 863c98a2..a78097fd 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -975,6 +975,12 @@ pub struct MoveInstantaneousReward { pub target: InstantaneousRewardTarget, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct MoveInstantaneousRewardWithPos { + pub cert: MoveInstantaneousReward, + pub tx_identifier: TxIdentifier, +} + /// Register stake (Conway version) = 'reg_cert' #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Registration { @@ -1830,7 +1836,7 @@ pub enum TxCertificate { GenesisKeyDelegation(GenesisKeyDelegation), /// Move instantaneous rewards - MoveInstantaneousReward(MoveInstantaneousReward), + MoveInstantaneousReward(MoveInstantaneousRewardWithPos), /// New stake registration Registration(RegistrationWithPos), diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 84814425..a509c317 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -892,7 +892,7 @@ impl State { } TxCertificate::MoveInstantaneousReward(mir) => { - self.handle_mir(&mir).unwrap_or_else(|e| error!("MIR failed: {e:#}")); + self.handle_mir(&mir.cert).unwrap_or_else(|e| error!("MIR failed: {e:#}")); } TxCertificate::Registration(reg) => { diff --git a/modules/historical_accounts_state/src/historical_accounts_state.rs b/modules/historical_accounts_state/src/historical_accounts_state.rs index 2cbb85ee..0f5e4d90 100644 --- a/modules/historical_accounts_state/src/historical_accounts_state.rs +++ b/modules/historical_accounts_state/src/historical_accounts_state.rs @@ -2,7 +2,7 @@ //! Manages optional state data needed for Blockfrost alignment use acropolis_common::queries::accounts::{ - AccountsStateQueryResponse, DEFAULT_HISTORICAL_ACCOUNTS_QUERY_TOPIC, + AccountsStateQuery, AccountsStateQueryResponse, DEFAULT_HISTORICAL_ACCOUNTS_QUERY_TOPIC, }; use acropolis_common::{ messages::{CardanoMessage, Message, StateQuery, StateQueryResponse}, @@ -35,10 +35,10 @@ const DEFAULT_PARAMETERS_SUBSCRIBE_TOPIC: (&str, &str) = const DEFAULT_HISTORICAL_ACCOUNTS_DB_PATH: (&str, &str) = ("db-path", "./db"); const DEFAULT_STORE_REWARDS_HISTORY: (&str, bool) = ("store-rewards-history", false); const DEFAULT_STORE_ACTIVE_STAKE_HISTORY: (&str, bool) = ("store-active-stake-history", false); -const DEFAULT_STORE_DELEGATION_HISTORY: (&str, bool) = ("store-delegation-history", false); const DEFAULT_STORE_REGISTRATION_HISTORY: (&str, bool) = ("store-registration-history", false); -const DEFAULT_STORE_WITHDRAWAL_HISTORY: (&str, bool) = ("store-withdrawal-history", false); +const DEFAULT_STORE_DELEGATION_HISTORY: (&str, bool) = ("store-delegation-history", false); const DEFAULT_STORE_MIR_HISTORY: (&str, bool) = ("store-mir-history", false); +const DEFAULT_STORE_WITHDRAWAL_HISTORY: (&str, bool) = ("store-withdrawal-history", false); const DEFAULT_STORE_ADDRESSES: (&str, bool) = ("store-addresses", false); /// Historical Accounts State module @@ -154,7 +154,7 @@ impl HistoricalAccountsState { async { Self::check_sync(¤t_block, &block_info); state - .handle_tx_certificates(tx_certs_msg) + .handle_tx_certificates(tx_certs_msg, block_info.epoch as u32) .inspect_err(|e| error!("TxCertificates handling error: {e:#}")) .ok(); } @@ -313,7 +313,7 @@ impl HistoricalAccountsState { let state_query = state_mutex.clone(); context.handle(&historical_accounts_query_topic, move |message| { - let _state = state_query.clone(); + let state = state_query.clone(); async move { let Message::StateQuery(StateQuery::Accounts(query)) = message.as_ref() else { return Arc::new(Message::StateQueryResponse(StateQueryResponse::Accounts( @@ -324,6 +324,31 @@ impl HistoricalAccountsState { }; let response = match query { + AccountsStateQuery::GetAccountRegistrationHistory { account } => { + match state.lock().await.get_registration_history(&account).await { + Ok(registrations) => { + AccountsStateQueryResponse::AccountRegistrationHistory( + registrations, + ) + } + Err(e) => AccountsStateQueryResponse::Error(e.to_string()), + } + } + AccountsStateQuery::GetAccountDelegationHistory { account } => { + match state.lock().await.get_delegation_history(&account).await { + Ok(delegations) => { + AccountsStateQueryResponse::AccountDelegationHistory(delegations) + } + Err(e) => AccountsStateQueryResponse::Error(e.to_string()), + } + } + AccountsStateQuery::GetAccountMIRHistory { account } => { + match state.lock().await.get_mir_history(&account).await { + Ok(mirs) => AccountsStateQueryResponse::AccountMIRHistory(mirs), + Err(e) => AccountsStateQueryResponse::Error(e.to_string()), + } + } + _ => AccountsStateQueryResponse::Error(format!( "Unimplemented query variant: {:?}", query diff --git a/modules/historical_accounts_state/src/immutable_historical_account_store.rs b/modules/historical_accounts_state/src/immutable_historical_account_store.rs index bae28a5c..7212c53e 100644 --- a/modules/historical_accounts_state/src/immutable_historical_account_store.rs +++ b/modules/historical_accounts_state/src/immutable_historical_account_store.rs @@ -1,6 +1,9 @@ use std::{collections::HashMap, path::Path}; -use acropolis_common::{ShelleyAddress, StakeCredential}; +use acropolis_common::{ + queries::accounts::{AccountWithdrawal, DelegationUpdate, RegistrationUpdate}, + ShelleyAddress, StakeCredential, +}; use anyhow::Result; use fjall::{Keyspace, Partition, PartitionCreateOptions}; use minicbor::{decode, to_vec}; @@ -8,18 +11,15 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use tokio::sync::Mutex; use tracing::{debug, error, info}; -use crate::state::{ - AccountEntry, AccountWithdrawal, ActiveStakeHistory, DelegationUpdate, - HistoricalAccountsConfig, RegistrationUpdate, RewardHistory, -}; +use crate::state::{AccountEntry, ActiveStakeHistory, HistoricalAccountsConfig, RewardHistory}; pub struct ImmutableHistoricalAccountStore { rewards_history: Partition, active_stake_history: Partition, delegation_history: Partition, registration_history: Partition, - withdrawal_history: Partition, mir_history: Partition, + withdrawal_history: Partition, addresses: Partition, keyspace: Keyspace, pub pending: Mutex>>, @@ -187,7 +187,26 @@ impl ImmutableHistoricalAccountStore { Ok((!immutable_active_stake.is_empty()).then_some(immutable_active_stake)) } - pub async fn _get_delegation_history( + pub async fn get_registration_history( + &self, + account: &StakeCredential, + ) -> Result>> { + let mut immutable_registrations = self.collect_partition::( + &self.registration_history, + &account.get_hash(), + )?; + + self.merge_pending( + account, + |e| e.registration_history.as_ref(), + &mut immutable_registrations, + ) + .await; + + Ok((!immutable_registrations.is_empty()).then_some(immutable_registrations)) + } + + pub async fn get_delegation_history( &self, account: &StakeCredential, ) -> Result>> { @@ -204,23 +223,16 @@ impl ImmutableHistoricalAccountStore { Ok((!immutable_delegations.is_empty()).then_some(immutable_delegations)) } - pub async fn _get_registration_history( + pub async fn get_mir_history( &self, account: &StakeCredential, - ) -> Result>> { - let mut immutable_registrations = self.collect_partition::( - &self.registration_history, - &account.get_hash(), - )?; + ) -> Result>> { + let mut immutable_mirs = + self.collect_partition::(&self.mir_history, &account.get_hash())?; - self.merge_pending( - account, - |e| e.registration_history.as_ref(), - &mut immutable_registrations, - ) - .await; + self.merge_pending(account, |e| e.mir_history.as_ref(), &mut immutable_mirs).await; - Ok((!immutable_registrations.is_empty()).then_some(immutable_registrations)) + Ok((!immutable_mirs.is_empty()).then_some(immutable_mirs)) } pub async fn _get_withdrawal_history( @@ -242,18 +254,6 @@ impl ImmutableHistoricalAccountStore { Ok((!immutable_withdrawals.is_empty()).then_some(immutable_withdrawals)) } - pub async fn _get_mir_history( - &self, - account: &StakeCredential, - ) -> Result>> { - let mut immutable_mirs = - self.collect_partition::(&self.mir_history, &account.get_hash())?; - - self.merge_pending(account, |e| e.mir_history.as_ref(), &mut immutable_mirs).await; - - Ok((!immutable_mirs.is_empty()).then_some(immutable_mirs)) - } - pub async fn _get_addresses( &self, account: &StakeCredential, diff --git a/modules/historical_accounts_state/src/state.rs b/modules/historical_accounts_state/src/state.rs index 14b1d487..75cce7dd 100644 --- a/modules/historical_accounts_state/src/state.rs +++ b/modules/historical_accounts_state/src/state.rs @@ -7,9 +7,11 @@ use acropolis_common::{ messages::{ AddressDeltasMessage, StakeRewardDeltasMessage, TxCertificatesMessage, WithdrawalsMessage, }, - BlockInfo, MoveInstantaneousReward, PoolId, ShelleyAddress, StakeAddress, StakeCredential, - TxCertificate, TxIdentifier, + queries::accounts::{AccountWithdrawal, DelegationUpdate, RegistrationUpdate}, + BlockInfo, InstantaneousRewardTarget, PoolId, ShelleyAddress, StakeCredential, TxCertificate, + TxIdentifier, }; +use tracing::warn; use crate::{ immutable_historical_account_store::ImmutableHistoricalAccountStore, @@ -51,34 +53,6 @@ pub struct ActiveStakeHistory { pub pool: PoolId, } -#[derive(Debug, Clone, minicbor::Decode, minicbor::Encode)] -pub struct DelegationUpdate { - #[n(0)] - pub active_epoch: u32, - #[n(1)] - pub tx_identifier: TxIdentifier, - #[n(2)] - pub amount: u64, - #[n(3)] - pub pool: PoolId, -} - -#[derive(Debug, Clone, minicbor::Decode, minicbor::Encode)] -pub struct RegistrationUpdate { - #[n(0)] - pub tx_identifier: TxIdentifier, - #[n(1)] - pub deregistered: bool, -} - -#[derive(Debug, Clone, minicbor::Decode, minicbor::Encode)] -pub struct AccountWithdrawal { - #[n(0)] - pub tx_identifier: TxIdentifier, - #[n(1)] - pub amount: u64, -} - #[derive(Debug, Clone)] pub struct HistoricalAccountsConfig { pub db_path: String, @@ -87,8 +61,8 @@ pub struct HistoricalAccountsConfig { pub store_active_stake_history: bool, pub store_delegation_history: bool, pub store_registration_history: bool, - pub store_withdrawal_history: bool, pub store_mir_history: bool, + pub store_withdrawal_history: bool, pub store_addresses: bool, } @@ -98,8 +72,8 @@ impl HistoricalAccountsConfig { || self.store_active_stake_history || self.store_delegation_history || self.store_registration_history - || self.store_withdrawal_history || self.store_mir_history + || self.store_withdrawal_history || self.store_addresses } } @@ -144,47 +118,71 @@ impl State { Ok(()) } - pub fn handle_tx_certificates(&mut self, tx_certs: &TxCertificatesMessage) -> Result<()> { + pub fn handle_tx_certificates( + &mut self, + tx_certs: &TxCertificatesMessage, + epoch: u32, + ) -> Result<()> { // Handle certificates for tx_cert in tx_certs.certificates.iter() { match tx_cert { // Pre-Conway stake registration/deregistration certs TxCertificate::StakeRegistration(sc) => { - self.handle_stake_registration(&sc.stake_credential, &sc.tx_identifier); + self.handle_stake_registration_change( + &sc.stake_credential, + &sc.tx_identifier, + false, + ); } TxCertificate::StakeDeregistration(sc) => { - self.handle_stake_deregistration(&sc.stake_credential, &sc.tx_identifier); + self.handle_stake_registration_change( + &sc.stake_credential, + &sc.tx_identifier, + true, + ); } // Post-Conway stake registration/deregistration certs TxCertificate::Registration(reg) => { - self.handle_stake_registration(®.cert.credential, ®.tx_identifier); + self.handle_stake_registration_change( + ®.cert.credential, + ®.tx_identifier, + false, + ); } TxCertificate::Deregistration(dreg) => { - self.handle_stake_deregistration(&dreg.cert.credential, &dreg.tx_identifier); + self.handle_stake_registration_change( + &dreg.cert.credential, + &dreg.tx_identifier, + true, + ); } // Registration and delegation certs TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(delegation) => { - self.handle_stake_registration( + self.handle_stake_registration_change( &delegation.cert.credential, &delegation.tx_identifier, + false, ); self.handle_stake_delegation( &delegation.cert.credential, &delegation.cert.operator, &delegation.tx_identifier, + epoch, ); } TxCertificate::StakeRegistrationAndDelegation(delegation) => { - self.handle_stake_registration( + self.handle_stake_registration_change( &delegation.cert.credential, &delegation.tx_identifier, + false, ); self.handle_stake_delegation( &delegation.cert.credential, &delegation.cert.operator, &delegation.tx_identifier, + epoch, ); } @@ -194,6 +192,7 @@ impl State { &delegation.cert.credential, &delegation.cert.operator, &delegation.tx_identifier, + epoch, ); } TxCertificate::StakeAndVoteDelegation(delegation) => { @@ -201,17 +200,21 @@ impl State { &delegation.cert.credential, &delegation.cert.operator, &delegation.tx_identifier, + epoch, ); } TxCertificate::StakeRegistrationAndVoteDelegation(delegation) => { - self.handle_stake_registration( + self.handle_stake_registration_change( &delegation.cert.credential, &delegation.tx_identifier, + false, ); } // MIR certs - TxCertificate::MoveInstantaneousReward(mir) => self.handle_mir(mir), + TxCertificate::MoveInstantaneousReward(mir) => { + self.handle_mir(&mir.cert.target, &mir.tx_identifier); + } _ => (), }; @@ -227,92 +230,142 @@ impl State { Ok(()) } - pub fn _get_reward_history(&self, _account: StakeAddress) -> Result> { + pub async fn _get_reward_history( + &self, + _account: &StakeCredential, + ) -> Result> { Ok(Vec::new()) } - pub fn _get_active_stake_history( + pub async fn _get_active_stake_history( &self, - _account: StakeCredential, + _account: &StakeCredential, ) -> Result> { Ok(Vec::new()) } - pub fn _get_delegation_history( + pub async fn get_registration_history( &self, - _account: StakeCredential, - ) -> Result> { - Ok(Vec::new()) + account: &StakeCredential, + ) -> Result> { + let mut registrations = + self.immutable.get_registration_history(&account).await?.unwrap_or_default(); + + self.merge_volatile_history( + &account, + |e| e.registration_history.as_ref(), + &mut registrations, + ); + + Ok(registrations) } - pub fn _get_registration_history( + pub async fn get_delegation_history( &self, - _account: StakeCredential, - ) -> Result> { - Ok(Vec::new()) + account: &StakeCredential, + ) -> Result> { + let mut delegations = + self.immutable.get_delegation_history(&account).await?.unwrap_or_default(); + + self.merge_volatile_history( + &account, + |e| e.delegation_history.as_ref(), + &mut delegations, + ); + + Ok(delegations) } - pub fn _get_withdrawal_history( + pub async fn get_mir_history( &self, - _account: StakeCredential, + account: &StakeCredential, ) -> Result> { - Ok(Vec::new()) + let mut mirs = self.immutable.get_mir_history(&account).await?.unwrap_or_default(); + + self.merge_volatile_history(&account, |e| e.mir_history.as_ref(), &mut mirs); + + Ok(mirs) } - pub fn _get_mir_history(&self, _account: StakeCredential) -> Result> { + pub async fn _get_withdrawal_history( + &self, + _account: &StakeCredential, + ) -> Result> { Ok(Vec::new()) } - pub fn _get_addresses(&self, _account: StakeCredential) -> Result> { + pub async fn _get_addresses(&self, _account: StakeCredential) -> Result> { Ok(Vec::new()) } - fn handle_stake_registration( + fn handle_stake_registration_change( &mut self, account: &StakeCredential, tx_identifier: &TxIdentifier, + deregistered: bool, ) { let volatile = self.volatile.window.back_mut().expect("window should never be empty"); - let entry = volatile.entry(account.clone()).or_default(); - - if let Some(registration_history) = &mut entry.registration_history { - registration_history.push(RegistrationUpdate { - tx_identifier: *tx_identifier, - deregistered: false, - }); - } else { - entry.registration_history = Some(vec![RegistrationUpdate { - tx_identifier: *tx_identifier, - deregistered: false, - }]); - } + let update = RegistrationUpdate { + tx_identifier: *tx_identifier, + deregistered, + }; + entry.registration_history.get_or_insert_with(Vec::new).push(update); } - fn handle_stake_deregistration( + fn handle_stake_delegation( &mut self, account: &StakeCredential, + pool: &PoolId, tx_identifier: &TxIdentifier, + epoch: u32, ) { let volatile = self.volatile.window.back_mut().expect("window should never be empty"); - let entry = volatile.entry(account.clone()).or_default(); + let update = DelegationUpdate { + active_epoch: epoch.saturating_add(2), + tx_identifier: *tx_identifier, + amount: 0, // Amount is set during persistence when active stake is known + pool: pool.clone(), + }; + entry.delegation_history.get_or_insert_with(Vec::new).push(update); + } + + fn handle_mir(&mut self, mir: &InstantaneousRewardTarget, tx_identifier: &TxIdentifier) { + let volatile = self.volatile.window.back_mut().expect("window should never be empty"); + + if let InstantaneousRewardTarget::StakeCredentials(payments) = mir { + for (account, amount) in payments { + if *amount <= 0 { + warn!( + "Ignoring invalid MIR (negative or zero) for stake credential {}", + hex::encode(account.get_hash()) + ); + continue; + } + + let entry = volatile.entry(account.clone()).or_default(); + let update = AccountWithdrawal { + tx_identifier: *tx_identifier, + amount: *amount as u64, + }; - if let Some(mut registration_history) = entry.registration_history.clone() { - registration_history.push(RegistrationUpdate { - tx_identifier: *tx_identifier, - deregistered: true, - }) + entry.mir_history.get_or_insert_with(Vec::new).push(update); + } } } - fn handle_stake_delegation( - &mut self, - _account: &StakeCredential, - _pool: &PoolId, - _tx_identifier: &TxIdentifier, - ) { + fn merge_volatile_history(&self, account: &StakeCredential, f: F, out: &mut Vec) + where + F: Fn(&AccountEntry) -> Option<&Vec>, + T: Clone, + { + for block_map in self.volatile.window.iter() { + if let Some(entry) = block_map.get(account) { + if let Some(pending) = f(entry) { + out.extend(pending.iter().cloned()); + } + } + } } - - fn handle_mir(&mut self, _mir: &MoveInstantaneousReward) {} } diff --git a/modules/historical_accounts_state/src/volatile_historical_accounts.rs b/modules/historical_accounts_state/src/volatile_historical_accounts.rs index 6e407352..41bf846f 100644 --- a/modules/historical_accounts_state/src/volatile_historical_accounts.rs +++ b/modules/historical_accounts_state/src/volatile_historical_accounts.rs @@ -3,7 +3,6 @@ use std::collections::{HashMap, VecDeque}; use acropolis_common::StakeCredential; use crate::state::AccountEntry; -use acropolis_common::StakeCredential; #[derive(Debug, Clone)] pub struct VolatileHistoricalAccounts { diff --git a/processes/omnibus/omnibus.toml b/processes/omnibus/omnibus.toml index 0d2ff6d1..c4015fca 100644 --- a/processes/omnibus/omnibus.toml +++ b/processes/omnibus/omnibus.toml @@ -55,6 +55,13 @@ store-stake-addresses = false store-spdd = false [module.historical-accounts-state] +store-rewards-history = false +store-active-stake-history = false +store-registration-history = true +store-delegation-history = true +store-mir-history = true +store-withdrawal-history = false +store-addresses = false [module.drep-state] # Enables /governance/dreps/{drep_id} endpoint (Requires store-delegators to be enabled) From c09a81df41d8e2c57037f972897533dfae87a268 Mon Sep 17 00:00:00 2001 From: William Hankins Date: Fri, 24 Oct 2025 20:55:15 +0000 Subject: [PATCH 3/6] fix typo and add RegistrationStatus enum Signed-off-by: William Hankins --- common/src/queries/accounts.rs | 12 +++++++++- .../src/immutable_historical_account_store.rs | 4 ++-- .../historical_accounts_state/src/state.rs | 22 ++++++++++--------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/common/src/queries/accounts.rs b/common/src/queries/accounts.rs index f60e7239..979425ce 100644 --- a/common/src/queries/accounts.rs +++ b/common/src/queries/accounts.rs @@ -118,7 +118,17 @@ pub struct RegistrationUpdate { #[n(0)] pub tx_identifier: TxIdentifier, #[n(1)] - pub deregistered: bool, + pub status: RegistrationStatus, +} + +#[derive( + Debug, Clone, serde::Serialize, serde::Deserialize, minicbor::Decode, minicbor::Encode, +)] +pub enum RegistrationStatus { + #[n(0)] + Registered, + #[n(1)] + Deregistered, } #[derive( diff --git a/modules/historical_accounts_state/src/immutable_historical_account_store.rs b/modules/historical_accounts_state/src/immutable_historical_account_store.rs index a2e4b2af..bea59143 100644 --- a/modules/historical_accounts_state/src/immutable_historical_account_store.rs +++ b/modules/historical_accounts_state/src/immutable_historical_account_store.rs @@ -270,8 +270,8 @@ impl ImmutableHistoricalAccountStore { block_deltas: Vec>, ) -> HashMap { block_deltas.into_par_iter().reduce(HashMap::new, |mut acc, block_map| { - for (accont, entry) in block_map { - let agg_entry = acc.entry(accont).or_default(); + for (account, entry) in block_map { + let agg_entry = acc.entry(account).or_default(); Self::extend_opt_vec(&mut agg_entry.reward_history, entry.reward_history); Self::extend_opt_vec( diff --git a/modules/historical_accounts_state/src/state.rs b/modules/historical_accounts_state/src/state.rs index 576b13c9..f26583b4 100644 --- a/modules/historical_accounts_state/src/state.rs +++ b/modules/historical_accounts_state/src/state.rs @@ -7,7 +7,9 @@ use acropolis_common::{ messages::{ AddressDeltasMessage, StakeRewardDeltasMessage, TxCertificatesMessage, WithdrawalsMessage, }, - queries::accounts::{AccountWithdrawal, DelegationUpdate, RegistrationUpdate}, + queries::accounts::{ + AccountWithdrawal, DelegationUpdate, RegistrationStatus, RegistrationUpdate, + }, BlockInfo, InstantaneousRewardTarget, PoolId, ShelleyAddress, StakeAddress, StakeCredential, TxCertificate, TxIdentifier, }; @@ -131,14 +133,14 @@ impl State { self.handle_stake_registration_change( &sc.stake_address, &sc.tx_identifier, - false, + RegistrationStatus::Registered, ); } TxCertificate::StakeDeregistration(sc) => { self.handle_stake_registration_change( &sc.stake_address, &sc.tx_identifier, - true, + RegistrationStatus::Deregistered, ); } @@ -147,14 +149,14 @@ impl State { self.handle_stake_registration_change( ®.cert.stake_address, ®.tx_identifier, - false, + RegistrationStatus::Registered, ); } TxCertificate::Deregistration(dreg) => { self.handle_stake_registration_change( &dreg.cert.stake_address, &dreg.tx_identifier, - true, + RegistrationStatus::Deregistered, ); } @@ -163,7 +165,7 @@ impl State { self.handle_stake_registration_change( &delegation.cert.stake_address, &delegation.tx_identifier, - false, + RegistrationStatus::Registered, ); self.handle_stake_delegation( &delegation.cert.stake_address, @@ -176,7 +178,7 @@ impl State { self.handle_stake_registration_change( &delegation.cert.stake_address, &delegation.tx_identifier, - false, + RegistrationStatus::Registered, ); self.handle_stake_delegation( &delegation.cert.stake_address, @@ -207,7 +209,7 @@ impl State { self.handle_stake_registration_change( &delegation.cert.stake_address, &delegation.tx_identifier, - false, + RegistrationStatus::Registered, ); } @@ -299,13 +301,13 @@ impl State { &mut self, account: &StakeAddress, tx_identifier: &TxIdentifier, - deregistered: bool, + status: RegistrationStatus, ) { let volatile = self.volatile.window.back_mut().expect("window should never be empty"); let entry = volatile.entry(account.clone()).or_default(); let update = RegistrationUpdate { tx_identifier: *tx_identifier, - deregistered, + status, }; entry.registration_history.get_or_insert_with(Vec::new).push(update); } From 54cd9aa50047c5fd104053cde6360412eaa6952c Mon Sep 17 00:00:00 2001 From: William Hankins Date: Mon, 27 Oct 2025 18:42:22 +0000 Subject: [PATCH 4/6] merge upstream/main Signed-off-by: William Hankins --- .../workflows/run-tests-on-push-to-main.yml | 4 +- .github/workflows/weekly-update.yml | 88 +++ Cargo.lock | 33 ++ Cargo.toml | 10 +- codec/src/map_parameters.rs | 18 +- common/src/address.rs | 121 ++-- common/src/commands/mod.rs | 1 + common/src/commands/transactions.rs | 19 + common/src/lib.rs | 1 + common/src/messages.rs | 15 + common/src/stake_addresses.rs | 6 +- common/src/types.rs | 16 +- modules/accounts_state/src/snapshot.rs | 6 +- modules/accounts_state/src/state.rs | 5 +- .../rest_blockfrost/src/handlers/epochs.rs | 8 +- modules/stake_delta_filter/src/predefined.rs | 12 +- modules/stake_delta_filter/src/utils.rs | 21 +- modules/tx_submitter/Cargo.toml | 23 + modules/tx_submitter/README.md | 21 + modules/tx_submitter/src/peer.rs | 333 +++++++++++ modules/tx_submitter/src/tx.rs | 25 + modules/tx_submitter/src/tx_submitter.rs | 98 ++++ processes/README.md | 1 + processes/tx_submitter_cli/Cargo.toml | 27 + processes/tx_submitter_cli/README.md | 11 + processes/tx_submitter_cli/src/main.rs | 125 ++++ processes/tx_submitter_cli/tx-submitter.toml | 18 + scripts/weekly_status_markdown.py | 536 ++++++++++++++++++ 28 files changed, 1484 insertions(+), 118 deletions(-) create mode 100644 .github/workflows/weekly-update.yml create mode 100644 common/src/commands/mod.rs create mode 100644 common/src/commands/transactions.rs create mode 100644 modules/tx_submitter/Cargo.toml create mode 100644 modules/tx_submitter/README.md create mode 100644 modules/tx_submitter/src/peer.rs create mode 100644 modules/tx_submitter/src/tx.rs create mode 100644 modules/tx_submitter/src/tx_submitter.rs create mode 100644 processes/tx_submitter_cli/Cargo.toml create mode 100644 processes/tx_submitter_cli/README.md create mode 100644 processes/tx_submitter_cli/src/main.rs create mode 100644 processes/tx_submitter_cli/tx-submitter.toml create mode 100644 scripts/weekly_status_markdown.py diff --git a/.github/workflows/run-tests-on-push-to-main.yml b/.github/workflows/run-tests-on-push-to-main.yml index 742b5784..89066338 100644 --- a/.github/workflows/run-tests-on-push-to-main.yml +++ b/.github/workflows/run-tests-on-push-to-main.yml @@ -40,8 +40,10 @@ jobs: --package acropolis_module_snapshot_bootstrapper \ --package acropolis_module_spdd_state \ --package acropolis_module_stake_delta_filter \ + --package acropolis_module_tx_submitter \ --package acropolis_module_upstream_chain_fetcher \ - --package acropolis_module_utxo_state + --package acropolis_module_utxo_state \ + --package acropolis_process_tx_submitter_cli - name: Run Build run: cargo build --verbose diff --git a/.github/workflows/weekly-update.yml b/.github/workflows/weekly-update.yml new file mode 100644 index 00000000..e4471ef6 --- /dev/null +++ b/.github/workflows/weekly-update.yml @@ -0,0 +1,88 @@ +name: Weekly Status Markdown + +on: + schedule: + - cron: "0 14 * * MON" # Mondays 07:00 PT (14:00 UTC) + workflow_dispatch: + +jobs: + build-status: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write # needed to create/update issues + pull-requests: write # needed to post/update a PR comment + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install deps + run: pip install requests + + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + - name: Generate weekly status markdown + id: gen + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: input-output-hk/acropolis + # --- Use ONE of the two approaches below --- + + # A) Projects v2 (recommended): + PROJECT_OWNER: input-output-hk + PROJECT_NUMBER: 7 + STATUS_DONE_VALUE: Done + STATUS_INPROGRESS_VALUE: In Progress + + # B) Fallback via labels: + # DONE_LABELS: "status: done,done" + # INPROGRESS_LABELS: "status: in progress,in progress" + + OUTPUT_PATH: artifacts/weekly_status.md + run: | + mkdir -p artifacts + python scripts/weekly_status_markdown.py | tee /tmp/out.md + echo "md<> $GITHUB_OUTPUT + cat /tmp/out.md >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Upload markdown artifact + uses: actions/upload-artifact@v4 + with: + name: weekly-status + path: artifacts/weekly_status.md + if-no-files-found: warn + + # Create GitHub Issue with weekly status + - name: Create Weekly Status Issue + if: ${{ github.event_name != 'pull_request' }} + uses: actions/github-script@v7 + with: + script: | + console.log('Event name:', context.eventName); + console.log('Checking if artifacts/weekly_status.md exists...'); + + const fs = require('fs'); + if (!fs.existsSync('artifacts/weekly_status.md')) { + console.log('❌ artifacts/weekly_status.md not found!'); + throw new Error('Weekly status file not found'); + } + + const content = fs.readFileSync('artifacts/weekly_status.md', 'utf8'); + console.log('✅ File read successfully, length:', content.length); + + const issue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `📊 Weekly Status - Week of ${{ steps.date.outputs.date }}`, + body: content, + labels: ['weekly-status', 'automated'] + }); + + console.log('✅ Issue created successfully:', issue.data.html_url); diff --git a/Cargo.lock b/Cargo.lock index bd584c53..04d9375e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,6 +397,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "acropolis_module_tx_submitter" +version = "0.1.0" +dependencies = [ + "acropolis_common", + "anyhow", + "caryatid_sdk", + "config", + "futures", + "hex", + "pallas 0.33.0", + "tokio", + "tracing", +] + [[package]] name = "acropolis_module_tx_unpacker" version = "0.2.1" @@ -569,6 +584,24 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "acropolis_process_tx_submitter_cli" +version = "0.1.0" +dependencies = [ + "acropolis_common", + "acropolis_module_tx_submitter", + "anyhow", + "caryatid_process", + "caryatid_sdk", + "clap", + "config", + "hex", + "pallas 0.33.0", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "adler2" version = "2.0.1" diff --git a/Cargo.toml b/Cargo.toml index 9efddb72..035c1edb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,13 @@ members = [ "modules/historical_accounts_state", # Tracks historical account information "modules/consensus", # Chooses favoured chain across multiple options "modules/chain_store", # Tracks historical information about blocks and TXs + "modules/tx_submitter", # Submits TXs to peers # Process builds - "processes/omnibus", # All-inclusive omnibus process - "processes/replayer", # All-inclusive process to replay messages - "processes/golden_tests", # All-inclusive golden tests process + "processes/omnibus", # All-inclusive omnibus process + "processes/replayer", # All-inclusive process to replay messages + "processes/golden_tests", # All-inclusive golden tests process + "processes/tx_submitter_cli", # CLI wrapper for TX submitter ] resolver = "2" @@ -41,7 +43,7 @@ caryatid_module_clock = "0.12" caryatid_module_spy = "0.12" anyhow = "1.0" chrono = "0.4" -clap = { version = "4.5", features = ["derive"] } +clap = { version = "4.5", features = ["derive", "string"] } config = "0.15.11" dashmap = "6.1.0" hex = "0.4" diff --git a/codec/src/map_parameters.rs b/codec/src/map_parameters.rs index 99e959c4..986846a1 100644 --- a/codec/src/map_parameters.rs +++ b/codec/src/map_parameters.rs @@ -69,13 +69,9 @@ pub fn map_address(address: &addresses::Address) -> Result
{ addresses::Address::Stake(stake_address) => Ok(Address::Stake(StakeAddress { network: map_network(stake_address.network())?, - payload: match stake_address.payload() { - addresses::StakePayload::Stake(hash) => { - StakeAddressPayload::StakeKeyHash(hash.to_vec()) - } - addresses::StakePayload::Script(hash) => { - StakeAddressPayload::ScriptHash(hash.to_vec()) - } + credential: match stake_address.payload() { + addresses::StakePayload::Stake(hash) => StakeCredential::AddrKeyHash(hash.to_vec()), + addresses::StakePayload::Script(hash) => StakeCredential::ScriptHash(hash.to_vec()), }, })), } @@ -97,10 +93,10 @@ pub fn map_stake_credential(cred: &PallasStakeCredential) -> StakeCredential { 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()) + StakeCredential::AddrKeyHash(key_hash.to_vec()) } PallasStakeCredential::ScriptHash(script_hash) => { - StakeAddressPayload::ScriptHash(script_hash.to_vec()) + StakeCredential::ScriptHash(script_hash.to_vec()) } }; @@ -274,7 +270,7 @@ pub fn map_certificate( .iter() .map(|v| { StakeAddress::new( - StakeAddressPayload::StakeKeyHash(v.to_vec()), + StakeCredential::AddrKeyHash(v.to_vec()), network_id.clone().into(), ) }) @@ -398,7 +394,7 @@ pub fn map_certificate( .into_iter() .map(|v| { StakeAddress::new( - StakeAddressPayload::StakeKeyHash(v.to_vec()), + StakeCredential::AddrKeyHash(v.to_vec()), network_id.clone().into(), ) }) diff --git a/common/src/address.rs b/common/src/address.rs index 8992e253..7c5e6b3a 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -4,11 +4,10 @@ use crate::cip19::{VarIntDecoder, VarIntEncoder}; use crate::types::{KeyHash, ScriptHash}; -use crate::{Credential, NetworkId}; +use crate::{Credential, NetworkId, StakeCredential}; use anyhow::{anyhow, bail, Result}; use crc::{Crc, CRC_32_ISO_HDLC}; use minicbor::data::IanaTag; -use serde_with::{hex::Hex, serde_as}; use std::cmp::Ordering; use std::fmt::{Display, Formatter}; @@ -159,7 +158,7 @@ pub enum ShelleyAddressDelegationPart { #[n(1)] StakeKeyHash(#[n(0)] Vec), - /// Delegation to script key + /// Delegation to script key hash #[n(2)] ScriptHash(#[n(0)] ScriptHash), @@ -357,55 +356,35 @@ impl ShelleyAddress { } } -/// Payload of a stake address -#[serde_as] -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)] -pub enum StakeAddressPayload { - /// Stake key - StakeKeyHash(#[serde_as(as = "Hex")] KeyHash), - - /// Script hash - ScriptHash(#[serde_as(as = "Hex")] ScriptHash), -} - -impl StakeAddressPayload { - // Convert to string - note different encoding from when used as part of a StakeAddress - pub fn to_string(&self) -> Result { - let (hrp, data) = match &self { - Self::StakeKeyHash(data) => (bech32::Hrp::parse("stake_vkh")?, data), - Self::ScriptHash(data) => (bech32::Hrp::parse("script")?, data), - }; - - Ok(bech32::encode::(hrp, data)?) - } -} - /// A stake address #[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] pub struct StakeAddress { /// Network id pub network: NetworkId, - /// Payload - pub payload: StakeAddressPayload, + /// Credential + pub credential: StakeCredential, // payload: StakePayload? } impl StakeAddress { - pub fn new(payload: StakeAddressPayload, network: NetworkId) -> Self { - StakeAddress { network, payload } + pub fn new(credential: StakeCredential, network: NetworkId) -> Self { + StakeAddress { + network, + credential, + } } pub fn get_hash(&self) -> &[u8] { - match &self.payload { - StakeAddressPayload::StakeKeyHash(hash) => hash, - StakeAddressPayload::ScriptHash(hash) => hash, + match &self.credential { + StakeCredential::AddrKeyHash(hash) => hash, + StakeCredential::ScriptHash(hash) => hash, } } pub fn get_credential(&self) -> Credential { - match &self.payload { - StakeAddressPayload::StakeKeyHash(hash) => Credential::AddrKeyHash(hash.clone()), - StakeAddressPayload::ScriptHash(hash) => Credential::ScriptHash(hash.clone()), + match &self.credential { + StakeCredential::AddrKeyHash(hash) => Credential::AddrKeyHash(hash.clone()), + StakeCredential::ScriptHash(hash) => Credential::ScriptHash(hash.clone()), } } @@ -429,13 +408,16 @@ impl StakeAddress { false => NetworkId::Mainnet, }; - let payload = match (header >> 4) & 0x0Fu8 { - 0b1110 => StakeAddressPayload::StakeKeyHash(data[1..].to_vec()), - 0b1111 => StakeAddressPayload::ScriptHash(data[1..].to_vec()), + let credential = match (header >> 4) & 0x0Fu8 { + 0b1110 => StakeCredential::AddrKeyHash(data[1..].to_vec()), + 0b1111 => StakeCredential::ScriptHash(data[1..].to_vec()), _ => return Err(anyhow!("Unknown header {header} in stake address")), }; - return Ok(StakeAddress { network, payload }); + return Ok(StakeAddress { + network, + credential, + }); } Err(anyhow!("Empty stake address data")) @@ -448,9 +430,9 @@ impl StakeAddress { NetworkId::Testnet => 0b0u8, }; - let (stake_bits, stake_hash): (u8, &Vec) = match &self.payload { - StakeAddressPayload::StakeKeyHash(data) => (0b1110, data), - StakeAddressPayload::ScriptHash(data) => (0b1111, data), + let (stake_bits, stake_hash): (u8, &Vec) = match &self.credential { + StakeCredential::AddrKeyHash(data) => (0b1110, data), + StakeCredential::ScriptHash(data) => (0b1111, data), }; let mut data = vec![network_bits | (stake_bits << 4)]; @@ -469,20 +451,23 @@ impl StakeAddress { _ => NetworkId::Testnet, }; - let payload = match (data[0] >> 4) & 0x0F { - 0b1110 => StakeAddressPayload::StakeKeyHash(data[1..].to_vec()), - 0b1111 => StakeAddressPayload::ScriptHash(data[1..].to_vec()), + let credential = match (data[0] >> 4) & 0x0F { + 0b1110 => StakeCredential::AddrKeyHash(data[1..].to_vec()), + 0b1111 => StakeCredential::ScriptHash(data[1..].to_vec()), _ => bail!("Unknown header byte {:x} in stake address", data[0]), }; - Ok(StakeAddress { network, payload }) + Ok(StakeAddress { + network, + credential, + }) } pub fn to_bytes_key(&self) -> Result> { let mut out = Vec::new(); - let (bits, hash): (u8, &[u8]) = match &self.payload { - StakeAddressPayload::StakeKeyHash(h) => (0b1110, h), - StakeAddressPayload::ScriptHash(h) => (0b1111, h), + let (bits, hash): (u8, &[u8]) = match &self.credential { + StakeCredential::AddrKeyHash(h) => (0b1110, h), + StakeCredential::ScriptHash(h) => (0b1111, h), }; let net_bit = match self.network { @@ -529,7 +514,7 @@ impl Default for StakeAddress { fn default() -> Self { StakeAddress { network: NetworkId::Mainnet, - payload: StakeAddressPayload::StakeKeyHash(vec![0u8; 28]), + credential: StakeCredential::AddrKeyHash(vec![0u8; 28]), } } } @@ -611,9 +596,9 @@ impl Address { ShelleyAddressPaymentPart::PaymentKeyHash(_) => false, ShelleyAddressPaymentPart::ScriptHash(_) => true, }, - Address::Stake(stake) => match stake.payload { - StakeAddressPayload::StakeKeyHash(_) => false, - StakeAddressPayload::ScriptHash(_) => true, + Address::Stake(stake) => match stake.credential { + StakeCredential::AddrKeyHash(_) => false, + StakeCredential::ScriptHash(_) => true, }, Address::Byron(_) | Address::None => false, } @@ -828,7 +813,7 @@ mod tests { fn shelley_type_14() { let address = Address::Stake(StakeAddress { network: NetworkId::Mainnet, - payload: StakeAddressPayload::StakeKeyHash(test_stake_key_hash()), + credential: StakeCredential::AddrKeyHash(test_stake_key_hash()), }); let text = address.to_string().unwrap(); @@ -845,7 +830,7 @@ mod tests { fn shelley_type_15() { let address = Address::Stake(StakeAddress { network: NetworkId::Mainnet, - payload: StakeAddressPayload::ScriptHash(test_script_hash()), + credential: StakeCredential::ScriptHash(test_script_hash()), }); let text = address.to_string().unwrap(); @@ -890,8 +875,8 @@ mod tests { let sa = StakeAddress::from_binary(&binary).unwrap(); assert_eq!(sa.network, NetworkId::Mainnet); assert_eq!( - match sa.payload { - StakeAddressPayload::StakeKeyHash(key) => hex::encode(&key), + match sa.credential { + StakeCredential::AddrKeyHash(key) => hex::encode(&key), _ => "SCRIPT".to_string(), }, "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" @@ -906,8 +891,8 @@ mod tests { let sa = StakeAddress::from_binary(&binary).unwrap(); assert_eq!(sa.network, NetworkId::Mainnet); assert_eq!( - match sa.payload { - StakeAddressPayload::ScriptHash(key) => hex::encode(&key), + match sa.credential { + StakeCredential::ScriptHash(key) => hex::encode(&key), _ => "STAKE".to_string(), }, "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" @@ -922,8 +907,8 @@ mod tests { let sa = StakeAddress::from_binary(&binary).unwrap(); assert_eq!(sa.network, NetworkId::Testnet); assert_eq!( - match sa.payload { - StakeAddressPayload::StakeKeyHash(key) => hex::encode(&key), + match sa.credential { + StakeCredential::AddrKeyHash(key) => hex::encode(&key), _ => "SCRIPT".to_string(), }, "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" @@ -975,8 +960,8 @@ mod tests { assert_eq!(decoded.network, NetworkId::Mainnet); assert_eq!( - match decoded.payload { - StakeAddressPayload::StakeKeyHash(key) => hex::encode(&key), + match decoded.credential { + StakeCredential::AddrKeyHash(key) => hex::encode(&key), _ => "STAKE".to_string(), }, "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" @@ -998,8 +983,8 @@ mod tests { assert_eq!(decoded.network, NetworkId::Mainnet); assert_eq!( - match decoded.payload { - StakeAddressPayload::ScriptHash(key) => hex::encode(&key), + match decoded.credential { + StakeCredential::ScriptHash(key) => hex::encode(&key), _ => "STAKE".to_string(), }, "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" @@ -1019,8 +1004,8 @@ mod tests { assert_eq!(decoded.network, NetworkId::Testnet); assert_eq!( - match decoded.payload { - StakeAddressPayload::ScriptHash(key) => hex::encode(&key), + match decoded.credential { + StakeCredential::ScriptHash(key) => hex::encode(&key), _ => "SCRIPT".to_string(), }, "558f3ee09b26d88fac2eddc772a9eda94cce6dbadbe9fee439bd6001" diff --git a/common/src/commands/mod.rs b/common/src/commands/mod.rs new file mode 100644 index 00000000..0824d7a9 --- /dev/null +++ b/common/src/commands/mod.rs @@ -0,0 +1 @@ +pub mod transactions; diff --git a/common/src/commands/transactions.rs b/common/src/commands/transactions.rs new file mode 100644 index 00000000..92453109 --- /dev/null +++ b/common/src/commands/transactions.rs @@ -0,0 +1,19 @@ +use serde_with::{hex::Hex, serde_as}; + +use crate::TxHash; + +#[serde_as] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum TransactionsCommand { + Submit { + #[serde_as(as = "Hex")] + cbor: Vec, + wait_for_ack: bool, + }, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum TransactionsCommandResponse { + Submitted { id: TxHash }, + Error(String), +} diff --git a/common/src/lib.rs b/common/src/lib.rs index abf55551..a9cf24c3 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -4,6 +4,7 @@ pub mod address; pub mod byte_array; pub mod calculations; pub mod cip19; +pub mod commands; pub mod crypto; pub mod genesis_values; pub mod hash; diff --git a/common/src/messages.rs b/common/src/messages.rs index 2cd325a6..8ed9856e 100644 --- a/common/src/messages.rs +++ b/common/src/messages.rs @@ -3,6 +3,7 @@ // We don't use these messages in the acropolis_common crate itself #![allow(dead_code)] +use crate::commands::transactions::{TransactionsCommand, TransactionsCommandResponse}; use crate::genesis_values::GenesisValues; use crate::ledger_state::SPOState; use crate::protocol_params::{NonceHash, ProtocolParams}; @@ -350,6 +351,10 @@ pub enum Message { // State query messages StateQuery(StateQuery), StateQueryResponse(StateQueryResponse), + + // Commands + Command(Command), + CommandResponse(CommandResponse), } // Casts from specific Caryatid messages @@ -422,3 +427,13 @@ pub enum StateQueryResponse { UTxOs(UTxOStateQueryResponse), SPDD(SPDDStateQueryResponse), } + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum Command { + Transactions(TransactionsCommand), +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum CommandResponse { + Transactions(TransactionsCommandResponse), +} diff --git a/common/src/stake_addresses.rs b/common/src/stake_addresses.rs index b2a18883..b4daf076 100644 --- a/common/src/stake_addresses.rs +++ b/common/src/stake_addresses.rs @@ -544,7 +544,7 @@ impl StakeAddressMap { #[cfg(test)] mod tests { - use crate::{NetworkId, StakeAddress, StakeAddressPayload}; + use crate::{NetworkId, StakeAddress, StakeCredential}; use super::*; @@ -558,9 +558,7 @@ mod tests { fn create_stake_address(hash: &[u8]) -> StakeAddress { StakeAddress::new( - StakeAddressPayload::StakeKeyHash( - hash.to_vec().try_into().expect("Invalid hash length"), - ), + StakeCredential::AddrKeyHash(hash.to_vec().try_into().expect("Invalid hash length")), NetworkId::Mainnet, ) } diff --git a/common/src/types.rs b/common/src/types.rs index 67b40e04..35182082 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -625,15 +625,16 @@ pub struct PotDelta { pub delta: LovelaceDelta, } +#[serde_as] #[derive( 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. - ScriptHash(KeyHash), + ScriptHash(#[serde_as(as = "Hex")] KeyHash), /// Address key hash - AddrKeyHash(KeyHash), + AddrKeyHash(#[serde_as(as = "Hex")] KeyHash), } impl Credential { @@ -735,6 +736,17 @@ impl Credential { pub type StakeCredential = Credential; +impl StakeCredential { + pub fn to_string(&self) -> Result { + let (hrp, data) = match &self { + Self::AddrKeyHash(data) => (Hrp::parse("stake_vkh")?, data), + Self::ScriptHash(data) => (Hrp::parse("script")?, data), + }; + + Ok(bech32::encode::(hrp, data)?) + } +} + /// Relay single host address #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)] pub struct SingleHostAddr { diff --git a/modules/accounts_state/src/snapshot.rs b/modules/accounts_state/src/snapshot.rs index 3983a1d6..8d1a9d3d 100644 --- a/modules/accounts_state/src/snapshot.rs +++ b/modules/accounts_state/src/snapshot.rs @@ -187,7 +187,7 @@ mod tests { use super::*; use acropolis_common::stake_addresses::StakeAddressState; use acropolis_common::NetworkId::Mainnet; - use acropolis_common::{StakeAddress, StakeAddressPayload}; + use acropolis_common::{StakeAddress, StakeCredential}; // Helper function to create stake addresses for testing fn create_test_stake_address(id: u8) -> StakeAddress { @@ -195,9 +195,7 @@ mod tests { hash[0] = id; StakeAddress { network: Mainnet, - payload: StakeAddressPayload::StakeKeyHash( - hash.try_into().expect("Invalid hash length"), - ), + credential: StakeCredential::AddrKeyHash(hash.try_into().expect("Invalid hash length")), } } diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index ffea3a0d..8d0c61f2 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -837,7 +837,6 @@ impl State { .unwrap_or(DEFAULT_KEY_DEPOSIT) } }; - self.pots.deposits -= deposit; // Schedule refund self.stake_refunds.push((stake_address.clone(), deposit)); @@ -1015,7 +1014,7 @@ mod tests { protocol_params::ConwayParams, rational_number::RationalNumber, Anchor, Committee, Constitution, CostModel, DRepVotingThresholds, NetworkId, PoolVotingThresholds, Pot, PotDelta, Ratio, Registration, RegistrationWithPos, StakeAddress, StakeAddressDelta, - StakeAddressPayload, StakeAndVoteDelegation, StakeAndVoteDelegationWithPos, + StakeAndVoteDelegation, StakeAndVoteDelegationWithPos, StakeCredential, StakeRegistrationAndStakeAndVoteDelegation, StakeRegistrationAndStakeAndVoteDelegationWithPos, StakeRegistrationAndVoteDelegation, StakeRegistrationAndVoteDelegationWithPos, TxIdentifier, VoteDelegation, Withdrawal, @@ -1027,7 +1026,7 @@ mod tests { full_hash[..hash.len().min(28)].copy_from_slice(&hash[..hash.len().min(28)]); StakeAddress { network: NetworkId::Mainnet, - payload: StakeAddressPayload::StakeKeyHash(full_hash), + credential: StakeCredential::AddrKeyHash(full_hash), } } diff --git a/modules/rest_blockfrost/src/handlers/epochs.rs b/modules/rest_blockfrost/src/handlers/epochs.rs index 7df97108..94b4a30d 100644 --- a/modules/rest_blockfrost/src/handlers/epochs.rs +++ b/modules/rest_blockfrost/src/handlers/epochs.rs @@ -15,7 +15,7 @@ use acropolis_common::{ utils::query_state, }, serialization::Bech32WithHrp, - NetworkId, StakeAddress, StakeAddressPayload, + NetworkId, StakeAddress, StakeCredential, }; use anyhow::{anyhow, Result}; use caryatid_sdk::Context; @@ -508,7 +508,7 @@ pub async fn handle_epoch_total_stakes_blockfrost( .map(|(pool_id, stake_key_hash, amount)| { let stake_address = StakeAddress { network: network.clone(), - payload: StakeAddressPayload::StakeKeyHash(stake_key_hash), + credential: StakeCredential::AddrKeyHash(stake_key_hash), } .to_string() .map_err(|e| anyhow::anyhow!("Failed to convert stake address to string: {e}"))?; @@ -642,10 +642,10 @@ pub async fn handle_epoch_pool_stakes_blockfrost( .await?; let spdd_response = spdd .into_iter() - .map(|(stake_key_hash, amount)| { + .map(|(key_hash, amount)| { let stake_address = StakeAddress { network: network.clone(), - payload: StakeAddressPayload::StakeKeyHash(stake_key_hash), + credential: StakeCredential::AddrKeyHash(key_hash), } .to_string() .map_err(|e| anyhow::anyhow!("Failed to convert stake address to string: {e}"))?; diff --git a/modules/stake_delta_filter/src/predefined.rs b/modules/stake_delta_filter/src/predefined.rs index 36debe29..f1a2ef92 100644 --- a/modules/stake_delta_filter/src/predefined.rs +++ b/modules/stake_delta_filter/src/predefined.rs @@ -59,8 +59,8 @@ pub const POINTER_CACHE: [(&str, &str); 1] = [( }, { "network": "Mainnet", - "payload": { - "StakeKeyHash": "bc1597ad71c55d2d009a9274b3831ded155118dd769f5376decc1369" + "credential": { + "AddrKeyHash": "bc1597ad71c55d2d009a9274b3831ded155118dd769f5376decc1369" } } ], @@ -80,8 +80,8 @@ pub const POINTER_CACHE: [(&str, &str); 1] = [( }, { "network": "Mainnet", - "payload": { - "StakeKeyHash": "e46c33afa9ca60cfeb3b7452a415c271772020b3f57ac90c496a6127" + "credential": { + "AddrKeyHash": "e46c33afa9ca60cfeb3b7452a415c271772020b3f57ac90c496a6127" } } ], @@ -117,8 +117,8 @@ pub const POINTER_CACHE: [(&str, &str); 1] = [( }, { "network": "Mainnet", - "payload": { - "StakeKeyHash": "1332d859dd71f5b1089052a049690d81f7367eac9fafaef80b4da395" + "credential": { + "AddrKeyHash": "1332d859dd71f5b1089052a049690d81f7367eac9fafaef80b4da395" } } ], diff --git a/modules/stake_delta_filter/src/utils.rs b/modules/stake_delta_filter/src/utils.rs index 5a349c25..41431f1b 100644 --- a/modules/stake_delta_filter/src/utils.rs +++ b/modules/stake_delta_filter/src/utils.rs @@ -1,7 +1,7 @@ use acropolis_common::{ messages::{AddressDeltasMessage, StakeAddressDeltasMessage}, Address, AddressDelta, BlockInfo, Era, ShelleyAddressDelegationPart, ShelleyAddressPointer, - StakeAddress, StakeAddressDelta, StakeAddressPayload, + StakeAddress, StakeAddressDelta, StakeCredential, }; use anyhow::{anyhow, Result}; use serde_with::serde_as; @@ -345,12 +345,12 @@ pub fn process_message( // Base addresses (stake delegated to itself) ShelleyAddressDelegationPart::StakeKeyHash(keyhash) => StakeAddress { network: shelley.network.clone(), - payload: StakeAddressPayload::StakeKeyHash(keyhash.clone()), + credential: StakeCredential::AddrKeyHash(keyhash.clone()), }, ShelleyAddressDelegationPart::ScriptHash(scripthash) => StakeAddress { network: shelley.network.clone(), - payload: StakeAddressPayload::ScriptHash(scripthash.clone()), + credential: StakeCredential::ScriptHash(scripthash.clone()), }, // Shelley addresses (stake delegated to some different address) @@ -403,9 +403,8 @@ mod test { use crate::*; use acropolis_common::{ messages::AddressDeltasMessage, Address, AddressDelta, BlockHash, BlockInfo, BlockStatus, - ByronAddress, Era, NetworkId, ShelleyAddress, ShelleyAddressDelegationPart, - ShelleyAddressPaymentPart, ShelleyAddressPointer, StakeAddress, StakeAddressPayload, - UTxOIdentifier, ValueDelta, + ByronAddress, Era, ShelleyAddress, ShelleyAddressDelegationPart, ShelleyAddressPaymentPart, + ShelleyAddressPointer, StakeAddress, StakeCredential, UTxOIdentifier, ValueDelta, }; use bech32::{Bech32, Hrp}; @@ -470,12 +469,12 @@ mod test { addresses::Address::Stake(stake_address) => Ok(Address::Stake(StakeAddress { network: map_network(stake_address.network())?, - payload: match stake_address.payload() { + credential: match stake_address.payload() { addresses::StakePayload::Stake(hash) => { - StakeAddressPayload::StakeKeyHash(hash.to_vec()) + StakeCredential::AddrKeyHash(hash.to_vec()) } addresses::StakePayload::Script(hash) => { - StakeAddressPayload::ScriptHash(hash.to_vec()) + StakeCredential::ScriptHash(hash.to_vec()) } }, })), @@ -586,11 +585,11 @@ mod test { // additional check: payload conversion correctness assert_eq!( - stake_delta.deltas.get(0).unwrap().address.payload.to_string().unwrap(), + stake_delta.deltas.get(0).unwrap().address.credential.to_string().unwrap(), stake_key_hash ); assert_eq!( - stake_delta.deltas.get(2).unwrap().address.payload.to_string().unwrap(), + stake_delta.deltas.get(2).unwrap().address.credential.to_string().unwrap(), script_hash ); diff --git a/modules/tx_submitter/Cargo.toml b/modules/tx_submitter/Cargo.toml new file mode 100644 index 00000000..f8bb86a5 --- /dev/null +++ b/modules/tx_submitter/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "acropolis_module_tx_submitter" +version = "0.1.0" +edition = "2024" +authors = ["Simon Gellis "] +description = "TX submission module for Acropolis" +license = "Apache-2.0" + +[dependencies] +acropolis_common = { path = "../../common" } + +caryatid_sdk = { workspace = true } + +anyhow = { workspace = true } +config = { workspace = true } +futures = "0.3.31" +hex = { workspace = true } +pallas = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } + +[lib] +path = "src/tx_submitter.rs" \ No newline at end of file diff --git a/modules/tx_submitter/README.md b/modules/tx_submitter/README.md new file mode 100644 index 00000000..a9b2e836 --- /dev/null +++ b/modules/tx_submitter/README.md @@ -0,0 +1,21 @@ +# TX submission module + +The TX submission module implements the TXSubmission node-to-node protocol to submit transactions to a single upstream source. + +## Messages + +The TX submission module listens for requests to submit transactions on the `cardano.txs.submit` topic. It will send a response once any upstream server has acknowledged the transaction. + +## Default configuration + +```toml +[module.tx-submitter] + +# Upstream node connection +node-address = "backbone.cardano.iog.io:3001" +magic-number = 764824073 + +# Message topics +subscribe-topic = "cardano.txs.submit" + +``` \ No newline at end of file diff --git a/modules/tx_submitter/src/peer.rs b/modules/tx_submitter/src/peer.rs new file mode 100644 index 00000000..3aecaede --- /dev/null +++ b/modules/tx_submitter/src/peer.rs @@ -0,0 +1,333 @@ +use std::{collections::VecDeque, sync::Arc, time::Duration}; + +use anyhow::{Context, Result, bail}; +use config::Config; +use pallas::network::{facades::PeerClient, miniprotocols::txsubmission}; +use tokio::{ + select, + sync::{mpsc, oneshot}, +}; +use tracing::{debug, error, instrument, warn}; + +use crate::{SubmitterConfig, tx::Transaction}; + +pub struct PeerConfig { + address: String, +} +impl PeerConfig { + pub fn parse(config: &Config) -> Result { + let address = + config.get_string("node-address").unwrap_or("backbone.cardano.iog.io:3001".to_string()); + Ok(Self { address }) + } +} + +pub struct PeerConnection { + pub name: String, + tx_sink: mpsc::UnboundedSender, +} +impl PeerConnection { + pub fn open(submitter: &SubmitterConfig, peer: PeerConfig) -> Self { + let (tx_sink, tx_source) = mpsc::unbounded_channel(); + let worker = PeerWorker { + tx_source, + tx_queue: TxQueue::new(), + address: peer.address.clone(), + magic: submitter.magic, + }; + tokio::task::spawn(worker.run()); + Self { + name: peer.address, + tx_sink, + } + } + + pub fn queue(&self, tx: Arc) -> Result> { + let (done, done_rx) = oneshot::channel(); + let queued_tx = QueuedTx { tx, done }; + self.tx_sink.send(queued_tx).context("could not queue tx")?; + Ok(done_rx) + } +} + +struct PeerWorker { + tx_source: mpsc::UnboundedReceiver, + tx_queue: TxQueue, + address: String, + magic: u64, +} +impl PeerWorker { + async fn run(mut self) { + while !self.tx_source.is_closed() { + if let Err(error) = self.run_connection().await { + error!("error connecting to {}: {:#}", self.address, error); + debug!("reconnecting in 5 seconds"); + tokio::time::sleep(Duration::from_secs(5)).await; + } + } + } + + #[instrument(skip(self), fields(address = %self.address))] + async fn run_connection(&mut self) -> Result<()> { + let mut client = + PeerClient::connect(&self.address, self.magic).await.context("could not connect")?; + let submission = client.txsubmission(); + submission.send_init().await.context("failed to init")?; + debug!("initialized connection"); + let mut pending_tx_requests = None; + self.tx_queue.requeue_sent(); + loop { + select! { + new_tx = self.tx_source.recv() => { + let Some(tx) = new_tx else { + // parent process must have disconnected + break; + }; + debug!("received tx {tx}"); + self.tx_queue.push(tx); + if let Some(req) = pending_tx_requests.take() { + let ids = self.tx_queue.req(req); + let count = ids.len(); + submission.reply_tx_ids(ids).await.context("could not send tx ids")?; + self.tx_queue.mark_requested(count); + } + } + request = submission.next_request(), if pending_tx_requests.is_none() => { + let req = request.context("could not receive request")?; + pending_tx_requests = self.handle_request(submission, req).await?; + } + } + } + if !matches!(submission.state(), txsubmission::State::Idle) { + submission.send_done().await?; + } + Ok(()) + } + + async fn handle_request( + &mut self, + submission: &mut txsubmission::GenericClient< + txsubmission::EraTxId, + txsubmission::EraTxBody, + >, + req: txsubmission::Request, + ) -> Result> { + match req { + txsubmission::Request::TxIds(ack, req) => { + debug!("received TxIds({ack}, {req})"); + self.tx_queue.ack(ack)?; + + let ids = self.tx_queue.req(req); + if ids.is_empty() { + Ok(Some(req)) + } else { + let count = ids.len(); + submission.reply_tx_ids(ids).await.context("could not send tx ids")?; + self.tx_queue.mark_requested(count); + Ok(None) + } + } + txsubmission::Request::TxIdsNonBlocking(ack, req) => { + debug!("received TxIdsNonBlocking({ack}, {req})"); + self.tx_queue.ack(ack)?; + + let ids = self.tx_queue.req(req); + let count = ids.len(); + submission.reply_tx_ids(ids).await.context("could not send tx ids")?; + self.tx_queue.mark_requested(count); + Ok(None) + } + txsubmission::Request::Txs(ids) => { + debug!( + "received Txs({:?})", + ids.iter().map(|id| hex::encode(&id.1)).collect::>() + ); + let mut txs = vec![]; + for id in ids { + match self.tx_queue.announced_tx_body(&id) { + Some(body) => { + debug!("Sending TX {}", hex::encode(id.1)); + txs.push(body); + } + None => { + warn!("Server requested unrecognized TX {}", hex::encode(id.1)); + } + } + } + submission.reply_txs(txs).await.context("could not send tx bodies")?; + Ok(None) + } + } + } +} + +struct QueuedTx { + tx: Arc, + done: oneshot::Sender<()>, +} +impl std::fmt::Display for QueuedTx { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&hex::encode(&self.tx.id)) + } +} +impl QueuedTx { + fn tx_id_and_size(&self) -> txsubmission::TxIdAndSize { + txsubmission::TxIdAndSize( + txsubmission::EraTxId(self.tx.era, self.tx.id.to_vec()), + self.tx.body.len() as u32, + ) + } + fn era_tx_body(&self) -> txsubmission::EraTxBody { + txsubmission::EraTxBody(self.tx.era, self.tx.body.clone()) + } +} + +#[derive(Default)] +struct TxQueue { + unsent: VecDeque, + sent: VecDeque, +} +impl TxQueue { + pub fn new() -> Self { + Self::default() + } + + pub fn push(&mut self, tx: QueuedTx) { + self.unsent.push_back(tx); + } + + pub fn ack(&mut self, count: u16) -> Result<()> { + for _ in 0..count { + match self.sent.pop_front() { + Some(tx) => { + debug!("TX {tx} has been acknowledged"); + let _ = tx.done.send(()); + } + None => bail!("Server acked a TX which we never sent"), + } + } + Ok(()) + } + + pub fn req(&self, count: u16) -> Vec> { + self.unsent.iter().take(count as usize).map(|tx| tx.tx_id_and_size()).collect() + } + + pub fn mark_requested(&mut self, count: usize) { + for _ in 0..count { + let tx = self.unsent.pop_front().expect("logic error"); + self.sent.push_back(tx); + } + } + + pub fn announced_tx_body(&self, id: &txsubmission::EraTxId) -> Option { + self.sent.iter().find(|tx| *tx.tx.id == *id.1).map(|tx| tx.era_tx_body()) + } + + pub fn requeue_sent(&mut self) { + while let Some(tx) = self.sent.pop_back() { + self.unsent.push_front(tx); + } + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use acropolis_common::TxHash; + use tokio::sync::oneshot; + + use crate::{peer::QueuedTx, tx::Transaction}; + + use super::TxQueue; + + #[test] + fn tx_queue_should_not_ack_unsubmitted_requests() { + let mut queue = TxQueue::new(); + assert!(queue.ack(1).is_err()); + } + + #[test] + fn tx_queue_should_return_no_txs_when_empty() { + let queue = TxQueue::new(); + assert!(queue.req(1).is_empty()); + } + + #[test] + fn tx_queue_should_acknowledge_request() { + let (done, done_rx) = oneshot::channel(); + let tx = QueuedTx { + tx: Arc::new(Transaction { + id: TxHash::default(), + body: vec![], + era: 6, + }), + done, + }; + let id = tx.tx_id_and_size().0; + let mut queue = TxQueue::new(); + queue.push(tx); + + // the TX hasn't been announced yet + assert!(queue.announced_tx_body(&id).is_none()); + assert!(done_rx.is_empty()); + + // now the server requests it + let ids = queue.req(2); + assert_eq!(ids.len(), 1); + assert_eq!(ids[0].0.1, id.1); + queue.mark_requested(1); + + // the TX has been announced, so the server can request the body + assert!(queue.announced_tx_body(&id).is_some()); + assert!(done_rx.is_empty()); + + // the server acks the request. now we're done! + assert!(queue.ack(1).is_ok()); + assert!(queue.announced_tx_body(&id).is_none()); + assert!(!done_rx.is_empty()); + } + + #[test] + fn tx_queue_should_restart_submission_after_connection_lost() { + let (done, done_rx) = oneshot::channel(); + let tx = QueuedTx { + tx: Arc::new(Transaction { + id: TxHash::default(), + body: vec![], + era: 6, + }), + done, + }; + let id = tx.tx_id_and_size().0; + let mut queue = TxQueue::new(); + queue.push(tx); + + // the TX hasn't been announced yet + assert!(queue.announced_tx_body(&id).is_none()); + assert!(done_rx.is_empty()); + + // now the server requests it + let ids = queue.req(2); + assert_eq!(ids.len(), 1); + assert_eq!(ids[0].0.1, id.1); + queue.mark_requested(1); + + // the TX has been announced, so the server can request the body + assert!(queue.announced_tx_body(&id).is_some()); + assert!(done_rx.is_empty()); + + // uh oh! we disconnected and reconnected before the server acked it. + queue.requeue_sent(); + + // now we pretend we never sent it + assert!(queue.announced_tx_body(&id).is_none()); + assert!(done_rx.is_empty()); + + // and the server can request it again + let ids = queue.req(2); + assert_eq!(ids.len(), 1); + assert_eq!(ids[0].0.1, id.1); + } +} diff --git a/modules/tx_submitter/src/tx.rs b/modules/tx_submitter/src/tx.rs new file mode 100644 index 00000000..c1b038e5 --- /dev/null +++ b/modules/tx_submitter/src/tx.rs @@ -0,0 +1,25 @@ +use acropolis_common::TxHash; +use anyhow::{Result, bail}; +use pallas::ledger::traverse::{Era, MultiEraTx}; + +pub struct Transaction { + pub id: TxHash, + pub body: Vec, + pub era: u16, +} + +impl Transaction { + pub fn from_bytes(bytes: &[u8]) -> Result { + let parsed = MultiEraTx::decode(bytes)?; + let id = TxHash::from(*parsed.hash()); + let era = match parsed.era() { + Era::Conway => 6, + other => bail!("cannot submit {other} era transactions"), + }; + Ok(Self { + id, + body: bytes.to_vec(), + era, + }) + } +} diff --git a/modules/tx_submitter/src/tx_submitter.rs b/modules/tx_submitter/src/tx_submitter.rs new file mode 100644 index 00000000..a8237380 --- /dev/null +++ b/modules/tx_submitter/src/tx_submitter.rs @@ -0,0 +1,98 @@ +mod peer; +mod tx; + +use std::sync::Arc; + +use acropolis_common::{ + commands::transactions::{TransactionsCommand, TransactionsCommandResponse}, + messages::{Command, CommandResponse, Message}, +}; +use anyhow::{Context as _, Result, bail}; +use caryatid_sdk::{Context, Module, module}; +use config::Config; +use futures::stream::{FuturesUnordered, StreamExt}; +use peer::PeerConfig; +use tokio::sync::RwLock; +use tracing::warn; + +use crate::{peer::PeerConnection, tx::Transaction}; + +#[module( + message_type(Message), + name = "tx-submitter", + description = "TX submission module" +)] +pub struct TxSubmitter; + +impl TxSubmitter { + pub async fn init(&self, context: Arc>, config: Arc) -> Result<()> { + let submitter = Arc::new(SubmitterConfig::parse(&config)?); + let peer = PeerConfig::parse(&config)?; + let state = Arc::new(RwLock::new(SubmitterState { + peers: vec![PeerConnection::open(&submitter, peer)], + })); + context.handle(&submitter.subscribe_topic, move |message| { + let state = state.clone(); + async move { + let state = state.read().await; + let res = Self::handle_command(message, &state.peers) + .await + .unwrap_or_else(|e| TransactionsCommandResponse::Error(e.to_string())); + Arc::new(Message::CommandResponse(CommandResponse::Transactions(res))) + } + }); + Ok(()) + } + + async fn handle_command( + message: Arc, + peers: &Vec, + ) -> Result { + let Message::Command(Command::Transactions(TransactionsCommand::Submit { + cbor, + wait_for_ack, + })) = message.as_ref() + else { + bail!("unexpected tx request") + }; + let tx = Arc::new(Transaction::from_bytes(cbor)?); + let mut waiting = FuturesUnordered::new(); + for peer in peers { + let peer_name = peer.name.clone(); + let receiver = peer.queue(tx.clone())?; + waiting.push(async move { + receiver.await.context(format!("could not send tx to {peer_name}")) + }); + } + if !*wait_for_ack { + return Ok(TransactionsCommandResponse::Submitted { id: tx.id }); + } + while let Some(result) = waiting.next().await { + match result { + Ok(()) => return Ok(TransactionsCommandResponse::Submitted { id: tx.id }), + Err(err) => warn!("{err:#}"), + } + } + bail!("could not send tx to any peers"); + } +} + +struct SubmitterConfig { + subscribe_topic: String, + magic: u64, +} +impl SubmitterConfig { + pub fn parse(config: &Config) -> Result { + let subscribe_topic = + config.get_string("subscribe-topic").unwrap_or("cardano.txs.submit".to_string()); + let magic = config.get("magic-number").unwrap_or(764824073); + Ok(Self { + subscribe_topic, + magic, + }) + } +} + +struct SubmitterState { + peers: Vec, +} diff --git a/processes/README.md b/processes/README.md index 42ab8c57..c21ff0c9 100644 --- a/processes/README.md +++ b/processes/README.md @@ -5,3 +5,4 @@ These are the process builds for the Acropolis architecture: * [Omnibus](omnibus/) - All-you-can-eat container for testing * [Replayer](replayer/) - Locally replay previously downloaded selected messages, stored in JSON on disk * [Golden Tests](golden_tests/) - Provides a testing module to execute end to end golden tests +* [TX Submitter CLI](tx_submitter_cli/) - Provides a CLI wrapper for the tx submitter module diff --git a/processes/tx_submitter_cli/Cargo.toml b/processes/tx_submitter_cli/Cargo.toml new file mode 100644 index 00000000..a58ce2b2 --- /dev/null +++ b/processes/tx_submitter_cli/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "acropolis_process_tx_submitter_cli" +version = "0.1.0" +edition = "2024" +authors = ["Simon Gellis "] +description = "CLI tool to submit transactions" +license = "Apache-2.0" + +[dependencies] +acropolis_common = { path = "../../common" } +acropolis_module_tx_submitter = { path = "../../modules/tx_submitter" } + +caryatid_sdk = { workspace = true } +caryatid_process = { workspace = true} + +anyhow = { workspace = true } +clap = { workspace = true } +config = { workspace = true } +hex = { workspace = true } +pallas = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { version = "0.3.20", features = ["registry", "env-filter"] } + +[[bin]] +name = "tx-submitter-cli" +path = "src/main.rs" \ No newline at end of file diff --git a/processes/tx_submitter_cli/README.md b/processes/tx_submitter_cli/README.md new file mode 100644 index 00000000..4c38244a --- /dev/null +++ b/processes/tx_submitter_cli/README.md @@ -0,0 +1,11 @@ +# Acropolis tx-submitter-cli tool + +This process is a CLI wrapper for the [tx_submitter module](../../modules/tx_submitter/). It allows you to submit transactions to upstream peers. + +## How to run it + +```shell +cd processes/tx_submitter_cli +cargo run -- +``` +The `tx-file` arg should be the path to a file containing a raw signed transaction. diff --git a/processes/tx_submitter_cli/src/main.rs b/processes/tx_submitter_cli/src/main.rs new file mode 100644 index 00000000..5a3ee682 --- /dev/null +++ b/processes/tx_submitter_cli/src/main.rs @@ -0,0 +1,125 @@ +use std::{path::PathBuf, sync::Arc}; + +use acropolis_common::{ + commands::transactions::{TransactionsCommand, TransactionsCommandResponse}, + messages::{Command, CommandResponse, Message}, +}; +use acropolis_module_tx_submitter::TxSubmitter; +use anyhow::{Result, bail}; +use caryatid_process::Process; +use caryatid_sdk::{Context, Module, module}; +use clap::Parser; +use config::{Config, File}; +use tokio::{fs, select, sync::mpsc}; +use tracing::info; +use tracing_subscriber::{ + EnvFilter, Layer as _, Registry, filter, fmt, layer::SubscriberExt as _, + util::SubscriberInitExt as _, +}; + +fn default_config_path() -> PathBuf { + PathBuf::from( + option_env!("ACROPOLIS_TX_SUBMITTER_DEFAULT_CONFIG").unwrap_or("tx-submitter.toml"), + ) +} + +#[derive(clap::Parser, Clone)] +struct Args { + /// Path to configuration. + #[arg(long, default_value = default_config_path().into_os_string())] + config: PathBuf, + /// File containing the raw bytes of a transaction. + tx_file: PathBuf, +} + +#[derive(Clone)] +struct CliState { + args: Args, + done: mpsc::Sender>, +} +impl CliState { + pub fn run(self, ctx: Arc>, fut: F) + where + F: FnOnce(Args, Arc>) -> Fut + Send + 'static, + Fut: Future> + Send + 'static, + { + let args = self.args.clone(); + let c = ctx.clone(); + ctx.run(async move { + let result = fut(args, c).await; + let _ = self.done.send(result).await; + }); + } +} + +tokio::task_local!(static CLI: CliState); +async fn run_process(process: Process, args: Args) -> Result<()> { + let (tx, mut rx) = mpsc::channel(1); + let state = CliState { args, done: tx }; + select! { + res = CLI.scope(state, process.run()) => { + res?; + bail!("process terminated") + } + res = rx.recv() => { + match res { + Some(result) => { + info!("process completed"); + result + } + None => bail!("process terminated") + } + } + } +} + +#[tokio::main] +pub async fn main() -> Result<()> { + let args = Args::try_parse()?; + + // Standard logging using RUST_LOG for log levels default to INFO for events only + let fmt_layer = fmt::layer() + .with_filter(EnvFilter::from_default_env().add_directive(filter::LevelFilter::INFO.into())) + .with_filter(filter::filter_fn(|meta| meta.is_event())); + Registry::default().with(fmt_layer).init(); + + let config = Arc::new(Config::builder().add_source(File::from(args.config.as_path())).build()?); + let mut process = Process::::create(config).await; + + TxSubmitter::register(&mut process); + CliDriver::register(&mut process); + + run_process(process, args).await +} + +#[module( + message_type(Message), + name = "cli-driver", + description = "Module to interface with the CLI tool" +)] +struct CliDriver; +impl CliDriver { + pub async fn init(&self, context: Arc>, _config: Arc) -> Result<()> { + let state = CLI.get(); + state.run(context, move |args, context| async move { + let tx = fs::read(args.tx_file).await?; + let request = Arc::new(Message::Command(Command::Transactions( + TransactionsCommand::Submit { + cbor: tx, + wait_for_ack: true, + }, + ))); + let response = context.request("cardano.txs.submit", request).await?; + if let Message::CommandResponse(CommandResponse::Transactions( + TransactionsCommandResponse::Submitted { id }, + )) = response.as_ref() + { + info!("Submitted TX {}", hex::encode(id)); + } else { + info!("{response:?}"); + } + Ok(()) + }); + Ok(()) + } +} diff --git a/processes/tx_submitter_cli/tx-submitter.toml b/processes/tx_submitter_cli/tx-submitter.toml new file mode 100644 index 00000000..1c894c94 --- /dev/null +++ b/processes/tx_submitter_cli/tx-submitter.toml @@ -0,0 +1,18 @@ +[module.tx-submitter] + +[module.cli-driver] + +[message-bus.internal] +class = "in-memory" +workers = 50 +dispatch-queue-size = 1000 +worker-queue-size = 100 +bulk-block-capacity = 50 +bulk-resume-capacity = 75 + +# Message routing +[message-router] +request-timeout = 300 +[[message-router.route]] # Everything is internal only +pattern = "#" +bus = "internal" diff --git a/scripts/weekly_status_markdown.py b/scripts/weekly_status_markdown.py new file mode 100644 index 00000000..9377ebcd --- /dev/null +++ b/scripts/weekly_status_markdown.py @@ -0,0 +1,536 @@ +#!/usr/bin/env python3 +""" +Generates a 2-column Markdown snippet for weekly status: +- Last week's achievements (Status == Done) +- Plans for next week (Status == In Progress) + +Data sources (in priority order): + A) GitHub Projects v2 Status field (org-level or user-level project) + B) Issue labels (DONE_LABELS / INPROGRESS_LABELS, comma-separated) + +Environment: + REPO (required) e.g. "input-output-hk/acropolis" + # --- For Projects v2 (preferred) --- + PROJECT_OWNER (optional) e.g. "input-output-hk" (org login) or user login + PROJECT_NUMBER (optional) e.g. "7" + STATUS_DONE_VALUE (optional) defaults to "Done" + STATUS_INPROGRESS_VALUE (optional) defaults to "In Progress" + # --- Fallback via labels --- + DONE_LABELS (optional) comma-separated (default: "status: done,done") + INPROGRESS_LABELS (optional) comma-separated (default: "status: in progress,in progress") + +Behavior: + - "Last week" time window is the previous Mon–Sun in repo default timezone (UTC). + - "Achievements" are items with Status == Done and (closed/merged/updated) in last week. + - "Plans" are items currently Status == In Progress and updated within last 14 days. + +Output: + - Prints Markdown to stdout + - If GITHUB_STEP_SUMMARY is set, also writes to it. + - If OUTPUT_PATH is set, also writes file to that path. +""" + +# Suppress urllib3 warnings early +import warnings +warnings.filterwarnings("ignore", message="urllib3 v2 only supports OpenSSL 1.1.1+") +warnings.filterwarnings("ignore", category=UserWarning, module="urllib3") + +import os +import sys +import json +import datetime as dt +from typing import List, Dict, Optional, Tuple +import requests + +GITHUB_API = "https://api.github.com/graphql" +REST_API = "https://api.github.com" + +def iso(d: dt.datetime) -> str: + return d.replace(microsecond=0, tzinfo=dt.timezone.utc).isoformat().replace("+00:00", "Z") + +def previous_monday(d: dt.date) -> dt.date: + return d - dt.timedelta(days=(d.weekday())) # Monday is 0 + +def last_week_window(today: dt.date) -> Tuple[dt.datetime, dt.datetime]: + # Define "last week" as the Monday..Sunday immediately before the current week. + this_monday = previous_monday(today) + last_monday = this_monday - dt.timedelta(days=7) + last_sunday = this_monday - dt.timedelta(seconds=1) + start = dt.datetime.combine(last_monday, dt.time(0,0,0), tzinfo=dt.timezone.utc) + end = dt.datetime.combine(last_sunday, dt.time(23,59,59), tzinfo=dt.timezone.utc) + return start, end + +def gh_graphql(token: str, query: str, variables: dict) -> dict: + r = requests.post( + GITHUB_API, + headers={"Authorization": f"bearer {token}", "Accept": "application/vnd.github+json"}, + json={"query": query, "variables": variables}, + timeout=60, + ) + r.raise_for_status() + data = r.json() + if "errors" in data: + raise RuntimeError(f"GitHub GraphQL errors: {data['errors']}") + return data["data"] + +def gh_rest(token: str, url: str, params: dict=None) -> dict: + r = requests.get( + url, + headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"}, + params=params or {}, + timeout=60, + ) + r.raise_for_status() + return r.json() + +def discover_projects(token: str, owner: str) -> List[dict]: + """ + Discover available GitHub Projects v2 for the given owner (org or user). + Returns list of {number, title} dicts. + """ + query = """ + query($owner: String!) { + organization(login: $owner) { + projectsV2(first: 20) { + nodes { number title } + } + } + user(login: $owner) { + projectsV2(first: 20) { + nodes { number title } + } + } + } + """ + try: + data = gh_graphql(token, query, {"owner": owner}) + projects = [] + + # Try org projects first + org_projects = data.get("organization", {}) + if org_projects and org_projects.get("projectsV2"): + projects.extend(org_projects["projectsV2"]["nodes"] or []) + + # Try user projects + user_projects = data.get("user", {}) + if user_projects and user_projects.get("projectsV2"): + projects.extend(user_projects["projectsV2"]["nodes"] or []) + + return [p for p in projects if p] # filter out nulls + except Exception as e: + print(f"[warn] Failed to discover projects for {owner}: {e}", file=sys.stderr) + return [] + +def get_project_and_status_field(token: str, owner: str, number: int) -> Tuple[str, str, Dict[str,str]]: + """ + Returns (projectId, statusFieldId, statusOptionsMap{name->optionId}) + """ + query = """ + query($owner: String!, $number: Int!) { + organization(login: $owner) { + projectV2(number: $number) { + id + fields(first: 50) { + nodes { + ... on ProjectV2FieldCommon { + id + name + dataType + } + ... on ProjectV2SingleSelectField { + id + name + dataType + options { id name } + } + } + } + } + } + user(login: $owner) { + projectV2(number: $number) { + id + fields(first: 50) { + nodes { + ... on ProjectV2FieldCommon { + id + name + dataType + } + ... on ProjectV2SingleSelectField { + id + name + dataType + options { id name } + } + } + } + } + } + } + """ + data = gh_graphql(token, query, {"owner": owner, "number": number}) + proj = data.get("organization", {}).get("projectV2") or data.get("user", {}).get("projectV2") + if not proj: + raise RuntimeError("Project not found (check PROJECT_OWNER/PROJECT_NUMBER).") + project_id = proj["id"] + + status_field = None + status_options = {} + for f in proj["fields"]["nodes"]: + if f and f.get("name") == "Status": + status_field = f["id"] + opts = f.get("options") or [] + status_options = {o["name"]: o["id"] for o in opts} + break + if not status_field: + raise RuntimeError("Status field not found on the project.") + return project_id, status_field, status_options + +def items_by_status_from_project(token: str, owner: str, number: int, + wanted_status_names: List[str], + repo_fullname: str) -> Dict[str, List[dict]]: + """ + Returns map {statusName: [ {title,url,number,updatedAt,closedAt,type} ]} + filtered to the specified repo. + """ + project_id, status_field_id, status_options = get_project_and_status_field(token, owner, number) + wanted_option_ids = [status_options[n] for n in wanted_status_names if n in status_options] + + results = {n: [] for n in wanted_status_names} + + # Paginate through project items + query = """ + query($projectId: ID!, $after: String) { + node(id: $projectId) { + ... on ProjectV2 { + items(first: 100, after: $after) { + pageInfo { hasNextPage endCursor } + nodes { + updatedAt + content { + __typename + ... on Issue { + number + title + url + repository { nameWithOwner } + closedAt + } + ... on PullRequest { + number + title + url + repository { nameWithOwner } + mergedAt + closedAt + } + } + fieldValues(first: 20) { + nodes { + __typename + ... on ProjectV2ItemFieldSingleSelectValue { + field { ... on ProjectV2SingleSelectField { id name } } + optionId + } + } + } + } + } + } + } + } + """ + after = None + while True: + data = gh_graphql(token, query, {"projectId": project_id, "after": after}) + items = data["node"]["items"]["nodes"] + for it in items: + # find the Status value (optionId) + option_id = None + for fv in it["fieldValues"]["nodes"]: + if fv.get("__typename") == "ProjectV2ItemFieldSingleSelectValue" and \ + fv.get("field", {}).get("id") == status_field_id: + option_id = fv.get("optionId") + break + if option_id not in wanted_option_ids: + continue + + content = it.get("content") or {} + typename = content.get("__typename") + if typename not in ("Issue", "PullRequest"): + continue + if content["repository"]["nameWithOwner"].lower() != repo_fullname.lower(): + continue + + closedAt = content.get("closedAt") + mergedAt = content.get("mergedAt") + updatedAt = it["updatedAt"] + + results_key = None + for name, oid in status_options.items(): + if oid == option_id: + results_key = name + break + if not results_key: + continue + + results[results_key].append({ + "type": typename, + "title": content["title"], + "url": content["url"], + "number": content["number"], + "closedAt": closedAt, + "mergedAt": mergedAt, + "updatedAt": updatedAt, + }) + + pi = data["node"]["items"]["pageInfo"] + if not pi["hasNextPage"]: + break + after = pi["endCursor"] + + return results + +def search_by_labels(token: str, repo: str, labels: List[str]) -> List[dict]: + """ + Simple REST search for issues with any of the labels. + """ + items = [] + for lab in labels: + page = 1 + while True: + res = gh_rest(token, f"{REST_API}/repos/{repo}/issues", + params={"state": "all", "labels": lab, "per_page": 100, "page": page}) + if not res: + break + for it in res: + # Skip pull-request-only stubs unless desired; GitHub REST issues may include PRs + title = it["title"] + url = it["html_url"] + num = it["number"] + closedAt = it.get("closed_at") + updatedAt = it.get("updated_at") + is_pr = "pull_request" in it + items.append({ + "type": "PullRequest" if is_pr else "Issue", + "title": title, "url": url, "number": num, + "closedAt": closedAt, "mergedAt": None, "updatedAt": updatedAt + }) + if len(res) < 100: + break + page += 1 + # de-dup by number + seen = {} + for x in items: + seen[x["number"]] = x + return list(seen.values()) + +def get_recent_issues_by_state(token: str, repo: str, state: str = "all", days: int = 7) -> List[dict]: + """ + Fetch recent issues/PRs from the repo by state (open/closed/all). + More targeted approach when labels aren't well organized. + """ + items = [] + page = 1 + + # Calculate the date threshold + since_date = dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=days) + since_iso = since_date.isoformat().replace('+00:00', 'Z') + + while True: + params = { + "state": state, + "per_page": 100, + "page": page, + "sort": "updated", + "direction": "desc", + "since": since_iso + } + + res = gh_rest(token, f"{REST_API}/repos/{repo}/issues", params=params) + if not res: + break + + for it in res: + title = it["title"] + url = it["html_url"] + num = it["number"] + closedAt = it.get("closed_at") + updatedAt = it.get("updated_at") + createdAt = it.get("created_at") + state_val = it.get("state") + is_pr = "pull_request" in it + + items.append({ + "type": "PullRequest" if is_pr else "Issue", + "title": title, + "url": url, + "number": num, + "closedAt": closedAt, + "mergedAt": None, + "updatedAt": updatedAt, + "createdAt": createdAt, + "state": state_val + }) + + if len(res) < 100: + break + page += 1 + + return items + +def format_markdown(done_items: List[dict], inprog_items: List[dict]) -> str: + def fmt(items: List[dict]) -> str: + if not items: + return "_(none)_" + # sort by updatedAt desc + items = sorted(items, key=lambda x: x.get("updatedAt") or "", reverse=True) + lines = [] + for it in items: + t = "PR" if it["type"] == "PullRequest" else "Issue" + lines.append(f"- [{t} #{it['number']}]({it['url']}) — {it['title']}") + return "\n".join(lines) + + left = fmt(done_items) + right = fmt(inprog_items) + + # Two separate sections for easy copying to Confluence + md = [] + md.append("## Last week's achievements") + md.append("") + md.append(left) + md.append("") + md.append("## Plans for next week") + md.append("") + md.append(right) + + return "\n".join(md) + +def main(): + token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") + repo = os.environ.get("REPO") + if not token or not repo: + print("Missing GH_TOKEN/GITHUB_TOKEN or REPO (e.g., 'input-output-hk/acropolis')", file=sys.stderr) + sys.exit(2) + + # time windows + today = dt.datetime.now(dt.timezone.utc).date() + last_start, last_end = last_week_window(today) + + status_done_val = os.environ.get("STATUS_DONE_VALUE", "Done") + status_ip_val = os.environ.get("STATUS_INPROGRESS_VALUE", "In Progress") + + done_items: List[dict] = [] + inprog_items: List[dict] = [] + + proj_owner = os.environ.get("PROJECT_OWNER") + proj_number = os.environ.get("PROJECT_NUMBER") + + # Auto-discover projects if not specified + if not proj_owner or not proj_number: + # Try the repo owner first + repo_owner = repo.split('/')[0] + print(f"[info] No project specified, discovering projects for {repo_owner}...", file=sys.stderr) + projects = discover_projects(token, repo_owner) + + if projects: + print(f"[info] Found {len(projects)} projects:", file=sys.stderr) + for p in projects: + print(f" - Project {p['number']}: {p['title']}", file=sys.stderr) + + # Use the first project as default + if not proj_owner: + proj_owner = repo_owner + if not proj_number: + proj_number = str(projects[0]['number']) + print(f"[info] Auto-selecting project {proj_number}: {projects[0]['title']}", file=sys.stderr) + else: + print(f"[info] No projects found for {repo_owner}", file=sys.stderr) + + try_projects = bool(proj_owner and proj_number) + + if try_projects: + try: + print(f"[info] Attempting to use GitHub Project: owner={proj_owner}, number={proj_number}", file=sys.stderr) + buckets = items_by_status_from_project( + token, proj_owner, int(proj_number), + [status_done_val, status_ip_val], + repo_fullname=repo + ) + done_items = buckets.get(status_done_val, []) + inprog_items = buckets.get(status_ip_val, []) + print(f"[info] Found {len(done_items)} done items, {len(inprog_items)} in-progress items from project", file=sys.stderr) + except Exception as e: + print(f"[warn] Project v2 lookup failed, falling back to state-based filtering: {e}", file=sys.stderr) + try_projects = False + + if not try_projects: + # Try label-based approach first + done_labels = os.environ.get("DONE_LABELS", "").split(",") + ip_labels = os.environ.get("INPROGRESS_LABELS", "").split(",") + + # Filter out empty labels + done_labels = [s.strip() for s in done_labels if s.strip()] + ip_labels = [s.strip() for s in ip_labels if s.strip()] + + if done_labels or ip_labels: + # Use label-based approach if labels are specified + done_items = search_by_labels(token, repo, done_labels) if done_labels else [] + inprog_items= search_by_labels(token, repo, ip_labels) if ip_labels else [] + else: + # Fallback: use state-based approach + print("[info] No specific labels configured, using state-based filtering", file=sys.stderr) + + # Get all recent issues from the last 14 days for broader context + all_recent = get_recent_issues_by_state(token, repo, state="all", days=14) + + # Split into done (closed) and in-progress (open) items + done_items = [it for it in all_recent if it.get("state") == "closed"] + inprog_items = [it for it in all_recent if it.get("state") == "open"] + + # Filter by time windows + def closed_last_week(it: dict) -> bool: + # For achievements: closed/merged in the last week + for k in ("mergedAt", "closedAt"): + v = it.get(k) + if not v: + continue + t = dt.datetime.fromisoformat(v.replace("Z","+00:00")) + if last_start <= t <= last_end: + return True + return False + + def updated_recent(it: dict, days=7) -> bool: + # For in-progress: updated in the last week + v = it.get("updatedAt") + if not v: + return False + t = dt.datetime.fromisoformat(v.replace("Z","+00:00")) + return (dt.datetime.now(dt.timezone.utc) - t).days <= days + + # Achievements: items that were closed in the last week + achievements = [it for it in done_items if closed_last_week(it)] + + # Plans: open items that were updated in the last week (showing active work) + plans = [it for it in inprog_items if it.get("state") == "open" and updated_recent(it, days=7)] + + md = format_markdown(achievements, plans) + + # Print and optionally write outputs + print(md) + step_summary = os.environ.get("GITHUB_STEP_SUMMARY") + if step_summary: + with open(step_summary, "a", encoding="utf-8") as f: + f.write("\n\n## Weekly Status (auto-generated)\n") + f.write(md) + f.write("\n") + + out_path = os.environ.get("OUTPUT_PATH") + if out_path: + os.makedirs(os.path.dirname(out_path), exist_ok=True) + with open(out_path, "w", encoding="utf-8") as f: + f.write(md) + +if __name__ == "__main__": + main() From 437d3bb081f44770784c13d508b3c81e4cc19da3 Mon Sep 17 00:00:00 2001 From: William Hankins Date: Mon, 27 Oct 2025 20:16:10 +0000 Subject: [PATCH 5/6] refactor: Unify certificate position handling using TxCertificateWithPos Signed-off-by: William Hankins --- codec/src/map_parameters.rs | 484 +++++++++--------- common/src/messages.rs | 2 +- common/src/types.rs | 37 +- modules/accounts_state/src/state.rs | 139 +++-- modules/drep_state/src/state.rs | 166 +++--- .../historical_accounts_state/src/state.rs | 60 +-- modules/spo_state/src/state.rs | 356 ++++++------- modules/spo_state/src/test_utils.rs | 4 +- modules/stake_delta_filter/src/state.rs | 10 +- modules/tx_unpacker/src/tx_unpacker.rs | 2 +- 10 files changed, 603 insertions(+), 657 deletions(-) diff --git a/codec/src/map_parameters.rs b/codec/src/map_parameters.rs index 986846a1..ecae6d64 100644 --- a/codec/src/map_parameters.rs +++ b/codec/src/map_parameters.rs @@ -211,39 +211,31 @@ fn map_relay(relay: &PallasRelay) -> Relay { pub fn map_certificate( cert: &MultiEraCert, tx_identifier: TxIdentifier, - tx_index: u16, cert_index: usize, network_id: NetworkId, -) -> Result { +) -> 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(StakeAddressWithPos { - stake_address: map_stake_address(cred, network_id), - tx_identifier, - tx_index: tx_index.into(), - cert_index: cert_index.try_into().unwrap(), - })) - } - alonzo::Certificate::StakeDeregistration(cred) => { - Ok(TxCertificate::StakeDeregistration(StakeAddressWithPos { + alonzo::Certificate::StakeRegistration(cred) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistration(map_stake_address(cred, network_id)), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }), + alonzo::Certificate::StakeDeregistration(cred) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }), + alonzo::Certificate::StakeDelegation(cred, pool_key_hash) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeDelegation(StakeDelegation { stake_address: map_stake_address(cred, network_id), - tx_identifier, - tx_index: tx_index.try_into().unwrap(), - cert_index: cert_index.try_into().unwrap(), - })) - } - alonzo::Certificate::StakeDelegation(cred, pool_key_hash) => { - Ok(TxCertificate::StakeDelegation(StakeDelegationWithPos { - cert: StakeDelegation { - stake_address: map_stake_address(cred, network_id), - operator: pool_key_hash.to_vec(), - }, - tx_identifier, - })) - } + operator: pool_key_hash.to_vec(), + }), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }), alonzo::Certificate::PoolRegistration { operator, vrf_keyhash, @@ -254,119 +246,115 @@ pub fn map_certificate( pool_owners, relays, pool_metadata, - } => Ok(TxCertificate::PoolRegistrationWithPos( - PoolRegistrationWithPos { - cert: PoolRegistration { - operator: operator.to_vec(), - vrf_key_hash: vrf_keyhash.to_vec(), - pledge: *pledge, - cost: *cost, - margin: Ratio { - numerator: margin.numerator, - denominator: margin.denominator, - }, - reward_account: StakeAddress::from_binary(reward_account)?, - pool_owners: pool_owners - .iter() - .map(|v| { - StakeAddress::new( - StakeCredential::AddrKeyHash(v.to_vec()), - network_id.clone().into(), - ) - }) - .collect(), - relays: relays.iter().map(map_relay).collect(), - pool_metadata: match pool_metadata { - Nullable::Some(md) => Some(PoolMetadata { - url: md.url.clone(), - hash: md.hash.to_vec(), - }), - _ => None, - }, + } => Ok(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(PoolRegistration { + operator: operator.to_vec(), + vrf_key_hash: vrf_keyhash.to_vec(), + pledge: *pledge, + cost: *cost, + margin: Ratio { + numerator: margin.numerator, + denominator: margin.denominator, }, - tx_identifier, - cert_index: cert_index as u64, - }, - )), - alonzo::Certificate::PoolRetirement(pool_key_hash, epoch) => Ok( - TxCertificate::PoolRetirementWithPos(PoolRetirementWithPos { - cert: PoolRetirement { - operator: pool_key_hash.to_vec(), - epoch: *epoch, + reward_account: StakeAddress::from_binary(reward_account)?, + pool_owners: pool_owners + .iter() + .map(|v| { + StakeAddress::new( + StakeCredential::AddrKeyHash(v.to_vec()), + network_id.clone().into(), + ) + }) + .collect(), + relays: relays.iter().map(map_relay).collect(), + pool_metadata: match pool_metadata { + Nullable::Some(md) => Some(PoolMetadata { + url: md.url.clone(), + hash: md.hash.to_vec(), + }), + _ => None, }, - tx_identifier, - cert_index: cert_index as u64, }), - ), + tx_identifier, + cert_index: cert_index as u64, + }), + alonzo::Certificate::PoolRetirement(pool_key_hash, epoch) => Ok(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: pool_key_hash.to_vec(), + epoch: *epoch, + }), + tx_identifier, + cert_index: cert_index as u64, + }), alonzo::Certificate::GenesisKeyDelegation( genesis_hash, genesis_delegate_hash, vrf_key_hash, - ) => Ok(TxCertificate::GenesisKeyDelegation(GenesisKeyDelegation { - genesis_hash: genesis_hash.to_vec(), - genesis_delegate_hash: genesis_delegate_hash.to_vec(), - vrf_key_hash: vrf_key_hash.to_vec(), - })), - alonzo::Certificate::MoveInstantaneousRewardsCert(mir) => Ok( - TxCertificate::MoveInstantaneousReward(MoveInstantaneousRewardWithPos { - cert: MoveInstantaneousReward { - source: match mir.source { - alonzo::InstantaneousRewardSource::Reserves => { - InstantaneousRewardSource::Reserves - } - alonzo::InstantaneousRewardSource::Treasury => { - InstantaneousRewardSource::Treasury - } - }, - target: match &mir.target { - alonzo::InstantaneousRewardTarget::StakeCredentials(creds) => { - InstantaneousRewardTarget::StakeAddresses( - creds - .iter() - .map(|(sc, v)| { - (map_stake_address(sc, network_id.clone()), *v) - }) - .collect(), - ) - } - alonzo::InstantaneousRewardTarget::OtherAccountingPot(n) => { - InstantaneousRewardTarget::OtherAccountingPot(*n) - } - }, + ) => Ok(TxCertificateWithPos { + cert: TxCertificate::GenesisKeyDelegation(GenesisKeyDelegation { + genesis_hash: genesis_hash.to_vec(), + genesis_delegate_hash: genesis_delegate_hash.to_vec(), + vrf_key_hash: vrf_key_hash.to_vec(), + }), + tx_identifier, + cert_index: cert_index as u64, + }), + alonzo::Certificate::MoveInstantaneousRewardsCert(mir) => Ok(TxCertificateWithPos { + cert: TxCertificate::MoveInstantaneousReward(MoveInstantaneousReward { + source: match mir.source { + alonzo::InstantaneousRewardSource::Reserves => { + InstantaneousRewardSource::Reserves + } + alonzo::InstantaneousRewardSource::Treasury => { + InstantaneousRewardSource::Treasury + } + }, + target: match &mir.target { + alonzo::InstantaneousRewardTarget::StakeCredentials(creds) => { + InstantaneousRewardTarget::StakeAddresses( + creds + .iter() + .map(|(sc, v)| (map_stake_address(sc, network_id.clone()), *v)) + .collect(), + ) + } + alonzo::InstantaneousRewardTarget::OtherAccountingPot(n) => { + InstantaneousRewardTarget::OtherAccountingPot(*n) + } }, - tx_identifier, }), - ), + tx_identifier, + cert_index: cert_index as u64, + }), }, // Now repeated for a different type! MultiEraCert::Conway(cert) => { match cert.as_ref().as_ref() { - conway::Certificate::StakeRegistration(cred) => { - Ok(TxCertificate::StakeRegistration(StakeAddressWithPos { - stake_address: map_stake_address(cred, network_id), - tx_identifier, - tx_index: tx_index.into(), - cert_index: cert_index.try_into().unwrap(), - })) - } - conway::Certificate::StakeDeregistration(cred) => { - Ok(TxCertificate::StakeDeregistration(StakeAddressWithPos { - stake_address: map_stake_address(cred, network_id), - tx_identifier, - tx_index: tx_index.try_into().unwrap(), - cert_index: cert_index.try_into().unwrap(), - })) - } + conway::Certificate::StakeRegistration(cred) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistration(map_stake_address(cred, network_id)), + tx_identifier, + + cert_index: cert_index.try_into().unwrap(), + }), + + conway::Certificate::StakeDeregistration(cred) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)), + tx_identifier, + cert_index: cert_index.try_into().unwrap(), + }), + conway::Certificate::StakeDelegation(cred, pool_key_hash) => { - Ok(TxCertificate::StakeDelegation(StakeDelegationWithPos { - cert: StakeDelegation { + Ok(TxCertificateWithPos { + cert: TxCertificate::StakeDelegation(StakeDelegation { stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), - }, + }), tx_identifier, - })) + cert_index: cert_index.try_into().unwrap(), + }) } + conway::Certificate::PoolRegistration { // TODO relays, pool_metadata operator, @@ -378,178 +366,178 @@ pub fn map_certificate( pool_owners, relays, pool_metadata, - } => Ok(TxCertificate::PoolRegistrationWithPos( - PoolRegistrationWithPos { - cert: PoolRegistration { - operator: operator.to_vec(), - vrf_key_hash: vrf_keyhash.to_vec(), - pledge: *pledge, - cost: *cost, - margin: Ratio { - numerator: margin.numerator, - denominator: margin.denominator, - }, - reward_account: StakeAddress::from_binary(reward_account)?, - pool_owners: pool_owners - .into_iter() - .map(|v| { - StakeAddress::new( - StakeCredential::AddrKeyHash(v.to_vec()), - network_id.clone().into(), - ) - }) - .collect(), - relays: relays.iter().map(map_relay).collect(), - pool_metadata: match pool_metadata { - Nullable::Some(md) => Some(PoolMetadata { - url: md.url.clone(), - hash: md.hash.to_vec(), - }), - _ => None, - }, + } => Ok(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(PoolRegistration { + operator: operator.to_vec(), + vrf_key_hash: vrf_keyhash.to_vec(), + pledge: *pledge, + cost: *cost, + margin: Ratio { + numerator: margin.numerator, + denominator: margin.denominator, }, - tx_identifier, - cert_index: cert_index as u64, - }, - )), - conway::Certificate::PoolRetirement(pool_key_hash, epoch) => Ok( - TxCertificate::PoolRetirementWithPos(PoolRetirementWithPos { - cert: PoolRetirement { - operator: pool_key_hash.to_vec(), - epoch: *epoch, + reward_account: StakeAddress::from_binary(reward_account)?, + pool_owners: pool_owners + .into_iter() + .map(|v| { + StakeAddress::new( + StakeCredential::AddrKeyHash(v.to_vec()), + network_id.clone().into(), + ) + }) + .collect(), + relays: relays.iter().map(map_relay).collect(), + pool_metadata: match pool_metadata { + Nullable::Some(md) => Some(PoolMetadata { + url: md.url.clone(), + hash: md.hash.to_vec(), + }), + _ => None, }, - tx_identifier, - cert_index: cert_index as u64, }), - ), - - conway::Certificate::Reg(cred, coin) => { - Ok(TxCertificate::Registration(RegistrationWithPos { - cert: Registration { - stake_address: map_stake_address(cred, network_id), - deposit: *coin, - }, + tx_identifier, + cert_index: cert_index as u64, + }), + conway::Certificate::PoolRetirement(pool_key_hash, epoch) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: pool_key_hash.to_vec(), + epoch: *epoch, + }), tx_identifier, cert_index: cert_index as u64, - })) + }) } - conway::Certificate::UnReg(cred, coin) => { - Ok(TxCertificate::Deregistration(DeregistrationWithPos { - cert: Deregistration { - stake_address: map_stake_address(cred, network_id), - refund: *coin, - }, - tx_identifier, - cert_index: cert_index as u64, - })) - } + conway::Certificate::Reg(cred, coin) => Ok(TxCertificateWithPos { + cert: TxCertificate::Registration(Registration { + stake_address: map_stake_address(cred, network_id), + deposit: *coin, + }), + tx_identifier, + cert_index: cert_index as u64, + }), - conway::Certificate::VoteDeleg(cred, drep) => { - Ok(TxCertificate::VoteDelegation(VoteDelegation { + conway::Certificate::UnReg(cred, coin) => Ok(TxCertificateWithPos { + cert: TxCertificate::Deregistration(Deregistration { + stake_address: map_stake_address(cred, network_id), + refund: *coin, + }), + tx_identifier, + cert_index: cert_index as u64, + }), + + conway::Certificate::VoteDeleg(cred, drep) => Ok(TxCertificateWithPos { + cert: TxCertificate::VoteDelegation(VoteDelegation { stake_address: map_stake_address(cred, network_id), drep: map_drep(drep), - })) - } + }), + tx_identifier, + cert_index: cert_index as u64, + }), - conway::Certificate::StakeVoteDeleg(cred, pool_key_hash, drep) => Ok( - TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegationWithPos { - cert: StakeAndVoteDelegation { + conway::Certificate::StakeVoteDeleg(cred, pool_key_hash, drep) => { + Ok(TxCertificateWithPos { + cert: TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), drep: map_drep(drep), - }, + }), tx_identifier, - }), - ), + cert_index: cert_index as u64, + }) + } conway::Certificate::StakeRegDeleg(cred, pool_key_hash, coin) => { - Ok(TxCertificate::StakeRegistrationAndDelegation( - StakeRegistrationAndDelegationWithPos { - cert: StakeRegistrationAndDelegation { + Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistrationAndDelegation( + StakeRegistrationAndDelegation { stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), deposit: *coin, }, - tx_identifier, - }, - )) + ), + tx_identifier, + cert_index: cert_index as u64, + }) } - conway::Certificate::VoteRegDeleg(cred, drep, coin) => { - Ok(TxCertificate::StakeRegistrationAndVoteDelegation( - StakeRegistrationAndVoteDelegationWithPos { - cert: StakeRegistrationAndVoteDelegation { - stake_address: map_stake_address(cred, network_id), - drep: map_drep(drep), - deposit: *coin, - }, - tx_identifier, + conway::Certificate::VoteRegDeleg(cred, drep, coin) => Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistrationAndVoteDelegation( + StakeRegistrationAndVoteDelegation { + stake_address: map_stake_address(cred, network_id), + drep: map_drep(drep), + deposit: *coin, }, - )) - } + ), + tx_identifier, + cert_index: cert_index as u64, + }), conway::Certificate::StakeVoteRegDeleg(cred, pool_key_hash, drep, coin) => { - Ok(TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( - StakeRegistrationAndStakeAndVoteDelegationWithPos { - cert: StakeRegistrationAndStakeAndVoteDelegation { + Ok(TxCertificateWithPos { + cert: TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( + StakeRegistrationAndStakeAndVoteDelegation { stake_address: map_stake_address(cred, network_id), operator: pool_key_hash.to_vec(), drep: map_drep(drep), deposit: *coin, }, - tx_identifier, - }, - )) + ), + tx_identifier, + cert_index: cert_index as u64, + }) } conway::Certificate::AuthCommitteeHot(cold_cred, hot_cred) => { - Ok(TxCertificate::AuthCommitteeHot(AuthCommitteeHot { - cold_credential: map_stake_credential(cold_cred), - hot_credential: map_stake_credential(hot_cred), - })) + Ok(TxCertificateWithPos { + cert: TxCertificate::AuthCommitteeHot(AuthCommitteeHot { + cold_credential: map_stake_credential(cold_cred), + hot_credential: map_stake_credential(hot_cred), + }), + tx_identifier, + cert_index: cert_index as u64, + }) } conway::Certificate::ResignCommitteeCold(cold_cred, anchor) => { - Ok(TxCertificate::ResignCommitteeCold(ResignCommitteeCold { - cold_credential: map_stake_credential(cold_cred), - anchor: map_nullable_anchor(anchor), - })) - } - - conway::Certificate::RegDRepCert(cred, coin, anchor) => { - Ok(TxCertificate::DRepRegistration(DRepRegistrationWithPos { - cert: DRepRegistration { - credential: map_stake_credential(cred), - deposit: *coin, + Ok(TxCertificateWithPos { + cert: TxCertificate::ResignCommitteeCold(ResignCommitteeCold { + cold_credential: map_stake_credential(cold_cred), anchor: map_nullable_anchor(anchor), - }, + }), tx_identifier, cert_index: cert_index as u64, - })) + }) } - conway::Certificate::UnRegDRepCert(cred, coin) => Ok( - TxCertificate::DRepDeregistration(DRepDeregistrationWithPos { - cert: DRepDeregistration { - credential: map_stake_credential(cred), - refund: *coin, - }, - tx_identifier, - cert_index: cert_index as u64, + conway::Certificate::RegDRepCert(cred, coin, anchor) => Ok(TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { + credential: map_stake_credential(cred), + deposit: *coin, + anchor: map_nullable_anchor(anchor), }), - ), + tx_identifier, + cert_index: cert_index as u64, + }), - conway::Certificate::UpdateDRepCert(cred, anchor) => { - Ok(TxCertificate::DRepUpdate(DRepUpdateWithPos { - cert: DRepUpdate { - credential: map_stake_credential(cred), - anchor: map_nullable_anchor(anchor), - }, - tx_identifier, - cert_index: cert_index as u64, - })) - } + conway::Certificate::UnRegDRepCert(cred, coin) => Ok(TxCertificateWithPos { + cert: TxCertificate::DRepDeregistration(DRepDeregistration { + credential: map_stake_credential(cred), + refund: *coin, + }), + tx_identifier, + cert_index: cert_index as u64, + }), + + conway::Certificate::UpdateDRepCert(cred, anchor) => Ok(TxCertificateWithPos { + cert: TxCertificate::DRepUpdate(DRepUpdate { + credential: map_stake_credential(cred), + anchor: map_nullable_anchor(anchor), + }), + tx_identifier, + cert_index: cert_index as u64, + }), } } diff --git a/common/src/messages.rs b/common/src/messages.rs index 8ed9856e..a214977d 100644 --- a/common/src/messages.rs +++ b/common/src/messages.rs @@ -91,7 +91,7 @@ pub struct AssetDeltasMessage { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct TxCertificatesMessage { /// Ordered set of certificates - pub certificates: Vec, + pub certificates: Vec, } /// Address deltas message diff --git a/common/src/types.rs b/common/src/types.rs index 35182082..624a0d5f 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -1859,46 +1859,46 @@ pub enum TxCertificate { None(()), /// Stake registration - StakeRegistration(StakeAddressWithPos), + StakeRegistration(StakeAddress), /// Stake de-registration - StakeDeregistration(StakeAddressWithPos), + StakeDeregistration(StakeAddress), /// Stake Delegation to a pool - StakeDelegation(StakeDelegationWithPos), + StakeDelegation(StakeDelegation), /// Pool registration - PoolRegistrationWithPos(PoolRegistrationWithPos), + PoolRegistration(PoolRegistration), /// Pool retirement - PoolRetirementWithPos(PoolRetirementWithPos), + PoolRetirement(PoolRetirement), /// Genesis key delegation GenesisKeyDelegation(GenesisKeyDelegation), /// Move instantaneous rewards - MoveInstantaneousReward(MoveInstantaneousRewardWithPos), + MoveInstantaneousReward(MoveInstantaneousReward), /// New stake registration - Registration(RegistrationWithPos), + Registration(Registration), /// Stake deregistration - Deregistration(DeregistrationWithPos), + Deregistration(Deregistration), /// Vote delegation VoteDelegation(VoteDelegation), /// Combined stake and vote delegation - StakeAndVoteDelegation(StakeAndVoteDelegationWithPos), + StakeAndVoteDelegation(StakeAndVoteDelegation), /// Stake registration and SPO delegation - StakeRegistrationAndDelegation(StakeRegistrationAndDelegationWithPos), + StakeRegistrationAndDelegation(StakeRegistrationAndDelegation), /// Stake registration and vote delegation - StakeRegistrationAndVoteDelegation(StakeRegistrationAndVoteDelegationWithPos), + StakeRegistrationAndVoteDelegation(StakeRegistrationAndVoteDelegation), /// Stake registration and combined SPO and vote delegation - StakeRegistrationAndStakeAndVoteDelegation(StakeRegistrationAndStakeAndVoteDelegationWithPos), + StakeRegistrationAndStakeAndVoteDelegation(StakeRegistrationAndStakeAndVoteDelegation), /// Authorise a committee hot credential AuthCommitteeHot(AuthCommitteeHot), @@ -1907,13 +1907,20 @@ pub enum TxCertificate { ResignCommitteeCold(ResignCommitteeCold), /// DRep registration - DRepRegistration(DRepRegistrationWithPos), + DRepRegistration(DRepRegistration), /// DRep deregistration - DRepDeregistration(DRepDeregistrationWithPos), + DRepDeregistration(DRepDeregistration), /// DRep update - DRepUpdate(DRepUpdateWithPos), + DRepUpdate(DRepUpdate), +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct TxCertificateWithPos { + pub cert: TxCertificate, + pub tx_identifier: TxIdentifier, + pub cert_index: u64, } #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] diff --git a/modules/accounts_state/src/state.rs b/modules/accounts_state/src/state.rs index 8d0c61f2..9e7a1451 100644 --- a/modules/accounts_state/src/state.rs +++ b/modules/accounts_state/src/state.rs @@ -875,32 +875,29 @@ impl State { pub fn handle_tx_certificates(&mut self, tx_certs_msg: &TxCertificatesMessage) -> Result<()> { // Handle certificates for tx_cert in tx_certs_msg.certificates.iter() { - match tx_cert { + match &tx_cert.cert { TxCertificate::StakeRegistration(reg) => { - self.register_stake_address(®.stake_address, None); + self.register_stake_address(®, None); } TxCertificate::StakeDeregistration(dreg) => { - self.deregister_stake_address(&dreg.stake_address, None); + self.deregister_stake_address(&dreg, None); } TxCertificate::MoveInstantaneousReward(mir) => { - self.handle_mir(&mir.cert).unwrap_or_else(|e| error!("MIR failed: {e:#}")); + self.handle_mir(&mir).unwrap_or_else(|e| error!("MIR failed: {e:#}")); } TxCertificate::Registration(reg) => { - self.register_stake_address(®.cert.stake_address, Some(reg.cert.deposit)); + self.register_stake_address(®.stake_address, Some(reg.deposit)); } TxCertificate::Deregistration(dreg) => { - self.deregister_stake_address(&dreg.cert.stake_address, Some(dreg.cert.refund)); + self.deregister_stake_address(&dreg.stake_address, Some(dreg.refund)); } TxCertificate::StakeDelegation(delegation) => { - self.record_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - ); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); } TxCertificate::VoteDelegation(delegation) => { @@ -908,51 +905,33 @@ impl State { } TxCertificate::StakeAndVoteDelegation(delegation) => { - self.record_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - ); - self.record_drep_delegation( - &delegation.cert.stake_address, - &delegation.cert.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.cert.stake_address, - Some(delegation.cert.deposit), - ); - self.record_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, + &delegation.stake_address, + Some(delegation.deposit), ); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); } TxCertificate::StakeRegistrationAndVoteDelegation(delegation) => { self.register_stake_address( - &delegation.cert.stake_address, - Some(delegation.cert.deposit), - ); - self.record_drep_delegation( - &delegation.cert.stake_address, - &delegation.cert.drep, + &delegation.stake_address, + Some(delegation.deposit), ); + self.record_drep_delegation(&delegation.stake_address, &delegation.drep); } TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(delegation) => { self.register_stake_address( - &delegation.cert.stake_address, - Some(delegation.cert.deposit), - ); - self.record_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - ); - self.record_drep_delegation( - &delegation.cert.stake_address, - &delegation.cert.drep, + &delegation.stake_address, + Some(delegation.deposit), ); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); + self.record_drep_delegation(&delegation.stake_address, &delegation.drep); } _ => (), @@ -1013,11 +992,10 @@ mod tests { use acropolis_common::{ protocol_params::ConwayParams, rational_number::RationalNumber, Anchor, Committee, Constitution, CostModel, DRepVotingThresholds, NetworkId, PoolVotingThresholds, Pot, - PotDelta, Ratio, Registration, RegistrationWithPos, StakeAddress, StakeAddressDelta, - StakeAndVoteDelegation, StakeAndVoteDelegationWithPos, StakeCredential, - StakeRegistrationAndStakeAndVoteDelegation, - StakeRegistrationAndStakeAndVoteDelegationWithPos, StakeRegistrationAndVoteDelegation, - StakeRegistrationAndVoteDelegationWithPos, TxIdentifier, VoteDelegation, Withdrawal, + PotDelta, Ratio, Registration, StakeAddress, StakeAddressDelta, StakeAndVoteDelegation, + StakeCredential, StakeRegistrationAndStakeAndVoteDelegation, + StakeRegistrationAndVoteDelegation, TxCertificateWithPos, TxIdentifier, VoteDelegation, + Withdrawal, }; // Helper to create a StakeAddress from a byte slice @@ -1390,57 +1368,66 @@ mod tests { let spo3 = create_address(&[0x03]); let spo4 = create_address(&[0x04]); + let tx_identifier = TxIdentifier::default(); + let certificates = vec![ // register the first two SPOs separately from their delegation - TxCertificate::Registration(RegistrationWithPos { - cert: Registration { + TxCertificateWithPos { + cert: TxCertificate::Registration(Registration { stake_address: spo1.clone(), deposit: 1, - }, - tx_identifier: TxIdentifier::default(), + }), + tx_identifier, cert_index: 0, - }), - TxCertificate::Registration(RegistrationWithPos { - cert: Registration { + }, + TxCertificateWithPos { + cert: TxCertificate::Registration(Registration { stake_address: spo2.clone(), deposit: 1, - }, - tx_identifier: TxIdentifier::default(), + }), + tx_identifier, cert_index: 0, - }), - TxCertificate::VoteDelegation(VoteDelegation { - stake_address: spo1.clone(), - drep: DRepChoice::Key(DREP_HASH.to_vec()), - }), - TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegationWithPos { - cert: StakeAndVoteDelegation { + }, + TxCertificateWithPos { + cert: TxCertificate::VoteDelegation(VoteDelegation { + stake_address: spo1.clone(), + drep: DRepChoice::Key(DREP_HASH.to_vec()), + }), + tx_identifier, + cert_index: 0, + }, + TxCertificateWithPos { + cert: TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation { stake_address: spo2.clone(), operator: spo1.get_hash().to_vec(), drep: DRepChoice::Script(DREP_HASH.to_vec()), - }, - tx_identifier: TxIdentifier::default(), - }), - TxCertificate::StakeRegistrationAndVoteDelegation( - StakeRegistrationAndVoteDelegationWithPos { - cert: StakeRegistrationAndVoteDelegation { + }), + tx_identifier, + cert_index: 0, + }, + TxCertificateWithPos { + cert: TxCertificate::StakeRegistrationAndVoteDelegation( + StakeRegistrationAndVoteDelegation { stake_address: spo3.clone(), drep: DRepChoice::Abstain, deposit: 1, }, - tx_identifier: TxIdentifier::default(), - }, - ), - TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( - StakeRegistrationAndStakeAndVoteDelegationWithPos { - cert: StakeRegistrationAndStakeAndVoteDelegation { + ), + tx_identifier, + cert_index: 0, + }, + TxCertificateWithPos { + cert: TxCertificate::StakeRegistrationAndStakeAndVoteDelegation( + StakeRegistrationAndStakeAndVoteDelegation { stake_address: spo4.clone(), operator: spo1.get_hash().to_vec(), drep: DRepChoice::NoConfidence, deposit: 1, }, - tx_identifier: TxIdentifier::default(), - }, - ), + ), + tx_identifier, + cert_index: 0, + }, ]; state.handle_tx_certificates(&TxCertificatesMessage { certificates })?; diff --git a/modules/drep_state/src/state.rs b/modules/drep_state/src/state.rs index 6381d34c..4d561e1a 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, Lovelace, StakeAddress, TxCertificate, TxHash, Voter, - VotingProcedures, + Anchor, DRepChoice, DRepCredential, Lovelace, StakeAddress, TxCertificate, + TxCertificateWithPos, TxHash, Voter, VotingProcedures, }; use anyhow::{anyhow, Result}; use caryatid_sdk::Context; @@ -220,7 +220,7 @@ impl State { pub async fn process_certificates( &mut self, context: Arc>, - tx_certs: &Vec, + tx_certs: &Vec, epoch: u64, ) -> Result<()> { let mut batched_delegators = Vec::new(); @@ -228,7 +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.cert) { batched_delegators.push((delegator, drep)); continue; } @@ -317,34 +317,34 @@ impl State { distribution } - fn process_one_cert(&mut self, tx_cert: &TxCertificate, epoch: u64) -> Result { - match tx_cert { + fn process_one_cert(&mut self, tx_cert: &TxCertificateWithPos, epoch: u64) -> Result { + match &tx_cert.cert { TxCertificate::DRepRegistration(reg) => { - let new = match self.dreps.get_mut(®.cert.credential) { + let new = match self.dreps.get_mut(®.credential) { Some(drep) => { - if reg.cert.deposit != 0 { + if reg.deposit != 0 { return Err(anyhow!( "DRep registration {:?}: replacement requires deposit = 0, got {}", - reg.cert.credential, - reg.cert.deposit + reg.credential, + reg.deposit )); } - drep.anchor = reg.cert.anchor.clone(); + drep.anchor = reg.anchor.clone(); false } None => { self.dreps.insert( - reg.cert.credential.clone(), - DRepRecord::new(reg.cert.deposit, reg.cert.anchor.clone()), + reg.credential.clone(), + DRepRecord::new(reg.deposit, reg.anchor.clone()), ); true } }; if self.historical_dreps.is_some() { - if let Err(err) = self.update_historical(®.cert.credential, true, |entry| { + if let Err(err) = self.update_historical(®.credential, true, |entry| { if let Some(info) = entry.info.as_mut() { - info.deposit = reg.cert.deposit; + info.deposit = reg.deposit; info.expired = false; info.retired = false; info.active_epoch = Some(epoch); @@ -352,12 +352,12 @@ impl State { } if let Some(updates) = entry.updates.as_mut() { updates.push(DRepUpdateEvent { - tx_identifier: reg.tx_identifier, - cert_index: reg.cert_index, + tx_identifier: tx_cert.tx_identifier, + cert_index: tx_cert.cert_index, action: DRepActionUpdate::Registered, }); } - if let Some(anchor) = ®.cert.anchor { + if let Some(anchor) = ®.anchor { if let Some(inner) = entry.metadata.as_mut() { *inner = Some(anchor.clone()); } @@ -372,16 +372,16 @@ impl State { TxCertificate::DRepDeregistration(reg) => { // Update live state - if self.dreps.remove(®.cert.credential).is_none() { + if self.dreps.remove(®.credential).is_none() { return Err(anyhow!( "DRep deregistration {:?}: credential not found", - reg.cert.credential + reg.credential )); } // Update history if enabled if self.historical_dreps.is_some() { - if let Err(err) = self.update_historical(®.cert.credential, false, |entry| { + if let Err(err) = self.update_historical(®.credential, false, |entry| { if let Some(info) = entry.info.as_mut() { info.deposit = 0; info.expired = false; @@ -391,8 +391,8 @@ impl State { } if let Some(updates) = entry.updates.as_mut() { updates.push(DRepUpdateEvent { - tx_identifier: reg.tx_identifier, - cert_index: reg.cert_index, + tx_identifier: tx_cert.tx_identifier, + cert_index: tx_cert.cert_index, action: DRepActionUpdate::Deregistered, }); } @@ -406,16 +406,13 @@ impl State { TxCertificate::DRepUpdate(reg) => { // Update live state - let drep = self.dreps.get_mut(®.cert.credential).ok_or_else(|| { - anyhow!( - "DRep update {:?}: credential not found", - reg.cert.credential - ) + let drep = self.dreps.get_mut(®.credential).ok_or_else(|| { + anyhow!("DRep update {:?}: credential not found", reg.credential) })?; - drep.anchor = reg.cert.anchor.clone(); + drep.anchor = reg.anchor.clone(); // Update history if enabled - if let Err(err) = self.update_historical(®.cert.credential, false, |entry| { + if let Err(err) = self.update_historical(®.credential, false, |entry| { if let Some(info) = entry.info.as_mut() { info.expired = false; info.retired = false; @@ -423,12 +420,12 @@ impl State { } if let Some(updates) = entry.updates.as_mut() { updates.push(DRepUpdateEvent { - tx_identifier: reg.tx_identifier, - cert_index: reg.cert_index, + tx_identifier: tx_cert.tx_identifier, + cert_index: tx_cert.cert_index, action: DRepActionUpdate::Updated, }); } - if let Some(anchor) = ®.cert.anchor { + if let Some(anchor) = ®.anchor { if let Some(inner) = entry.metadata.as_mut() { *inner = Some(anchor.clone()); } @@ -548,12 +545,12 @@ impl State { fn extract_delegation_fields(cert: &TxCertificate) -> Option<(&StakeAddress, &DRepChoice)> { match cert { TxCertificate::VoteDelegation(d) => Some((&d.stake_address, &d.drep)), - TxCertificate::StakeAndVoteDelegation(d) => Some((&d.cert.stake_address, &d.cert.drep)), + TxCertificate::StakeAndVoteDelegation(d) => Some((&d.stake_address, &d.drep)), TxCertificate::StakeRegistrationAndVoteDelegation(d) => { - Some((&d.cert.stake_address, &d.cert.drep)) + Some((&d.stake_address, &d.drep)) } TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(d) => { - Some((&d.cert.stake_address, &d.cert.drep)) + Some((&d.stake_address, &d.drep)) } _ => None, } @@ -572,8 +569,8 @@ fn drep_choice_to_credential(choice: &DRepChoice) -> Option { mod tests { use crate::state::{DRepRecord, DRepStorageConfig, State}; use acropolis_common::{ - Anchor, Credential, DRepDeregistration, DRepDeregistrationWithPos, DRepRegistration, - DRepRegistrationWithPos, DRepUpdate, DRepUpdateWithPos, TxCertificate, TxIdentifier, + Anchor, Credential, DRepDeregistration, DRepRegistration, DRepUpdate, TxCertificate, + TxCertificateWithPos, TxIdentifier, }; const CRED_1: [u8; 28] = [ @@ -588,15 +585,15 @@ mod tests { #[test] fn test_drep_process_one_certificate() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); - let tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - cert: DRepRegistration { + let tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, - }, + }), tx_identifier: TxIdentifier::default(), - cert_index: 1, - }); + cert_index: 0, + }; let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); assert_eq!(state.get_count(), 1); @@ -613,27 +610,28 @@ mod tests { #[test] fn test_drep_do_not_replace_existing_certificate() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); - let tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - cert: DRepRegistration { + let tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, - }, + }), tx_identifier: TxIdentifier::default(), - cert_index: 1, - }); + cert_index: 0, + }; + let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); - let bad_tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - cert: DRepRegistration { + let bad_tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { credential: tx_cred.clone(), deposit: 600000000, anchor: None, - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; assert!(state.process_one_cert(&bad_tx_cert, 1).is_err()); assert_eq!(state.get_count(), 1); @@ -650,15 +648,15 @@ mod tests { #[test] fn test_drep_update_certificate() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); - let tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - cert: DRepRegistration { + let tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); @@ -666,14 +664,14 @@ mod tests { url: "https://poop.bike".into(), data_hash: vec![0x13, 0x37], }; - let update_anchor_tx_cert = TxCertificate::DRepUpdate(DRepUpdateWithPos { - cert: DRepUpdate { + let update_anchor_tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepUpdate(DRepUpdate { credential: tx_cred.clone(), anchor: Some(anchor.clone()), - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; assert_eq!( state.process_one_cert(&update_anchor_tx_cert, 1).unwrap(), @@ -694,15 +692,15 @@ mod tests { #[test] fn test_drep_do_not_update_nonexistent_certificate() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); - let tx_cert = TxCertificate::DRepRegistration(DRepRegistrationWithPos { - cert: DRepRegistration { + let tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); @@ -710,14 +708,14 @@ mod tests { url: "https://poop.bike".into(), data_hash: vec![0x13, 0x37], }; - let update_anchor_tx_cert = TxCertificate::DRepUpdate(DRepUpdateWithPos { - cert: DRepUpdate { + let update_anchor_tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepUpdate(DRepUpdate { credential: Credential::AddrKeyHash(CRED_2.to_vec()), anchor: Some(anchor.clone()), - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; assert!(state.process_one_cert(&update_anchor_tx_cert, 1).is_err()); assert_eq!(state.get_count(), 1); @@ -734,26 +732,26 @@ mod tests { #[test] fn test_drep_deregister() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); - let tx_cert = TxCertificate::DRepRegistration(acropolis_common::DRepRegistrationWithPos { - cert: DRepRegistration { + let tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); - let unregister_tx_cert = TxCertificate::DRepDeregistration(DRepDeregistrationWithPos { - cert: DRepDeregistration { + let unregister_tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepDeregistration(DRepDeregistration { credential: tx_cred.clone(), refund: 500000000, - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; assert_eq!( state.process_one_cert(&unregister_tx_cert, 1).unwrap(), true @@ -765,26 +763,26 @@ mod tests { #[test] fn test_drep_do_not_deregister_nonexistent_cert() { let tx_cred = Credential::AddrKeyHash(CRED_1.to_vec()); - let tx_cert = TxCertificate::DRepRegistration(acropolis_common::DRepRegistrationWithPos { - cert: DRepRegistration { + let tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepRegistration(DRepRegistration { credential: tx_cred.clone(), deposit: 500000000, anchor: None, - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; let mut state = State::new(DRepStorageConfig::default()); assert_eq!(state.process_one_cert(&tx_cert, 1).unwrap(), true); - let unregister_tx_cert = TxCertificate::DRepDeregistration(DRepDeregistrationWithPos { - cert: DRepDeregistration { + let unregister_tx_cert = TxCertificateWithPos { + cert: TxCertificate::DRepDeregistration(DRepDeregistration { credential: Credential::AddrKeyHash(CRED_2.to_vec()), refund: 500000000, - }, + }), tx_identifier: TxIdentifier::default(), cert_index: 1, - }); + }; assert!(state.process_one_cert(&unregister_tx_cert, 1).is_err()); assert_eq!(state.get_count(), 1); assert_eq!(state.get_drep(&tx_cred).unwrap().deposit, 500000000); diff --git a/modules/historical_accounts_state/src/state.rs b/modules/historical_accounts_state/src/state.rs index f26583b4..96f1fbdf 100644 --- a/modules/historical_accounts_state/src/state.rs +++ b/modules/historical_accounts_state/src/state.rs @@ -127,19 +127,19 @@ impl State { ) -> Result<()> { // Handle certificates for tx_cert in tx_certs.certificates.iter() { - match tx_cert { + match &tx_cert.cert { // Pre-Conway stake registration/deregistration certs - TxCertificate::StakeRegistration(sc) => { + TxCertificate::StakeRegistration(stake_address) => { self.handle_stake_registration_change( - &sc.stake_address, - &sc.tx_identifier, + &stake_address, + &tx_cert.tx_identifier, RegistrationStatus::Registered, ); } - TxCertificate::StakeDeregistration(sc) => { + TxCertificate::StakeDeregistration(stake_address) => { self.handle_stake_registration_change( - &sc.stake_address, - &sc.tx_identifier, + &stake_address, + &tx_cert.tx_identifier, RegistrationStatus::Deregistered, ); } @@ -147,15 +147,15 @@ impl State { // Post-Conway stake registration/deregistration certs TxCertificate::Registration(reg) => { self.handle_stake_registration_change( - ®.cert.stake_address, - ®.tx_identifier, + ®.stake_address, + &tx_cert.tx_identifier, RegistrationStatus::Registered, ); } TxCertificate::Deregistration(dreg) => { self.handle_stake_registration_change( - &dreg.cert.stake_address, - &dreg.tx_identifier, + &dreg.stake_address, + &tx_cert.tx_identifier, RegistrationStatus::Deregistered, ); } @@ -163,27 +163,27 @@ impl State { // Registration and delegation certs TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(delegation) => { self.handle_stake_registration_change( - &delegation.cert.stake_address, - &delegation.tx_identifier, + &delegation.stake_address, + &tx_cert.tx_identifier, RegistrationStatus::Registered, ); self.handle_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - &delegation.tx_identifier, + &delegation.stake_address, + &delegation.operator, + &tx_cert.tx_identifier, epoch, ); } TxCertificate::StakeRegistrationAndDelegation(delegation) => { self.handle_stake_registration_change( - &delegation.cert.stake_address, - &delegation.tx_identifier, + &delegation.stake_address, + &tx_cert.tx_identifier, RegistrationStatus::Registered, ); self.handle_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - &delegation.tx_identifier, + &delegation.stake_address, + &delegation.operator, + &tx_cert.tx_identifier, epoch, ); } @@ -191,31 +191,31 @@ impl State { // Delegation certs TxCertificate::StakeDelegation(delegation) => { self.handle_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - &delegation.tx_identifier, + &delegation.stake_address, + &delegation.operator, + &tx_cert.tx_identifier, epoch, ); } TxCertificate::StakeAndVoteDelegation(delegation) => { self.handle_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - &delegation.tx_identifier, + &delegation.stake_address, + &delegation.operator, + &tx_cert.tx_identifier, epoch, ); } TxCertificate::StakeRegistrationAndVoteDelegation(delegation) => { self.handle_stake_registration_change( - &delegation.cert.stake_address, - &delegation.tx_identifier, + &delegation.stake_address, + &tx_cert.tx_identifier, RegistrationStatus::Registered, ); } // MIR certs TxCertificate::MoveInstantaneousReward(mir) => { - self.handle_mir(&mir.cert.target, &mir.tx_identifier); + self.handle_mir(&mir.target, &tx_cert.tx_identifier); } _ => (), diff --git a/modules/spo_state/src/state.rs b/modules/spo_state/src/state.rs index 4c0c8b64..b1ab81dc 100644 --- a/modules/spo_state/src/state.rs +++ b/modules/spo_state/src/state.rs @@ -10,9 +10,8 @@ use acropolis_common::{ params::TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH, queries::governance::VoteRecord, stake_addresses::StakeAddressMap, - BlockInfo, KeyHash, PoolMetadata, PoolRegistration, PoolRegistrationWithPos, PoolRetirement, - PoolRetirementWithPos, PoolUpdateEvent, Relay, StakeAddress, TxCertificate, TxHash, Voter, - VotingProcedures, + BlockInfo, KeyHash, PoolMetadata, PoolRegistration, PoolRetirement, PoolUpdateEvent, Relay, + StakeAddress, TxCertificate, TxHash, TxIdentifier, Voter, VotingProcedures, }; use anyhow::Result; use imbl::HashMap; @@ -325,40 +324,36 @@ impl State { fn handle_pool_registration( &mut self, block: &BlockInfo, - reg_with_pos: &PoolRegistrationWithPos, + reg: &PoolRegistration, + tx_identifier: &TxIdentifier, + cert_index: &u64, ) { - let PoolRegistrationWithPos { - cert, - tx_identifier, - cert_index, - } = reg_with_pos; - - if self.spos.contains_key(&cert.operator) { + if self.spos.contains_key(®.operator) { debug!( block = block.number, "New pending SPO update {} {:?}", - hex::encode(&cert.operator), - cert + hex::encode(®.operator), + reg ); - self.pending_updates.insert(cert.operator.clone(), cert.clone()); + self.pending_updates.insert(reg.operator.clone(), reg.clone()); } else { debug!( block = block.number, "Registering SPO {} {:?}", - hex::encode(&cert.operator), - cert + hex::encode(®.operator), + reg ); - self.spos.insert(cert.operator.clone(), cert.clone()); + self.spos.insert(reg.operator.clone(), reg.clone()); } // Remove any existing queued deregistrations for (epoch, deregistrations) in &mut self.pending_deregistrations.iter_mut() { let old_len = deregistrations.len(); - deregistrations.retain(|d| *d != cert.operator); + deregistrations.retain(|d| *d != reg.operator); if deregistrations.len() != old_len { debug!( "Removed pending deregistration of SPO {} from epoch {}", - hex::encode(&cert.operator), + hex::encode(®.operator), epoch ); } @@ -369,9 +364,9 @@ impl State { // Don't check there was registration already or not // because we don't remove registration when pool is retired. let historical_spo = historical_spos - .entry(cert.operator.clone()) + .entry(reg.operator.clone()) .or_insert_with(|| HistoricalSPOState::new(&self.store_config)); - historical_spo.add_pool_registration(cert); + historical_spo.add_pool_registration(reg); historical_spo.add_pool_updates(PoolUpdateEvent::register_event( tx_identifier.clone(), *cert_index, @@ -379,44 +374,45 @@ impl State { } } - fn handle_pool_retirement(&mut self, block: &BlockInfo, ret_with_pos: &PoolRetirementWithPos) { - let PoolRetirementWithPos { - cert, - tx_identifier, - cert_index, - } = ret_with_pos; + fn handle_pool_retirement( + &mut self, + block: &BlockInfo, + ret: &PoolRetirement, + tx_identifier: &TxIdentifier, + cert_index: &u64, + ) { debug!( "SPO {} wants to retire at the end of epoch {} (cert in block number {})", - hex::encode(&cert.operator), - cert.epoch, + hex::encode(&ret.operator), + ret.epoch, block.number ); - if cert.epoch <= self.epoch { + if ret.epoch <= self.epoch { error!( "SPO retirement received for current or past epoch {} for SPO {}", - cert.epoch, - hex::encode(&cert.operator) + ret.epoch, + hex::encode(&ret.operator) ); - } else if cert.epoch > self.epoch + TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH { + } else if ret.epoch > self.epoch + TECHNICAL_PARAMETER_POOL_RETIRE_MAX_EPOCH { error!( "SPO retirement received for epoch {} that exceeds future limit for SPO {}", - cert.epoch, - hex::encode(&cert.operator) + ret.epoch, + hex::encode(&ret.operator) ); } else { // Replace any existing queued deregistrations for (epoch, deregistrations) in &mut self.pending_deregistrations.iter_mut() { let old_len = deregistrations.len(); - deregistrations.retain(|d| *d != cert.operator); + deregistrations.retain(|d| *d != ret.operator); if deregistrations.len() != old_len { debug!( "Replaced pending deregistration of SPO {} from epoch {}", - hex::encode(&cert.operator), + hex::encode(&ret.operator), epoch ); } } - self.pending_deregistrations.entry(cert.epoch).or_default().push(cert.operator.clone()); + self.pending_deregistrations.entry(ret.epoch).or_default().push(ret.operator.clone()); // Note: not removing pending updates - the deregistation may happen many // epochs later than the update, and we apply updates before deregistrations @@ -425,13 +421,13 @@ impl State { // update historical spos if let Some(historical_spos) = self.historical_spos.as_mut() { - if let Some(historical_spo) = historical_spos.get_mut(&cert.operator) { + if let Some(historical_spo) = historical_spos.get_mut(&ret.operator) { historical_spo .add_pool_updates(PoolUpdateEvent::retire_event(*tx_identifier, *cert_index)); } else { error!( "Historical SPO for {} not registered when try to retire it", - hex::encode(&cert.operator) + hex::encode(&ret.operator) ); } } @@ -538,60 +534,58 @@ impl State { } // Handle certificates for tx_cert in tx_certs_msg.certificates.iter() { - match tx_cert { + match &tx_cert.cert { // for spo_state - TxCertificate::PoolRegistrationWithPos(reg_with_pos) => { - self.handle_pool_registration(block, reg_with_pos); + TxCertificate::PoolRegistration(reg) => { + self.handle_pool_registration( + block, + ®, + &tx_cert.tx_identifier, + &tx_cert.cert_index, + ); } - TxCertificate::PoolRetirementWithPos(ret_with_pos) => { - self.handle_pool_retirement(block, ret_with_pos); + TxCertificate::PoolRetirement(ret) => { + self.handle_pool_retirement( + block, + &ret, + &tx_cert.tx_identifier, + &tx_cert.cert_index, + ); } // for stake addresses - TxCertificate::StakeRegistration(stake_address_with_pos) => { - self.register_stake_address(&stake_address_with_pos.stake_address); + TxCertificate::StakeRegistration(stake_address) => { + self.register_stake_address(&stake_address); } TxCertificate::StakeDeregistration(stake_address) => { - self.deregister_stake_address(&stake_address.stake_address); + self.deregister_stake_address(&stake_address); } TxCertificate::Registration(reg) => { - self.register_stake_address(®.cert.stake_address); + self.register_stake_address(®.stake_address); // we don't care deposite } TxCertificate::Deregistration(dreg) => { - self.deregister_stake_address(&dreg.cert.stake_address); + self.deregister_stake_address(&dreg.stake_address); // we don't care refund } TxCertificate::StakeDelegation(delegation) => { - self.record_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - ); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); } TxCertificate::StakeAndVoteDelegation(delegation) => { - self.record_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - ); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); // don't care about vote delegation } TxCertificate::StakeRegistrationAndDelegation(delegation) => { - self.register_stake_address(&delegation.cert.stake_address); - self.record_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.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.cert.stake_address); + self.register_stake_address(&delegation.stake_address); // don't care about vote delegation } TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(delegation) => { - self.register_stake_address(&delegation.cert.stake_address); - self.record_stake_delegation( - &delegation.cert.stake_address, - &delegation.cert.operator, - ); + self.register_stake_address(&delegation.stake_address); + self.record_stake_delegation(&delegation.stake_address, &delegation.operator); // don't care about vote delegation } _ => (), @@ -693,7 +687,7 @@ mod tests { use crate::test_utils::*; use acropolis_common::{ state_history::{StateHistory, StateHistoryStore}, - PoolRetirement, Ratio, StakeAddress, TxCertificate, TxIdentifier, + PoolRetirement, Ratio, StakeAddress, TxCertificate, TxCertificateWithPos, TxIdentifier, }; use tokio::sync::Mutex; @@ -742,13 +736,11 @@ mod tests { async fn spo_gets_registered() { let mut state = State::default(); let mut msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRegistrationWithPos( - PoolRegistrationWithPos { - cert: default_pool_registration(vec![0], None), - tx_identifier: TxIdentifier::default(), - cert_index: 1, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(default_pool_registration(vec![0], None)), + tx_identifier: TxIdentifier::default(), + cert_index: 1, + }); let block = new_block(1); assert!(state.handle_tx_certs(&block, &msg).is_ok()); assert_eq!(1, state.spos.len()); @@ -760,16 +752,14 @@ mod tests { async fn pending_deregistration_gets_queued() { let mut state = State::default(); let mut msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![0], - epoch: 1, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![0], + epoch: 1, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); let block = new_block(0); assert!(state.handle_tx_certs(&block, &msg).is_ok()); assert_eq!(1, state.pending_deregistrations.len()); @@ -786,30 +776,26 @@ mod tests { let mut state = State::default(); let mut block = new_block(0); let mut msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![0], - epoch: 2, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![0], + epoch: 2, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); block.number = 1; msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![1], - epoch: 2, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![1], + epoch: 2, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); assert_eq!(1, state.pending_deregistrations.len()); @@ -831,32 +817,28 @@ mod tests { let mut state = history.lock().await.get_current_state(); let mut block = new_block(0); let mut msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![0], - epoch: 2, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![0], + epoch: 2, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); history.lock().await.commit(block.number, state); let mut state = history.lock().await.get_current_state(); block.number = 1; msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![1], - epoch: 2, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![1], + epoch: 2, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); history.lock().await.commit(block.number, state); @@ -878,13 +860,11 @@ mod tests { let mut state = State::default(); let mut block = new_block(0); let mut msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRegistrationWithPos( - PoolRegistrationWithPos { - cert: default_pool_registration(vec![0], None), - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(default_pool_registration(vec![0], None)), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); assert_eq!(1, state.spos.len()); @@ -893,16 +873,14 @@ mod tests { block.number = 1; let mut msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![0], - epoch: 1, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![0], + epoch: 1, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); block.epoch = 1; // SPO get retired at the start of the epoch it requests @@ -921,13 +899,11 @@ mod tests { let mut state = history.lock().await.get_current_state(); let mut block = new_block(0); let mut msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRegistrationWithPos( - PoolRegistrationWithPos { - cert: default_pool_registration(vec![0], None), - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(default_pool_registration(vec![0], None)), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); assert_eq!(1, state.spos.len()); let spo = state.spos.get(&vec![0u8]); @@ -937,16 +913,14 @@ mod tests { let mut state = history.lock().await.get_current_state(); block.number = 1; msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![0], - epoch: 1, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![0], + epoch: 1, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); history.lock().await.commit(block.number, state); @@ -979,30 +953,26 @@ mod tests { let mut state = State::default(); let mut block = new_block(0); let mut msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![0], - epoch: 2, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![0], + epoch: 2, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); block.number = 1; msg = new_certs_msg(); - msg.certificates.push(TxCertificate::PoolRetirementWithPos( - PoolRetirementWithPos { - cert: PoolRetirement { - operator: vec![1], - epoch: 3, - }, - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRetirement(PoolRetirement { + operator: vec![1], + epoch: 3, + }), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); let mut retiring_pools = state.get_retiring_pools(); retiring_pools.sort_by_key(|p| p.epoch); @@ -1026,13 +996,11 @@ mod tests { let mut block = new_block(0); let mut msg = new_certs_msg(); let spo_id = keyhash_224(&vec![1 as u8]); - msg.certificates.push(TxCertificate::PoolRegistrationWithPos( - PoolRegistrationWithPos { - cert: default_pool_registration(spo_id.clone(), None), - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(default_pool_registration(spo_id.clone(), None)), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); block = new_block(2); @@ -1063,13 +1031,11 @@ mod tests { let mut block = new_block(0); let mut msg = new_certs_msg(); let spo_id = keyhash_224(&vec![1 as u8]); - msg.certificates.push(TxCertificate::PoolRegistrationWithPos( - PoolRegistrationWithPos { - cert: default_pool_registration(spo_id.clone(), None), - tx_identifier: TxIdentifier::default(), - cert_index: 0, - }, - )); + msg.certificates.push(TxCertificateWithPos { + cert: TxCertificate::PoolRegistration(default_pool_registration(spo_id.clone(), None)), + tx_identifier: TxIdentifier::default(), + cert_index: 0, + }); assert!(state.handle_tx_certs(&block, &msg).is_ok()); block = new_block(2); assert_eq!(true, state.handle_mint(&block, &vec![1])); // Note raw issuer_vkey diff --git a/modules/spo_state/src/test_utils.rs b/modules/spo_state/src/test_utils.rs index 2e8772d8..8babf02f 100644 --- a/modules/spo_state/src/test_utils.rs +++ b/modules/spo_state/src/test_utils.rs @@ -2,7 +2,7 @@ use acropolis_common::{ messages::{ EpochActivityMessage, SPORewardsMessage, SPOStakeDistributionMessage, TxCertificatesMessage, }, - BlockHash, BlockInfo, BlockStatus, Era, TxCertificate, + BlockHash, BlockInfo, BlockStatus, Era, TxCertificateWithPos, }; use crate::store_config::StoreConfig; @@ -75,7 +75,7 @@ pub fn new_block(epoch: u64) -> BlockInfo { pub fn new_certs_msg() -> TxCertificatesMessage { TxCertificatesMessage { - certificates: Vec::::new(), + certificates: Vec::::new(), } } diff --git a/modules/stake_delta_filter/src/state.rs b/modules/stake_delta_filter/src/state.rs index f048e6ee..c898ba0a 100644 --- a/modules/stake_delta_filter/src/state.rs +++ b/modules/stake_delta_filter/src/state.rs @@ -83,16 +83,16 @@ impl State { block: &BlockInfo, msg: &TxCertificatesMessage, ) -> Result<()> { - for cert in msg.certificates.iter() { - if let TxCertificate::StakeRegistration(reg) = cert { + for tx_cert in msg.certificates.iter() { + if let TxCertificate::StakeRegistration(stake_address) = &tx_cert.cert { let ptr = ShelleyAddressPointer { slot: block.slot, - tx_index: reg.tx_index, - cert_index: reg.cert_index, + tx_index: tx_cert.tx_identifier.tx_index() as u64, + cert_index: tx_cert.cert_index, }; // Sets pointer; updates max processed slot - self.pointer_cache.set_pointer(ptr, reg.stake_address.clone(), block.slot); + self.pointer_cache.set_pointer(ptr, stake_address.clone(), block.slot); } } Ok(()) diff --git a/modules/tx_unpacker/src/tx_unpacker.rs b/modules/tx_unpacker/src/tx_unpacker.rs index 3be139ce..7111ca3c 100644 --- a/modules/tx_unpacker/src/tx_unpacker.rs +++ b/modules/tx_unpacker/src/tx_unpacker.rs @@ -269,7 +269,7 @@ impl TxUnpacker { if publish_certificates_topic.is_some() { for ( cert_index, cert) in certs.iter().enumerate() { - match map_parameters::map_certificate(&cert, tx_identifier, tx_index, cert_index, network_id.clone()) { + match map_parameters::map_certificate(&cert, tx_identifier, cert_index, network_id.clone()) { Ok(tx_cert) => { certificates.push(tx_cert); }, From 0f76582928db468985d42187601813d15bac8f1c Mon Sep 17 00:00:00 2001 From: William Hankins Date: Mon, 27 Oct 2025 21:40:00 +0000 Subject: [PATCH 6/6] fix: remove previous xxxWithPosition type definitions Signed-off-by: William Hankins --- common/src/types.rs | 95 --------------------------------------------- 1 file changed, 95 deletions(-) diff --git a/common/src/types.rs b/common/src/types.rs index 624a0d5f..3b6267d2 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -808,14 +808,6 @@ pub struct PoolMetadata { pub hash: DataHash, } -/// Pool registration with position -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PoolRegistrationWithPos { - pub cert: PoolRegistration, - pub tx_identifier: TxIdentifier, - pub cert_index: u64, -} - /// Pool registration data #[serde_as] #[derive( @@ -869,14 +861,6 @@ pub struct PoolRegistration { pub pool_metadata: Option, } -// Pool Retirment with position -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PoolRetirementWithPos { - pub cert: PoolRetirement, - pub tx_identifier: TxIdentifier, - pub cert_index: u64, -} - /// Pool retirement data #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PoolRetirement { @@ -950,12 +934,6 @@ pub struct StakeDelegation { pub operator: KeyHash, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct StakeDelegationWithPos { - pub cert: StakeDelegation, - pub tx_identifier: TxIdentifier, -} - /// SPO total delegation data (for SPDD) #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq)] pub struct DelegatedStake { @@ -1016,12 +994,6 @@ pub struct MoveInstantaneousReward { pub target: InstantaneousRewardTarget, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct MoveInstantaneousRewardWithPos { - pub cert: MoveInstantaneousReward, - pub tx_identifier: TxIdentifier, -} - /// Register stake (Conway version) = 'reg_cert' #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Registration { @@ -1032,13 +1004,6 @@ pub struct Registration { pub deposit: Lovelace, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RegistrationWithPos { - pub cert: Registration, - pub tx_identifier: TxIdentifier, - pub cert_index: u64, -} - /// Deregister stake (Conway version) = 'unreg_cert' #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Deregistration { @@ -1049,13 +1014,6 @@ pub struct Deregistration { pub refund: Lovelace, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct DeregistrationWithPos { - pub cert: Deregistration, - pub tx_identifier: TxIdentifier, - pub cert_index: u64, -} - /// DRepChoice (=CDDL drep, badly named) #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub enum DRepChoice { @@ -1095,12 +1053,6 @@ pub struct StakeAndVoteDelegation { pub drep: DRepChoice, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct StakeAndVoteDelegationWithPos { - pub cert: StakeAndVoteDelegation, - pub tx_identifier: TxIdentifier, -} - /// Stake delegation to SPO + registration = stake_reg_deleg_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeRegistrationAndDelegation { @@ -1114,12 +1066,6 @@ pub struct StakeRegistrationAndDelegation { pub deposit: Lovelace, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct StakeRegistrationAndDelegationWithPos { - pub cert: StakeRegistrationAndDelegation, - pub tx_identifier: TxIdentifier, -} - /// Vote delegation to DRep + registration = vote_reg_deleg_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StakeRegistrationAndVoteDelegation { @@ -1133,12 +1079,6 @@ pub struct StakeRegistrationAndVoteDelegation { pub deposit: Lovelace, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct StakeRegistrationAndVoteDelegationWithPos { - pub cert: StakeRegistrationAndVoteDelegation, - pub tx_identifier: TxIdentifier, -} - /// All the trimmings: /// Vote delegation to DRep + Stake delegation to SPO + registration /// = stake_vote_reg_deleg_cert @@ -1157,12 +1097,6 @@ pub struct StakeRegistrationAndStakeAndVoteDelegation { pub deposit: Lovelace, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct StakeRegistrationAndStakeAndVoteDelegationWithPos { - pub cert: StakeRegistrationAndStakeAndVoteDelegation, - pub tx_identifier: TxIdentifier, -} - /// Anchor #[serde_as] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] @@ -1190,13 +1124,6 @@ pub struct DRepRegistration { pub anchor: Option, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct DRepRegistrationWithPos { - pub cert: DRepRegistration, - pub tx_identifier: TxIdentifier, - pub cert_index: u64, -} - /// DRep Deregistration = unreg_drep_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DRepDeregistration { @@ -1207,13 +1134,6 @@ pub struct DRepDeregistration { pub refund: Lovelace, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct DRepDeregistrationWithPos { - pub cert: DRepDeregistration, - pub tx_identifier: TxIdentifier, - pub cert_index: u64, -} - /// DRep Update = update_drep_cert #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DRepUpdate { @@ -1224,13 +1144,6 @@ pub struct DRepUpdate { pub anchor: Option, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct DRepUpdateWithPos { - pub cert: DRepUpdate, - pub tx_identifier: TxIdentifier, - pub cert_index: u64, -} - pub type CommitteeCredential = Credential; /// Authorise a committee hot credential @@ -1844,14 +1757,6 @@ pub struct GovernanceOutcome { pub action_to_perform: GovernanceOutcomeVariant, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct StakeAddressWithPos { - pub stake_address: StakeAddress, - pub tx_identifier: TxIdentifier, - pub tx_index: u64, - pub cert_index: u64, -} - /// Certificate in a transaction #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum TxCertificate {