From 9467829999f249a682e840c760ef56c503cdb6ea Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 6 Jul 2026 17:34:29 -0300 Subject: [PATCH 1/3] fix(cardano): align proptest epochs with strict assertions The `strict` feature (enabled by `--all-features`) turns on epoch-alignment assertions in `EpochValue` methods (`schedule`, `live_mut`, `replace`, `transition`). These assertions enforce a production invariant: a delta's epoch must equal the entity's current `EpochValue` epoch, which holds because ESTART transitions every entity in lockstep with the ledger epoch. The proptest strategies generated entity and delta with independently random epochs (both drawn from `any_epoch()`), violating the invariant and tripping the strict assertions under `--all-features`. Add epoch-parameterized strategy variants (`any_epoch_value_at`, `any_account_state_at`, `any_pool_state_at`, `any_epoch_state_at`, etc.) and rewire the 14 failing tests to share a single epoch between entity and delta via `prop_flat_map`. For transition deltas, the entity is generated at `epoch` and the delta at `epoch + 1`, matching the `assert_eq!(self.epoch + 1, next_epoch)` invariant. All 14 previously-failing tests now pass; dolos-cardano reports 129 passed, 0 failed. --- crates/cardano/src/model/accounts.rs | 109 ++++++++------ crates/cardano/src/model/epoch_value.rs | 41 ++++++ crates/cardano/src/model/epochs.rs | 180 ++++++++++++++---------- crates/cardano/src/model/pools.rs | 53 +++++-- 4 files changed, 252 insertions(+), 131 deletions(-) diff --git a/crates/cardano/src/model/accounts.rs b/crates/cardano/src/model/accounts.rs index f60efff4..691e89cb 100644 --- a/crates/cardano/src/model/accounts.rs +++ b/crates/cardano/src/model/accounts.rs @@ -133,7 +133,7 @@ entity_boilerplate!(AccountState, "accounts"); #[cfg(test)] pub(crate) mod testing { use super::*; - use crate::model::epoch_value::testing::any_epoch_value; + use crate::model::epoch_value::testing::{any_epoch_value, any_epoch_value_at}; use crate::model::testing as root; use proptest::prelude::*; @@ -192,6 +192,30 @@ pub(crate) mod testing { } } } + + prop_compose! { + pub fn any_account_state_at(epoch: Epoch)( + credential in root::any_stake_credential(), + registered_at in prop::option::of(root::any_slot()), + deregistered_at in prop::option::of(root::any_slot()), + stake in any_epoch_value_at(epoch, any_stake().boxed()), + pool in any_epoch_value_at(epoch, any_pool_delegation().boxed()), + drep in any_epoch_value_at(epoch, any_drep_delegation().boxed()), + vote_delegated_at in prop::option::of((root::any_slot(), root::any_tx_order())), + retired_pool in prop::option::of(root::any_pool_hash()), + ) -> AccountState { + AccountState { + credential, + registered_at, + deregistered_at, + stake, + pool, + drep, + vote_delegated_at, + retired_pool, + } + } + } } impl AccountState { @@ -1020,22 +1044,16 @@ impl dolos_core::EntityDelta for AccountTransition { #[cfg(test)] mod prop_tests { use super::*; - use super::testing::any_account_state; + use super::testing::{any_account_state, any_account_state_at}; use crate::model::testing::{self as root, assert_delta_roundtrip, assert_delta_serde_roundtrip}; use proptest::prelude::*; - /// Build an `AccountState` whose `pool.live` is guaranteed to be `PoolDelegation::Pool(..)`. - /// Required by `PoolDelegatorRetire`, which panics via `unreachable!()` if `prev_pool` isn't - /// a real pool delegation. - fn any_account_with_active_pool() -> impl Strategy { - (any_account_state(), root::any_pool_hash()).prop_map(|(mut acct, pool)| { - let live = acct.pool.live().cloned(); - let epoch = acct.pool.epoch().unwrap_or(3); - if live.is_some() { - acct.pool.replace(PoolDelegation::Pool(pool), epoch); - } else { - acct.pool = EpochValue::with_live(epoch, PoolDelegation::Pool(pool)); - } + /// Like `any_account_with_active_pool` but pinned to a specific epoch so the + /// entity's `EpochValue` fields align with the delta's epoch (required by + /// `strict` assertions). + fn any_account_with_active_pool_at(epoch: Epoch) -> impl Strategy { + (any_account_state_at(epoch), root::any_pool_hash()).prop_map(move |(mut acct, pool)| { + acct.pool = EpochValue::with_live(epoch, PoolDelegation::Pool(pool)); acct }) } @@ -1073,32 +1091,29 @@ mod prop_tests { } prop_compose! { - fn any_stake_delegation()( + fn any_stake_delegation_at(epoch: Epoch)( cred in root::any_stake_credential(), pool in root::any_hash_28(), - epoch in root::any_epoch(), ) -> StakeDelegation { StakeDelegation::new(cred, pool, epoch) } } prop_compose! { - fn any_vote_delegation()( + fn any_vote_delegation_at(epoch: Epoch)( cred in root::any_stake_credential(), drep in root::any_drep(), slot in root::any_slot(), order in root::any_tx_order(), - epoch in root::any_epoch(), ) -> VoteDelegation { VoteDelegation::new(cred, drep, slot, order, epoch) } } prop_compose! { - fn any_stake_deregistration()( + fn any_stake_deregistration_at(epoch: Epoch)( cred in root::any_stake_credential(), slot in root::any_slot(), - epoch in root::any_epoch(), ) -> StakeDeregistration { StakeDeregistration::new(cred, slot, epoch) } @@ -1114,18 +1129,16 @@ mod prop_tests { } prop_compose! { - fn any_pool_delegator_retire()( + fn any_pool_delegator_retire_at(epoch: Epoch)( pool in root::any_hash_28(), - epoch in root::any_epoch(), ) -> PoolDelegatorRetire { PoolDelegatorRetire::new(dolos_core::EntityKey::from(pool.as_slice()), epoch) } } prop_compose! { - fn any_drep_delegator_drop()( + fn any_drep_delegator_drop_at(epoch: Epoch)( cred_hash in root::any_hash_28(), - epoch in root::any_epoch(), ) -> DRepDelegatorDrop { DRepDelegatorDrop::new(dolos_core::EntityKey::from(cred_hash.as_slice()), epoch) } @@ -1168,9 +1181,8 @@ mod prop_tests { } prop_compose! { - fn any_account_transition()( + fn any_account_transition_at(next_epoch: Epoch)( account_hash in root::any_hash_28(), - next_epoch in root::any_epoch(), ) -> AccountTransition { AccountTransition::new(dolos_core::EntityKey::from(account_hash.as_slice()), next_epoch) } @@ -1203,24 +1215,27 @@ mod prop_tests { #[test] fn stake_delegation_roundtrip( - entity in any_account_state(), - delta in any_stake_delegation(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_state_at(epoch), any_stake_delegation_at(epoch)) + }), ) { assert_delta_roundtrip(Some(entity), delta); } #[test] fn vote_delegation_roundtrip( - entity in any_account_state(), - delta in any_vote_delegation(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_state_at(epoch), any_vote_delegation_at(epoch)) + }), ) { assert_delta_roundtrip(Some(entity), delta); } #[test] fn stake_deregistration_roundtrip( - entity in any_account_state(), - delta in any_stake_deregistration(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_state_at(epoch), any_stake_deregistration_at(epoch)) + }), ) { assert_delta_roundtrip(Some(entity), delta); } @@ -1235,16 +1250,18 @@ mod prop_tests { #[test] fn pool_delegator_retire_roundtrip( - entity in any_account_with_active_pool(), - delta in any_pool_delegator_retire(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_with_active_pool_at(epoch), any_pool_delegator_retire_at(epoch)) + }), ) { assert_delta_roundtrip(Some(entity), delta); } #[test] fn drep_delegator_drop_roundtrip( - entity in any_account_state(), - delta in any_drep_delegator_drop(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_state_at(epoch), any_drep_delegator_drop_at(epoch)) + }), ) { assert_delta_roundtrip(Some(entity), delta); } @@ -1283,8 +1300,9 @@ mod prop_tests { #[test] fn account_transition_roundtrip( - entity in any_account_state(), - delta in any_account_transition(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_state_at(epoch), any_account_transition_at(epoch + 1)) + }), ) { assert_delta_roundtrip(Some(entity), delta); } @@ -1325,24 +1343,27 @@ mod prop_tests { #[test] fn stake_delegation_serde_roundtrip( - entity in any_account_state(), - delta in any_stake_delegation(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_state_at(epoch), any_stake_delegation_at(epoch)) + }), ) { assert_delta_serde_roundtrip(Some(entity), delta); } #[test] fn stake_deregistration_serde_roundtrip( - entity in any_account_state(), - delta in any_stake_deregistration(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_state_at(epoch), any_stake_deregistration_at(epoch)) + }), ) { assert_delta_serde_roundtrip(Some(entity), delta); } #[test] fn vote_delegation_serde_roundtrip( - entity in any_account_state(), - delta in any_vote_delegation(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_account_state_at(epoch), any_vote_delegation_at(epoch)) + }), ) { assert_delta_serde_roundtrip(Some(entity), delta); } diff --git a/crates/cardano/src/model/epoch_value.rs b/crates/cardano/src/model/epoch_value.rs index 05c8acaf..ba6a8318 100644 --- a/crates/cardano/src/model/epoch_value.rs +++ b/crates/cardano/src/model/epoch_value.rs @@ -389,6 +389,47 @@ pub(crate) mod testing { EpochValue::from_parts(epoch, Some(live), None, mark, set, go) }) } + + /// Like `any_epoch_value` but pins the EpochValue to a caller-chosen epoch + /// instead of drawing one at random. Used by tests that need to align the + /// entity's epoch with the delta's epoch (required by `strict` assertions). + pub fn any_epoch_value_at( + epoch: Epoch, + inner: BoxedStrategy, + ) -> impl Strategy> + where + T: Clone + std::fmt::Debug + 'static, + { + ( + inner.clone(), + prop::option::of(inner.clone()), + prop::option::of(inner.clone()), + prop::option::of(inner.clone()), + prop::option::of(inner), + ) + .prop_map(move |(live, next, mark, set, go)| { + EpochValue::from_parts(epoch, Some(live), next, mark, set, go) + }) + } + + /// Like `any_epoch_value_no_next` but pinned to a caller-chosen epoch. + pub fn any_epoch_value_no_next_at( + epoch: Epoch, + inner: BoxedStrategy, + ) -> impl Strategy> + where + T: Clone + std::fmt::Debug + 'static, + { + ( + inner.clone(), + prop::option::of(inner.clone()), + prop::option::of(inner.clone()), + prop::option::of(inner), + ) + .prop_map(move |(live, mark, set, go)| { + EpochValue::from_parts(epoch, Some(live), None, mark, set, go) + }) + } } #[cfg(test)] diff --git a/crates/cardano/src/model/epochs.rs b/crates/cardano/src/model/epochs.rs index c7e54717..17b36dfc 100644 --- a/crates/cardano/src/model/epochs.rs +++ b/crates/cardano/src/model/epochs.rs @@ -319,7 +319,7 @@ entity_boilerplate!(EpochState, "epochs"); #[cfg(test)] pub(crate) mod testing { use super::*; - use crate::model::epoch_value::testing::any_epoch_value; + use crate::model::epoch_value::testing::{any_epoch_value, any_epoch_value_at, any_epoch_value_no_next_at}; use crate::model::pparams::testing::any_pparams_set; use crate::model::testing as root; use crate::pots::testing::{any_epoch_incentives, any_pots}; @@ -418,6 +418,80 @@ pub(crate) mod testing { } } } + + prop_compose! { + pub fn any_epoch_state_at(epoch: Epoch)( + initial_pots in any_pots(), + rolling in any_epoch_value_at(epoch, any_rolling_stats().boxed()), + pparams in any_epoch_value_at(epoch, any_pparams_set().boxed()), + largest_stable_slot in root::any_slot(), + previous_nonce_tail in prop::option::of(root::any_hash_32()), + nonces in prop::option::of(any_nonces()), + end in prop::option::of(any_end_stats()), + incentives in prop::option::of(any_epoch_incentives()), + ewrap_progress in prop::option::of( + (0u32..32u32, 1u32..=32u32).prop_map(|(committed, total)| ShardProgress { committed, total }) + ), + estart_progress in prop::option::of( + (0u32..32u32, 1u32..=32u32).prop_map(|(committed, total)| ShardProgress { committed, total }) + ), + rupd_progress in prop::option::of( + (0u32..32u32, 1u32..=32u32).prop_map(|(committed, total)| ShardProgress { committed, total }) + ), + ) -> EpochState { + EpochState { + number: epoch, + initial_pots, + rolling, + pparams, + largest_stable_slot, + previous_nonce_tail, + nonces, + end, + incentives, + ewrap_progress, + estart_progress, + rupd_progress, + } + } + } + + prop_compose! { + pub fn any_epoch_state_no_rolling_next_at(epoch: Epoch)( + initial_pots in any_pots(), + rolling in any_epoch_value_no_next_at(epoch, any_rolling_stats().boxed()), + pparams in any_epoch_value_at(epoch, any_pparams_set().boxed()), + largest_stable_slot in root::any_slot(), + previous_nonce_tail in prop::option::of(root::any_hash_32()), + nonces in prop::option::of(any_nonces()), + end in prop::option::of(any_end_stats()), + incentives in prop::option::of(any_epoch_incentives()), + ewrap_progress in prop::option::of( + (0u32..32u32, 1u32..=32u32).prop_map(|(committed, total)| ShardProgress { committed, total }) + ), + estart_progress in prop::option::of( + (0u32..32u32, 1u32..=32u32).prop_map(|(committed, total)| ShardProgress { committed, total }) + ), + rupd_progress in prop::option::of( + (0u32..32u32, 1u32..=32u32).prop_map(|(committed, total)| ShardProgress { committed, total }) + ), + ) -> EpochState { + EpochState { + number: epoch, + initial_pots, + rolling, + pparams, + largest_stable_slot, + previous_nonce_tail, + nonces, + end, + incentives, + ewrap_progress, + estart_progress, + rupd_progress, + } + } + } } // --- Deltas --- @@ -1495,63 +1569,12 @@ impl dolos_core::EntityDelta for SetEpochIncentives { #[allow(deprecated)] // prop_compose! macro expansions reference legacy // EpochWrapUp/EpochTransition for back-compat tests mod prop_tests { - use super::testing::{any_end_stats, any_epoch_state, any_nonces}; + use super::testing::{any_end_stats, any_epoch_state, any_epoch_state_at, any_epoch_state_no_rolling_next_at, any_nonces}; use super::*; - use crate::model::epoch_value::testing::{any_epoch_value, any_epoch_value_no_next}; - use crate::model::pparams::testing::any_pparams_set; use crate::model::testing::{self as root, assert_delta_roundtrip}; - use crate::pots::testing::{any_epoch_incentives, any_pots}; + use crate::pots::testing::any_epoch_incentives; use proptest::prelude::*; - // `EpochStatsUpdate::apply` calls `rolling.live_mut` which asserts `next` - // is None, so we need a specialized generator that keeps `rolling.next` - // empty. - prop_compose! { - fn any_epoch_state_no_rolling_next()( - number in root::any_epoch(), - initial_pots in any_pots(), - rolling in any_epoch_value_no_next(super::testing::any_rolling_stats().boxed()), - pparams in any_epoch_value(any_pparams_set().boxed()), - largest_stable_slot in root::any_slot(), - previous_nonce_tail in prop::option::of(root::any_hash_32()), - nonces in prop::option::of(any_nonces()), - end in prop::option::of(any_end_stats()), - incentives in prop::option::of(any_epoch_incentives()), - ) -> EpochState { - EpochState { - number, - initial_pots, - rolling, - pparams, - largest_stable_slot, - previous_nonce_tail, - nonces, - end, - incentives, - ewrap_progress: None, - estart_progress: None, - rupd_progress: None, - } - } - } - - prop_compose! { - fn any_epoch_stats_update()( - epoch in root::any_epoch(), - block_fees in root::any_lovelace(), - utxo_delta in -1_000_000i64..1_000_000i64, - new_accounts in 0u64..100u64, - removed_accounts in 0u64..100u64, - withdrawals in root::any_lovelace(), - ) -> EpochStatsUpdate { - EpochStatsUpdate { - epoch, block_fees, utxo_delta, - new_accounts, removed_accounts, withdrawals, - ..EpochStatsUpdate::default() - } - } - } - prop_compose! { fn any_nonces_update()( slot in root::any_slot(), @@ -1694,25 +1717,27 @@ mod prop_tests { } } - prop_compose! { - fn any_epoch_transition()( - new_epoch in root::any_epoch(), - ) -> EpochTransition { - // new_pots is filled in by the test harness from the entity's initial_pots - // so that `new_pots.max_supply() == entity.initial_pots.max_supply()` holds - // (which `apply`'s debug_assert requires). - EpochTransition::new(new_epoch, crate::pots::Pots::default(), None, None) - } + fn any_epoch_transition_at(new_epoch: Epoch) -> impl Strategy { + Just(EpochTransition::new(new_epoch, crate::pots::Pots::default(), None, None)) + } + + fn any_epoch_transition_v2_at(new_epoch: Epoch) -> impl Strategy { + Just(EpochTransitionV2::new(new_epoch, crate::pots::Pots::default(), None, None)) } prop_compose! { - fn any_epoch_transition_v2()( - new_epoch in root::any_epoch(), - ) -> EpochTransitionV2 { - // new_pots is filled in by the test harness from the entity's initial_pots - // so that `new_pots.max_supply() == entity.initial_pots.max_supply()` holds - // (which `apply`'s debug_assert requires). - EpochTransitionV2::new(new_epoch, crate::pots::Pots::default(), None, None) + fn any_epoch_stats_update_at(epoch: Epoch)( + block_fees in root::any_lovelace(), + utxo_delta in -1_000_000i64..1_000_000i64, + new_accounts in 0u64..100u64, + removed_accounts in 0u64..100u64, + withdrawals in root::any_lovelace(), + ) -> EpochStatsUpdate { + EpochStatsUpdate { + epoch, block_fees, utxo_delta, + new_accounts, removed_accounts, withdrawals, + ..EpochStatsUpdate::default() + } } } @@ -1727,8 +1752,9 @@ mod prop_tests { proptest! { #[test] fn epoch_stats_update_roundtrip( - entity in any_epoch_state_no_rolling_next(), - delta in any_epoch_stats_update(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_epoch_state_no_rolling_next_at(epoch), any_epoch_stats_update_at(epoch)) + }), ) { assert_delta_roundtrip(Some(entity), delta); } @@ -1784,8 +1810,9 @@ mod prop_tests { #[test] fn epoch_transition_roundtrip( - entity in any_epoch_state(), - mut delta in any_epoch_transition(), + (entity, mut delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_epoch_state_at(epoch), any_epoch_transition_at(epoch + 1)) + }), ) { // align new_pots with the entity's initial_pots so apply's max_supply // consistency debug_assert holds. @@ -1795,8 +1822,9 @@ mod prop_tests { #[test] fn epoch_transition_v2_roundtrip( - entity in any_epoch_state(), - mut delta in any_epoch_transition_v2(), + (entity, mut delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_epoch_state_at(epoch), any_epoch_transition_v2_at(epoch + 1)) + }), ) { // align new_pots with the entity's initial_pots so apply's max_supply // consistency debug_assert holds. diff --git a/crates/cardano/src/model/pools.rs b/crates/cardano/src/model/pools.rs index 42ef28de..66bec6ab 100644 --- a/crates/cardano/src/model/pools.rs +++ b/crates/cardano/src/model/pools.rs @@ -122,7 +122,7 @@ impl From for PoolParams { #[cfg(test)] pub(crate) mod testing { use super::*; - use crate::model::epoch_value::testing::any_epoch_value; + use crate::model::epoch_value::testing::{any_epoch_value, any_epoch_value_at}; use crate::model::testing as root; use proptest::prelude::*; @@ -185,6 +185,26 @@ pub(crate) mod testing { } } } + + prop_compose! { + pub fn any_pool_state_at(epoch: Epoch)( + operator in root::any_pool_hash(), + snapshot in any_epoch_value_at(epoch, any_pool_snapshot().boxed()), + blocks_minted_total in 0u32..10_000u32, + register_slot in root::any_slot(), + retiring_epoch in prop::option::of(root::any_epoch()), + deposit in root::any_lovelace(), + ) -> PoolState { + PoolState { + operator, + snapshot, + blocks_minted_total, + register_slot, + retiring_epoch, + deposit, + } + } + } } // --- Deltas --- @@ -500,7 +520,7 @@ impl dolos_core::EntityDelta for PoolTransition { #[cfg(test)] mod prop_tests { use super::*; - use super::testing::any_pool_state; + use super::testing::{any_pool_state, any_pool_state_at}; use crate::model::testing::{self as root, assert_delta_roundtrip}; use crate::pallas_extras::testing::any_multi_era_pool_registration; use proptest::prelude::*; @@ -516,6 +536,16 @@ mod prop_tests { } } + prop_compose! { + fn any_pool_registration_at(epoch: Epoch)( + cert in any_multi_era_pool_registration(), + slot in root::any_slot(), + pool_deposit in root::any_lovelace(), + ) -> PoolRegistration { + PoolRegistration::new(cert, slot, epoch, pool_deposit) + } + } + prop_compose! { fn any_pool_deregistration()( operator in root::any_hash_28(), @@ -533,13 +563,9 @@ mod prop_tests { } } - // `PoolTransition::apply` expects `entity.snapshot.live` populated so the - // `TransitionDefault` impl on `PoolSnapshot` can clone it forward. - // Our `any_pool_state` always fills `live`, so this holds. prop_compose! { - fn any_pool_transition()( + fn any_pool_transition_at(next_epoch: Epoch)( pool in root::any_hash_28(), - next_epoch in root::any_epoch(), ) -> PoolTransition { PoolTransition::new(dolos_core::EntityKey::from(pool.as_slice()), next_epoch) } @@ -548,8 +574,12 @@ mod prop_tests { proptest! { #[test] fn pool_registration_roundtrip( - entity in prop::option::of(any_pool_state()), - delta in any_pool_registration(), + (entity, delta) in prop_oneof![ + any_pool_registration().prop_map(|delta| (None, delta)), + root::any_epoch().prop_flat_map(|epoch| { + (any_pool_state_at(epoch).prop_map(Some), any_pool_registration_at(epoch)) + }), + ], ) { assert_delta_roundtrip(entity, delta); } @@ -583,8 +613,9 @@ mod prop_tests { #[test] fn pool_transition_roundtrip( - entity in any_pool_state(), - delta in any_pool_transition(), + (entity, delta) in root::any_epoch().prop_flat_map(|epoch| { + (any_pool_state_at(epoch), any_pool_transition_at(epoch + 1)) + }), ) { assert_delta_roundtrip(Some(entity), delta); } From 9fb8c705918bb182a2fa816489a821659cf224fe Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 6 Jul 2026 17:41:30 -0300 Subject: [PATCH 2/3] ci: test with --all-features to cover feature-gated code The CI already runs clippy with --all-features but tests only with default features. This let 14 strict-feature proptest failures slip through undetected. Add a second test step with --all-features to catch feature-gated code paths. --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93bb4930..feeafdbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,12 @@ jobs: command: test args: --workspace --all-targets + - name: Run unit tests (all features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --workspace --all-targets --all-features + - name: Run e2e smoke tests if: runner.os != 'Windows' uses: actions-rs/cargo@v1 From 313e546754890dd5949e70cf5ffb0cc0c96e5a36 Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 7 Jul 2026 08:26:28 -0300 Subject: [PATCH 3/3] revert: ci: test with --all-features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --all-features test step exposed a pre-existing bug in the work buffer (crates/cardano/src/work.rs): when blocks jump multiple epochs (e.g. from epoch 0 to epoch 2 during bulk import), the EstartBoundary pop_work arm puts the block into OpenBatch without re-checking if the block is still in a later epoch. This causes EpochStatsUpdate::apply to call live_mut with the wrong epoch, tripping the strict assertion. Fixing this requires passing ChainSummary to pop_work so it can detect multi-epoch jumps — a separate refactor. Revert the CI change until that bug is fixed. --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index feeafdbe..93bb4930 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,12 +53,6 @@ jobs: command: test args: --workspace --all-targets - - name: Run unit tests (all features) - uses: actions-rs/cargo@v1 - with: - command: test - args: --workspace --all-targets --all-features - - name: Run e2e smoke tests if: runner.os != 'Windows' uses: actions-rs/cargo@v1