use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::StdResult;
use crate::entities::{
BlockNumber, BlockNumberOffset, BlockRange, CardanoDbBeacon, SignedEntityType,
SignedEntityTypeDiscriminants, TimePoint,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedEntityConfig {
pub allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
pub cardano_transactions_signing_config: Option<CardanoTransactionsSigningConfig>,
pub cardano_blocks_transactions_signing_config: Option<CardanoBlocksTransactionsSigningConfig>,
}
impl SignedEntityConfig {
pub const DEFAULT_ALLOWED_DISCRIMINANTS: [SignedEntityTypeDiscriminants; 1] =
[SignedEntityTypeDiscriminants::MithrilStakeDistribution];
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 => {
match &self.cardano_transactions_signing_config {
Some(config) => SignedEntityType::CardanoTransactions(
time_point.epoch,
config
.compute_block_number_to_be_signed(time_point.chain_point.block_number),
),
None => {
anyhow::bail!(
"Can't derive a `CardanoTransactions` signed entity type from a time point without a `CardanoTransactionsSigningConfig`"
)
}
}
}
SignedEntityTypeDiscriminants::CardanoBlocksTransactions => {
match &self.cardano_blocks_transactions_signing_config {
Some(config) => SignedEntityType::CardanoBlocksTransactions(
time_point.epoch,
config
.compute_block_number_to_be_signed(time_point.chain_point.block_number),
config.security_parameter,
),
None => {
anyhow::bail!(
"Can't derive a `CardanoBlocksTransactions` signed entity type from a time point without a `CardanoBlocksTransactionsSigningConfig`"
)
}
}
}
SignedEntityTypeDiscriminants::CardanoDatabase => SignedEntityType::CardanoDatabase(
CardanoDbBeacon::new(*time_point.epoch, time_point.immutable_file_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: BlockNumberOffset,
pub step: BlockNumber,
}
impl CardanoTransactionsSigningConfig {
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 =
compute_block_number_to_be_signed(block_number, self.security_parameter, adjusted_step);
block_number_to_be_signed - 1
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CardanoBlocksTransactionsSigningConfig {
pub security_parameter: BlockNumberOffset,
pub step: BlockNumber,
}
impl CardanoBlocksTransactionsSigningConfig {
pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
compute_block_number_to_be_signed(block_number, self.security_parameter, self.step)
}
}
fn compute_block_number_to_be_signed(
block_number: BlockNumber,
security_parameter: BlockNumberOffset,
step: BlockNumber,
) -> BlockNumber {
let adjusted_step = std::cmp::max(step, BlockNumber(1));
(block_number - security_parameter) / adjusted_step * adjusted_step
}
#[cfg(test)]
mod tests {
use crate::entities::{
BlockNumberOffset, CardanoDbBeacon, ChainPoint, Epoch, SignedEntityType, SlotNumber,
TimePoint,
};
use crate::test::{double::Dummy, double::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: Some(CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockNumber(15),
}),
cardano_blocks_transactions_signing_config: Some(
CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(5),
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()
);
assert_eq!(
SignedEntityType::CardanoBlocksTransactions(
Epoch(1),
BlockNumber(15),
BlockNumberOffset(5)
),
config
.time_point_to_signed_entity(
SignedEntityTypeDiscriminants::CardanoBlocksTransactions,
&time_point
)
.unwrap()
);
assert_eq!(
SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(1, 5)),
config
.time_point_to_signed_entity(
SignedEntityTypeDiscriminants::CardanoDatabase,
&time_point
)
.unwrap()
);
}
#[test]
fn can_not_convert_time_point_to_cardano_transaction_without_the_associated_config() {
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: None,
cardano_blocks_transactions_signing_config: None,
};
let error = config
.time_point_to_signed_entity(
SignedEntityTypeDiscriminants::CardanoTransactions,
&time_point,
)
.unwrap_err();
let expected_error = "Can't derive a `CardanoTransactions` signed entity type from a time point without a `CardanoTransactionsSigningConfig`";
assert!(
error.to_string().contains(expected_error),
"Error message: {error:?}\nshould contains: {expected_error}\n"
);
}
mod compute_block_number_to_be_signed_for_cardano_transactions {
use super::*;
#[test]
fn compute_without_a_security_parameter() {
let block_number = BlockNumber(105);
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockNumber(15),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
104
);
}
#[test]
fn compute_with_security_parameter_lower_than_block_number() {
let block_number = BlockNumber(100);
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(5),
step: BlockNumber(15),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
89
);
}
#[test]
fn when_security_parameter_plus_step_equal_to_block_number_return_step_minus_1() {
let block_number = BlockNumber(100);
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(85),
step: BlockNumber(15),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
14
);
}
#[test]
fn when_step_higher_than_block_number_return_0() {
let block_number = BlockNumber(29);
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockNumber(30),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
0
);
}
#[test]
fn should_not_overlow_on_security_parameter() {
let block_number = BlockNumber(50);
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(100),
step: BlockNumber(30),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
0
);
}
#[test]
fn round_step_to_previous_block_range_start_when_step_right_below_said_block_range_start() {
let block_number = BlockRange::LENGTH * 5 + 1;
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH * 2 - 1,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
BlockRange::LENGTH * 5 - 1
);
}
#[test]
fn round_step_to_block_range_start_when_step_right_after_said_block_range_start() {
let block_number = BlockRange::LENGTH * 5 + 1;
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH * 2 + 1,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
BlockRange::LENGTH * 4 - 1
);
}
#[test]
fn step_lower_than_block_range_length_is_adjusted_to_block_range_length() {
let block_number = BlockRange::LENGTH * 10 - 1;
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH - 1,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
BlockRange::LENGTH * 9 - 1
);
}
#[test]
fn step_and_block_number_below_block_range_length_returns_0() {
let block_number = BlockRange::LENGTH - 1;
let signing_config = CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH - 1,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
0
);
}
}
mod compute_block_number_to_be_signed_for_cardano_blocks_transactions {
use super::*;
#[test]
fn compute_without_a_security_parameter() {
let block_number = BlockNumber(105);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockNumber(15),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
105
);
}
#[test]
fn compute_with_security_parameter_lower_than_block_number() {
let block_number = BlockNumber(100);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(5),
step: BlockNumber(15),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
90
);
}
#[test]
fn when_security_parameter_plus_step_equal_to_block_number_return_step_minus_1() {
let block_number = BlockNumber(100);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(85),
step: BlockNumber(15),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
15
);
}
#[test]
fn when_step_higher_than_block_number_return_0() {
let block_number = BlockNumber(29);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockNumber(30),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
0
);
}
#[test]
fn should_not_overlow_on_security_parameter() {
let block_number = BlockNumber(50);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(100),
step: BlockNumber(30),
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
0
);
}
#[test]
fn can_use_step_right_below_than_a_multiple_of_a_block_range_length() {
let block_number = BlockNumber(150);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH - 1,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
140,
);
}
#[test]
fn can_use_step_right_after_a_multiple_of_a_block_range_length() {
let block_number = BlockNumber(150);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH + 1,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
144,
);
}
#[test]
fn can_use_step_higher_than_a_block_range_length_and_out_not_equal_to_a_range_boundaries() {
let block_number = BlockNumber(150);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH + 6,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
147,
);
}
#[test]
fn can_use_step_lower_than_a_block_range_length_and_out_not_equal_to_a_range_boundaries() {
let block_number = BlockNumber(150);
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH - 6,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
144,
);
}
#[test]
fn can_use_a_step_and_block_number_both_below_block_range_length() {
let block_number = BlockRange::LENGTH - 1;
let signing_config = CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockRange::LENGTH - 10,
};
assert_eq!(
signing_config.compute_block_number_to_be_signed(block_number),
10,
);
}
}
#[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,
SignedEntityTypeDiscriminants::CardanoDatabase,
]),
..SignedEntityConfig::dummy()
};
let discriminants = config.list_allowed_signed_entity_types_discriminants();
assert_eq!(
BTreeSet::from_iter(
[
SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
[
SignedEntityTypeDiscriminants::CardanoStakeDistribution,
SignedEntityTypeDiscriminants::CardanoTransactions,
SignedEntityTypeDiscriminants::CardanoDatabase
]
.as_slice()
]
.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,
SignedEntityTypeDiscriminants::CardanoBlocksTransactions,
]),
cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
security_parameter: BlockNumberOffset(0),
step: BlockNumber(15),
}),
cardano_blocks_transactions_signing_config: Some(
CardanoBlocksTransactionsSigningConfig {
security_parameter: BlockNumberOffset(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::CardanoTransactions(beacon.epoch, chain_point.block_number - 1),
SignedEntityType::CardanoBlocksTransactions(
beacon.epoch,
chain_point.block_number,
BlockNumberOffset(0)
),
],
signed_entity_types
);
}
}