Skip to content
210 changes: 210 additions & 0 deletions crates/cardano/src/hacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,29 @@ pub mod proposals {

pub fn outcome(protocol: u16, proposal: &str) -> ProposalOutcome {
match proposal {
// Committee Update enacted at epoch 1370
"06dcb60f4b6ee78024bd4c7978e8e093437903198cf06c3c5d34bf825129bc73#0" => {
Ratified(1369)
}
// Parameter Change enacted at epoch 1367
"dd4fbc61680bd7fa3cf97d815bdfaf54bc9872d2fd0c6f5e9bdb86fef0260c51#0" => {
Ratified(1366)
}
// Committee Update enacted at epoch 1361
"65c41d163aceb2be0b821b79fa71ee1ead7ecd13bd1ae812baddf5c962c9d62b#0" => {
Ratified(1360)
}
// Parameter Changes superseded at epoch 1367
"8009f3a24731320244568273a4d7eed1b436067c91aeb98bdb2872e45ef5b1d6#0"
| "8b5f096ad63618f1b73112d8ac46fb59519897cbfa318fdb9ffed2dfcee782e4#0"
| "8bd727c7aab758be609958b9bdaf84096b8f30bc7cfa0956d6a3ddc91b816550#0" => {
Canceled(1367)
}
// Committee actions superseded at epoch 1361
"1160cd45096980e1985628521aa6a84ac693e2616401dc459b3abe97a140b934#0"
| "2c3657d09e194507a4b120c3aeed4616818ad3875382ba68b4e668e6d3d5d625#0" => {
Canceled(1361)
}
// Parameter Change enacted at epoch 1270
"014c32e57347d114744210e1934a2084c5d0052a2312170d93758bfd566f3956#0" => {
Ratified(1269)
Expand Down Expand Up @@ -296,6 +319,18 @@ pub mod proposals {

pub fn outcome(protocol: u16, proposal: &str) -> ProposalOutcome {
match proposal {
// Parameter Change superseded at epoch 305
"38b9c1901e472f1318f44a324e74589466d77ff44a63cd35f034aa8998fc53aa#0" => {
Canceled(305)
}
// Parameter Change enacted at epoch 305
"e641ec802bb109e150e920c6c0387e85f2efd30944a46d08d08212bde540f69c#0" => {
Ratified(304)
}
// Committee Update enacted at epoch 304
"bbfa303aa35d19919934fb5fe36b174609a0c1dfb47a546ccd6ea58aa752e2aa#0" => {
Ratified(303)
}
// Byron intra-era hardfork
"9972ffaee13b4afcf1a133434161ce25e8ecaf34b7a76e06b0c642125cf911a9#0" => Ratified(1),
// Shelley hardfork
Expand Down Expand Up @@ -359,6 +394,30 @@ pub mod proposals {

pub fn outcome(protocol: u16, proposal: &str) -> ProposalOutcome {
match proposal {
// Treasury Withdrawal enacted at epoch 647
"d8de068952df50c862fa1bce9b8180d3387976cbae0fb2c3d9ef84f0faaf64d6#0" => {
Ratified(646)
}

// Treasury Withdrawals enacted at epoch 646
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#2"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#3"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#4"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#5"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#6" => {
Ratified(645)
}

// Treasury Withdrawals enacted at epoch 645
"fbb8d1a4a8d6b62f8cd706944a0582b884c2b90187b8fada7953d5c6a33eb5a7#0"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#1"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#7"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#8"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#9"
| "b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#10" => {
Ratified(644)
}

// Replace Interim Constitutional Committee
"47a0e7a4f9383b1afc2192b23b41824d65ac978d7741aca61fc1fa16833d1111#0" => {
Ratified(580)
Expand Down Expand Up @@ -772,4 +831,155 @@ pub mod proposals {
_ => ProposalOutcome::Unknown,
}
}

#[cfg(test)]
mod tests {
use super::{outcome, ProposalOutcome};

fn assert_ratified(magic: u32, proposal: &str, expected: u64) {
assert!(matches!(
outcome(magic, 9, proposal),
ProposalOutcome::Ratified(epoch) if epoch == expected
));
}

fn assert_canceled(magic: u32, proposal: &str, expected: u64) {
assert!(matches!(
outcome(magic, 9, proposal),
ProposalOutcome::Canceled(epoch) if epoch == expected
));
}

#[test]
fn recent_proposals_have_known_outcomes() {
let ratified = [
(
2,
"65c41d163aceb2be0b821b79fa71ee1ead7ecd13bd1ae812baddf5c962c9d62b#0",
1360,
),
(
2,
"dd4fbc61680bd7fa3cf97d815bdfaf54bc9872d2fd0c6f5e9bdb86fef0260c51#0",
1366,
),
(
2,
"06dcb60f4b6ee78024bd4c7978e8e093437903198cf06c3c5d34bf825129bc73#0",
1369,
),
(
1,
"bbfa303aa35d19919934fb5fe36b174609a0c1dfb47a546ccd6ea58aa752e2aa#0",
303,
),
(
1,
"e641ec802bb109e150e920c6c0387e85f2efd30944a46d08d08212bde540f69c#0",
304,
),
(
764824073,
"fbb8d1a4a8d6b62f8cd706944a0582b884c2b90187b8fada7953d5c6a33eb5a7#0",
644,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#1",
644,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#2",
645,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#3",
645,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#4",
645,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#5",
645,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#6",
645,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#7",
644,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#8",
644,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#9",
644,
),
(
764824073,
"b3d452bff7769d7f557ec6b8974760ee6c5e496c276652b654032966621e0ccf#10",
644,
),
(
764824073,
"d8de068952df50c862fa1bce9b8180d3387976cbae0fb2c3d9ef84f0faaf64d6#0",
646,
),
];

for (magic, proposal, expected) in ratified {
assert_ratified(magic, proposal, expected);
}

let canceled = [
(
2,
"1160cd45096980e1985628521aa6a84ac693e2616401dc459b3abe97a140b934#0",
1361,
),
(
2,
"2c3657d09e194507a4b120c3aeed4616818ad3875382ba68b4e668e6d3d5d625#0",
1361,
),
(
2,
"8009f3a24731320244568273a4d7eed1b436067c91aeb98bdb2872e45ef5b1d6#0",
1367,
),
(
2,
"8b5f096ad63618f1b73112d8ac46fb59519897cbfa318fdb9ffed2dfcee782e4#0",
1367,
),
(
2,
"8bd727c7aab758be609958b9bdaf84096b8f30bc7cfa0956d6a3ddc91b816550#0",
1367,
),
(
1,
"38b9c1901e472f1318f44a324e74589466d77ff44a63cd35f034aa8998fc53aa#0",
305,
),
];

for (magic, proposal, expected) in canceled {
assert_canceled(magic, proposal, expected);
}
}
}
}
90 changes: 90 additions & 0 deletions crates/cardano/src/model/epochs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,32 @@ pub type Lovelace = u64;

pub const CURRENT_EPOCH_KEY: &[u8] = b"0";

/// CBOR codec for `u128` fields. This minicbor version has no native `u128`
/// support, so the value is stored as a 16-byte big-endian byte string.
mod cbor_u128 {
use pallas::codec::minicbor::{
decode::{Decoder, Error as DecodeError},
encode::{Encoder, Error as EncodeError, Write},
};

pub fn encode<C, W: Write>(
v: &u128,
e: &mut Encoder<W>,
_ctx: &mut C,
) -> Result<(), EncodeError<W::Error>> {
e.bytes(&v.to_be_bytes())?;
Ok(())
}

pub fn decode<C>(d: &mut Decoder<'_>, _ctx: &mut C) -> Result<u128, DecodeError> {
let bytes = d.bytes()?;
let arr: [u8; 16] = bytes
.try_into()
.map_err(|_| DecodeError::message("expected 16-byte u128"))?;
Ok(u128::from_be_bytes(arr))
}
}

#[derive(Debug, Encode, Decode, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Nonces {
#[n(0)]
Expand Down Expand Up @@ -140,6 +166,30 @@ pub struct RollingStats {
#[n(21)]
#[cbor(default)]
pub treasury_mirs: Lovelace,

/// Number of transactions across all blocks minted this epoch.
#[n(22)]
#[cbor(default)]
pub tx_count: u64,

/// Gross sum of all produced transaction output lovelace this epoch
/// (matches db-sync's `epoch.out_sum`). u128 because the per-epoch total
/// can exceed u64 even though a single tx's outputs fit in u64.
#[n(23)]
#[cbor(default, with = "cbor_u128")]
pub output: u128,

/// Slot of the first block minted this epoch (0 if none). Stored as a slot
/// and converted to a wall-clock time by the reader via `ChainSummary`.
#[n(24)]
#[cbor(default)]
pub first_block_slot: u64,

/// Slot of the last block minted this epoch (0 if none). Stored as a slot
/// and converted to a wall-clock time by the reader via `ChainSummary`.
#[n(25)]
#[cbor(default)]
pub last_block_slot: u64,
}

impl TransitionDefault for RollingStats {
Expand Down Expand Up @@ -343,12 +393,20 @@ pub(crate) mod testing {
consumed_utxos in root::any_lovelace(),
gathered_fees in root::any_lovelace(),
blocks_minted in 0u32..1000u32,
tx_count in 0u64..100_000u64,
output in 0u128..u128::from(u64::MAX),
first_block_slot in root::any_slot(),
last_block_slot in root::any_slot(),
) -> RollingStats {
RollingStats {
produced_utxos,
consumed_utxos,
gathered_fees,
blocks_minted,
tx_count,
output,
first_block_slot,
last_block_slot,
..Default::default()
}
}
Expand Down Expand Up @@ -442,11 +500,19 @@ pub struct EpochStatsUpdate {
pub(crate) reserve_mirs: Lovelace,
pub(crate) treasury_mirs: Lovelace,
pub(crate) non_overlay_blocks_minted: u32,
pub(crate) tx_count: u64,
pub(crate) output: u128,
pub(crate) block_slot: u64,

// undo: did apply create rolling.live from default? Plus the pre-union pool set, which
// can't be recovered by set subtraction (a pool in both prev and self would be removed).
pub(crate) was_new: bool,
pub(crate) prev_registered_pools: HashSet<PoolHash>,

// undo for first/last block slot: min/max are not reversible by arithmetic,
// so the pre-apply values are captured to restore them exactly.
pub(crate) prev_first_block_slot: u64,
pub(crate) prev_last_block_slot: u64,
}

impl dolos_core::EntityDelta for EpochStatsUpdate {
Expand Down Expand Up @@ -484,6 +550,21 @@ impl dolos_core::EntityDelta for EpochStatsUpdate {
stats.reserve_mirs += self.reserve_mirs;
stats.treasury_mirs += self.treasury_mirs;
stats.non_overlay_blocks_minted += self.non_overlay_blocks_minted;
stats.tx_count += self.tx_count;
stats.output += self.output;

// Capture the pre-apply slots so `undo` can restore them (first/last
// are min/max, not reversible by arithmetic).
self.prev_first_block_slot = stats.first_block_slot;
self.prev_last_block_slot = stats.last_block_slot;

// `first_block_slot` is set only once (the epoch's earliest block);
// `last_block_slot` always advances to the most recent block. Blocks
// roll in slot order, so this yields the correct min/max.
if stats.first_block_slot == 0 {
stats.first_block_slot = self.block_slot;
}
stats.last_block_slot = self.block_slot;

stats.registered_pools = stats
.registered_pools
Expand Down Expand Up @@ -522,6 +603,11 @@ impl dolos_core::EntityDelta for EpochStatsUpdate {
stats.reserve_mirs -= self.reserve_mirs;
stats.treasury_mirs -= self.treasury_mirs;
stats.non_overlay_blocks_minted -= self.non_overlay_blocks_minted;
stats.tx_count -= self.tx_count;
stats.output -= self.output;

stats.first_block_slot = self.prev_first_block_slot;
stats.last_block_slot = self.prev_last_block_slot;

stats.registered_pools = self.prev_registered_pools.clone();
}
Expand Down Expand Up @@ -1549,10 +1635,14 @@ mod prop_tests {
new_accounts in 0u64..100u64,
removed_accounts in 0u64..100u64,
withdrawals in root::any_lovelace(),
tx_count in 0u64..1000u64,
output in 0u128..u128::from(u64::MAX),
block_slot in root::any_slot(),
) -> EpochStatsUpdate {
EpochStatsUpdate {
epoch, block_fees, utxo_delta,
new_accounts, removed_accounts, withdrawals,
tx_count, output, block_slot,
..EpochStatsUpdate::default()
}
}
Expand Down
Loading
Loading