use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::entities::{
BlockNumber, BlockRange, CardanoDbBeacon, SignedEntityType, SignedEntityTypeDiscriminants,
TimePoint,
};
use crate::StdResult;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedEntityConfig {
pub allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
}
impl SignedEntityConfig {
cfg_test_tools! {
pub fn dummy() -> Self {
Self {
allowed_discriminants: SignedEntityTypeDiscriminants::all(),
cardano_transactions_signing_config: CardanoTransactionsSigningConfig::dummy(),
}
}
}
pub const DEFAULT_ALLOWED_DISCRIMINANTS: [SignedEntityTypeDiscriminants; 2] = [
SignedEntityTypeDiscriminants::MithrilStakeDistribution,
SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
];
pub fn append_allowed_signed_entity_types_discriminants(
discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
) -> BTreeSet<SignedEntityTypeDiscriminants> {
let mut discriminants = discriminants;
discriminants.append(&mut BTreeSet::from(Self::DEFAULT_ALLOWED_DISCRIMINANTS));
discriminants
}
pub fn list_allowed_signed_entity_types_discriminants(
&self,
) -> BTreeSet<SignedEntityTypeDiscriminants> {
let discriminants = self.allowed_discriminants.clone();
Self::append_allowed_signed_entity_types_discriminants(discriminants)
}
pub fn time_point_to_signed_entity<D: Into<SignedEntityTypeDiscriminants>>(
&self,
discriminant: D,
time_point: &TimePoint,
) -> StdResult<SignedEntityType> {
let signed_entity_type = match discriminant.into() {
SignedEntityTypeDiscriminants::MithrilStakeDistribution => {
SignedEntityType::MithrilStakeDistribution(time_point.epoch)
}
SignedEntityTypeDiscriminants::CardanoStakeDistribution => {
SignedEntityType::CardanoStakeDistribution(time_point.epoch.previous()?)
}
SignedEntityTypeDiscriminants::CardanoImmutableFilesFull => {
SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(
*time_point.epoch,
time_point.immutable_file_number,
))
}
SignedEntityTypeDiscriminants::CardanoTransactions => {
SignedEntityType::CardanoTransactions(
time_point.epoch,
self.cardano_transactions_signing_config
.compute_block_number_to_be_signed(time_point.chain_point.block_number),
)
}
};
Ok(signed_entity_type)
}
pub fn list_allowed_signed_entity_types(
&self,
time_point: &TimePoint,
) -> StdResult<Vec<SignedEntityType>> {
self.list_allowed_signed_entity_types_discriminants()
.into_iter()
.map(|discriminant| self.time_point_to_signed_entity(discriminant, time_point))
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CardanoTransactionsSigningConfig {
pub security_parameter: BlockNumber,
pub step: BlockNumber,
}
impl CardanoTransactionsSigningConfig {
cfg_test_tools! {
pub fn new(security_parameter: BlockNumber, step: BlockNumber) -> Self {
Self {
security_parameter,
step,
}
}
pub fn dummy() -> Self {
Self {
security_parameter: BlockNumber(0),
step: BlockNumber(15),
}
}
}
pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
let adjusted_step = BlockRange::from_block_number(self.step).start;
let adjusted_step = std::cmp::max(adjusted_step, BlockRange::LENGTH);
let block_number_to_be_signed =
(block_number - self.security_parameter) / adjusted_step * adjusted_step;
block_number_to_be_signed - 1
}
}
#[cfg(test)]
mod tests {
use crate::entities::{
CardanoDbBeacon, ChainPoint, Epoch, SignedEntityType, SlotNumber, TimePoint,
};
use crate::test_utils::fake_data;
use super::*;
#[test]
fn given_discriminant_convert_to_signed_entity() {
let time_point = TimePoint {
epoch: Epoch(1),
immutable_file_number: 5,
chain_point: ChainPoint {
slot_number: SlotNumber(73),
block_number: BlockNumber(20),
block_hash: "block_hash-20".to_string(),
},
};
let config = SignedEntityConfig {
allowed_discriminants: SignedEntityTypeDiscriminants::all(),
cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(0),
step: BlockNumber(15),
},
};
assert_eq!(
SignedEntityType::MithrilStakeDistribution(Epoch(1)),
config
.time_point_to_signed_entity(
SignedEntityTypeDiscriminants::MithrilStakeDistribution,
&time_point
)
.unwrap()
);
assert_eq!(
SignedEntityType::CardanoStakeDistribution(Epoch(0)),
config
.time_point_to_signed_entity(
SignedEntityTypeDiscriminants::CardanoStakeDistribution,
&time_point
)
.unwrap()
);
assert_eq!(
SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(1, 5)),
config
.time_point_to_signed_entity(
SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
&time_point
)
.unwrap()
);
assert_eq!(
SignedEntityType::CardanoTransactions(Epoch(1), BlockNumber(14)),
config
.time_point_to_signed_entity(
SignedEntityTypeDiscriminants::CardanoTransactions,
&time_point
)
.unwrap()
);
}
#[test]
fn computing_block_number_to_be_signed() {
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(0),
step: BlockNumber(15),
}
.compute_block_number_to_be_signed(BlockNumber(105)),
104
);
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(5),
step: BlockNumber(15),
}
.compute_block_number_to_be_signed(BlockNumber(100)),
89
);
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(85),
step: BlockNumber(15),
}
.compute_block_number_to_be_signed(BlockNumber(100)),
14
);
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(0),
step: BlockNumber(30),
}
.compute_block_number_to_be_signed(BlockNumber(29)),
0
);
}
#[test]
fn computing_block_number_to_be_signed_should_not_overlow_on_security_parameter() {
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(100),
step: BlockNumber(30),
}
.compute_block_number_to_be_signed(BlockNumber(50)),
0
);
}
#[test]
fn computing_block_number_to_be_signed_round_step_to_a_block_range_start() {
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(0),
step: BlockRange::LENGTH * 2 - 1,
}
.compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
BlockRange::LENGTH * 5 - 1
);
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(0),
step: BlockRange::LENGTH * 2 + 1,
}
.compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
BlockRange::LENGTH * 4 - 1
);
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(0),
step: BlockRange::LENGTH - 1,
}
.compute_block_number_to_be_signed(BlockRange::LENGTH * 10 - 1),
BlockRange::LENGTH * 9 - 1
);
assert_eq!(
CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(0),
step: BlockRange::LENGTH - 1,
}
.compute_block_number_to_be_signed(BlockRange::LENGTH - 1),
0
);
}
#[test]
fn test_list_allowed_signed_entity_types_discriminant_without_specific_configuration() {
let config = SignedEntityConfig {
allowed_discriminants: BTreeSet::new(),
..SignedEntityConfig::dummy()
};
let discriminants = config.list_allowed_signed_entity_types_discriminants();
assert_eq!(
BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
discriminants
);
}
#[test]
fn test_list_allowed_signed_entity_types_discriminant_should_not_duplicate_a_signed_entity_discriminant_type_already_in_default_ones(
) {
let config = SignedEntityConfig {
allowed_discriminants: BTreeSet::from([
SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS[0],
]),
..SignedEntityConfig::dummy()
};
let discriminants = config.list_allowed_signed_entity_types_discriminants();
assert_eq!(
BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
discriminants
);
}
#[test]
fn test_list_allowed_signed_entity_types_discriminants_should_add_configured_discriminants() {
let config = SignedEntityConfig {
allowed_discriminants: BTreeSet::from([
SignedEntityTypeDiscriminants::CardanoStakeDistribution,
SignedEntityTypeDiscriminants::CardanoTransactions,
]),
..SignedEntityConfig::dummy()
};
let discriminants = config.list_allowed_signed_entity_types_discriminants();
assert_eq!(
BTreeSet::from_iter(
[
SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS,
[
SignedEntityTypeDiscriminants::CardanoStakeDistribution,
SignedEntityTypeDiscriminants::CardanoTransactions,
]
]
.concat()
),
discriminants
);
}
#[test]
fn test_list_allowed_signed_entity_types_discriminants_with_multiple_identical_signed_entity_types_in_configuration_should_not_be_added_several_times(
) {
let config = SignedEntityConfig {
allowed_discriminants: BTreeSet::from([
SignedEntityTypeDiscriminants::CardanoTransactions,
SignedEntityTypeDiscriminants::CardanoTransactions,
SignedEntityTypeDiscriminants::CardanoTransactions,
]),
..SignedEntityConfig::dummy()
};
let discriminants = config.list_allowed_signed_entity_types_discriminants();
assert_eq!(
BTreeSet::from_iter(
[
SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
[SignedEntityTypeDiscriminants::CardanoTransactions].as_slice()
]
.concat()
),
discriminants
);
}
#[test]
fn test_list_allowed_signed_entity_types_with_specific_configuration() {
let beacon = fake_data::beacon();
let chain_point = ChainPoint {
block_number: BlockNumber(45),
..ChainPoint::dummy()
};
let time_point = TimePoint::new(
*beacon.epoch,
beacon.immutable_file_number,
chain_point.clone(),
);
let config = SignedEntityConfig {
allowed_discriminants: BTreeSet::from([
SignedEntityTypeDiscriminants::CardanoStakeDistribution,
SignedEntityTypeDiscriminants::CardanoTransactions,
]),
cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
security_parameter: BlockNumber(0),
step: BlockNumber(15),
},
};
let signed_entity_types = config
.list_allowed_signed_entity_types(&time_point)
.unwrap();
assert_eq!(
vec![
SignedEntityType::MithrilStakeDistribution(beacon.epoch),
SignedEntityType::CardanoStakeDistribution(beacon.epoch - 1),
SignedEntityType::CardanoImmutableFilesFull(beacon.clone()),
SignedEntityType::CardanoTransactions(beacon.epoch, chain_point.block_number - 1),
],
signed_entity_types
);
}
}