From 8b975e462690fa3c9e73d81dde1ec887fb17567d Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 16 Aug 2023 11:40:01 -0400 Subject: [PATCH 01/28] Update info response and related structs --- sdk/src/client/core.rs | 9 +- sdk/src/client/node_manager/syncing.rs | 10 +- sdk/src/client/secret/ledger_nano.rs | 4 +- sdk/src/types/api/core/response.rs | 58 ++++--- sdk/src/types/block/basic.rs | 4 +- sdk/src/types/block/core.rs | 11 +- sdk/src/types/block/error.rs | 74 ++++++++- sdk/src/types/block/protocol.rs | 222 ++++++++++++++++++------- sdk/src/types/block/validation.rs | 5 +- sdk/src/utils/serde.rs | 27 +++ sdk/src/wallet/account/mod.rs | 4 +- sdk/tests/types/block.rs | 4 +- 12 files changed, 325 insertions(+), 107 deletions(-) diff --git a/sdk/src/client/core.rs b/sdk/src/client/core.rs index bfcaa919f7..40f1ab2c3e 100644 --- a/sdk/src/client/core.rs +++ b/sdk/src/client/core.rs @@ -124,7 +124,7 @@ impl ClientInner { /// Gets the protocol version of the node we're connecting to. pub async fn get_protocol_version(&self) -> Result { - Ok(self.get_network_info().await?.protocol_parameters.protocol_version()) + Ok(self.get_network_info().await?.protocol_parameters.version()) } /// Gets the network name of the node we're connecting to. @@ -139,12 +139,7 @@ impl ClientInner { /// Gets the bech32 HRP of the node we're connecting to. pub async fn get_bech32_hrp(&self) -> Result { - Ok(*self.get_network_info().await?.protocol_parameters.bech32_hrp()) - } - - /// Gets the below maximum depth of the node we're connecting to. - pub async fn get_below_max_depth(&self) -> Result { - Ok(self.get_network_info().await?.protocol_parameters.below_max_depth()) + Ok(self.get_network_info().await?.protocol_parameters.bech32_hrp()) } /// Gets the rent structure of the node we're connecting to. diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index 2399526b7b..f496c10345 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -73,13 +73,14 @@ impl ClientInner { match crate::client::Client::get_node_info(node.url.as_ref(), node.auth.clone()).await { Ok(info) => { if info.status.is_healthy || ignore_node_health { - match network_nodes.get_mut(info.protocol.network_name()) { + // TODO: is it okay to index like this? + let network_name = info.protocol_parameters[0].parameters.network_name(); + match network_nodes.get_mut(network_name) { Some(network_node_entry) => { network_node_entry.push((info, node.clone())); } None => { - network_nodes - .insert(info.protocol.network_name().to_owned(), vec![(info, node.clone())]); + network_nodes.insert(network_name.to_owned(), vec![(info, node.clone())]); } } } else { @@ -106,8 +107,9 @@ impl ClientInner { let mut network_info = self.network_info.write().await; // TODO change to one of the new timestamps, which ones ? + // TODO: okay to index here? // network_info.latest_milestone_timestamp = info.status.latest_milestone.timestamp; - network_info.protocol_parameters = info.protocol.clone(); + network_info.protocol_parameters = info.protocol_parameters[0].parameters.clone(); } for (info, node_url) in nodes { diff --git a/sdk/src/client/secret/ledger_nano.rs b/sdk/src/client/secret/ledger_nano.rs index 6c6eb4c29b..2bb4a8ab71 100644 --- a/sdk/src/client/secret/ledger_nano.rs +++ b/sdk/src/client/secret/ledger_nano.rs @@ -552,7 +552,7 @@ fn merge_unlocks( // address already at this point, because the reference index needs to be lower // than the current block index if !input_address.is_ed25519() { - return Err(Error::MissingInputWithEd25519Address)?; + return Err(Error::MissingInputWithEd25519Address); } let unlock = unlocks.next().ok_or(Error::MissingInputWithEd25519Address)?; @@ -561,7 +561,7 @@ fn merge_unlocks( let Signature::Ed25519(ed25519_signature) = signature_unlock.signature(); let ed25519_address = match input_address { Address::Ed25519(ed25519_address) => ed25519_address, - _ => return Err(Error::MissingInputWithEd25519Address)?, + _ => return Err(Error::MissingInputWithEd25519Address), }; ed25519_signature.is_valid(&hashed_essence, &ed25519_address)?; } diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index a284dc045b..35dcce6d33 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -6,7 +6,7 @@ use alloc::{string::String, vec::Vec}; use crate::types::block::{ output::{dto::OutputDto, OutputId, OutputMetadata, OutputWithMetadata}, protocol::ProtocolParameters, - slot::SlotIndex, + slot::{SlotCommitmentId, SlotIndex}, BlockId, }; @@ -23,10 +23,9 @@ pub struct InfoResponse { pub version: String, pub status: StatusResponse, pub metrics: MetricsResponse, - pub supported_protocol_versions: Vec, - pub protocol: ProtocolParameters, + pub protocol_parameters: Box<[ProtocolParametersResponse]>, pub base_token: BaseTokenResponse, - pub features: Vec, + pub features: Box<[String]>, } #[cfg(feature = "serde")] @@ -46,19 +45,19 @@ impl core::fmt::Display for InfoResponse { )] pub struct StatusResponse { pub is_healthy: bool, - #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + #[serde(with = "crate::utils::serde::string")] pub accepted_tangle_time: u64, - #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + #[serde(with = "crate::utils::serde::string")] pub relative_accepted_tangle_time: u64, - #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + #[serde(with = "crate::utils::serde::string")] pub confirmed_tangle_time: u64, - #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + #[serde(with = "crate::utils::serde::string")] pub relative_confirmed_tangle_time: u64, - pub latest_committed_slot: SlotIndex, + pub latest_commitment_id: SlotCommitmentId, pub latest_finalized_slot: SlotIndex, + pub latest_accepted_block_slot: BlockId, + pub latest_confirmed_block_slot: BlockId, pub pruning_slot: SlotIndex, - pub latest_accepted_block_id: BlockId, - pub latest_confirmed_block_id: BlockId, } /// Returned in [`InfoResponse`]. @@ -70,11 +69,26 @@ pub struct StatusResponse { serde(rename_all = "camelCase") )] pub struct MetricsResponse { + #[serde(with = "crate::utils::serde::string")] pub blocks_per_second: f64, + #[serde(with = "crate::utils::serde::string")] pub confirmed_blocks_per_second: f64, + #[serde(with = "crate::utils::serde::string")] pub confirmed_rate: f64, } +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct ProtocolParametersResponse { + // TODO: should this be a number? + pub start_epoch: String, + pub parameters: ProtocolParameters, +} + /// Returned in [`InfoResponse`]. /// Information about the base token. #[derive(Clone, Debug, Eq, PartialEq)] @@ -87,9 +101,9 @@ pub struct BaseTokenResponse { pub name: String, pub ticker_symbol: String, pub unit: String, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub subunit: Option, pub decimals: u32, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub subunit: Option, pub use_metric_prefix: bool, } @@ -142,19 +156,19 @@ pub struct BlockMetadataResponse { pub block_id: BlockId, pub parents: Vec, pub is_solid: bool, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub referenced_by_milestone_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub milestone_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub ledger_inclusion_state: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub conflict_reason: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub white_flag_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub should_promote: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub should_reattach: Option, } @@ -256,11 +270,11 @@ pub enum Relation { pub struct PeerResponse { pub id: String, pub multi_addresses: Vec, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub alias: Option, pub relation: Relation, pub connected: bool, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[serde(default, skip_serializing_if = "Option::is_none")] pub gossip: Option, } diff --git a/sdk/src/types/block/basic.rs b/sdk/src/types/block/basic.rs index 9da34ffe48..092a4577aa 100644 --- a/sdk/src/types/block/basic.rs +++ b/sdk/src/types/block/basic.rs @@ -188,9 +188,9 @@ impl Packable for BasicBlock { let protocol_version = u8::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - if VERIFY && protocol_version != protocol_params.protocol_version() { + if VERIFY && protocol_version != protocol_params.version() { return Err(UnpackError::Packable(Error::ProtocolVersionMismatch { - expected: protocol_params.protocol_version(), + expected: protocol_params.version(), actual: protocol_version, })); } diff --git a/sdk/src/types/block/core.rs b/sdk/src/types/block/core.rs index b97422a03e..7e31a06461 100644 --- a/sdk/src/types/block/core.rs +++ b/sdk/src/types/block/core.rs @@ -123,9 +123,9 @@ impl Packable for Block { let protocol_version = u8::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - if VERIFY && protocol_version != protocol_params.protocol_version() { + if VERIFY && protocol_version != protocol_params.version() { return Err(UnpackError::Packable(Error::ProtocolVersionMismatch { - expected: protocol_params.protocol_version(), + expected: protocol_params.version(), actual: protocol_version, })); } @@ -220,7 +220,7 @@ impl BlockWrapper { /// Returns the protocol version of a [`Block`]. #[inline(always)] pub fn protocol_version(&self) -> u8 { - self.protocol_params.protocol_version + self.protocol_params.version() } /// Returns the protocol parameters of a [`Block`]. @@ -321,6 +321,7 @@ impl Block { /// Creates a new [`BlockBuilder`] to construct an instance of a [`ValidationBlock`]. #[inline(always)] + #[allow(clippy::too_many_arguments)] pub fn build_validation( protocol_params: ProtocolParameters, issuing_time: u64, @@ -694,9 +695,9 @@ pub(crate) mod dto { impl Block { pub fn try_from_dto(dto: BlockDto, protocol_params: ProtocolParameters) -> Result { - if dto.protocol_version != protocol_params.protocol_version() { + if dto.protocol_version != protocol_params.version() { return Err(Error::ProtocolVersionMismatch { - expected: protocol_params.protocol_version(), + expected: protocol_params.version(), actual: dto.protocol_version, }); } diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 5d0a362b2b..fb34f0e514 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -5,7 +5,7 @@ use alloc::string::{FromUtf8Error, String}; use core::{convert::Infallible, fmt}; use crypto::Error as CryptoError; -// use packable::bounded::BoundedU8; +use packable::prefix::UnpackPrefixError; use prefix_hex::Error as HexError; use primitive_types::U256; @@ -83,6 +83,10 @@ pub enum Error { InvalidMetadataFeatureLength(>::Error), InvalidNativeTokenCount(>::Error), InvalidNetworkName(FromUtf8Error), + InvalidEpochNearingThreshold(FromUtf8Error), + InvalidManaDecayFactors(UnpackOptionError), + InvalidLivenessThreshold(UnpackOptionError), + InvalidOption(Box>), InvalidNftIndex(>::Error), InvalidOutputAmount(u64), InvalidOutputCount(>::Error), @@ -245,6 +249,10 @@ impl fmt::Display for Error { } Self::InvalidNativeTokenCount(count) => write!(f, "invalid native token count: {count}"), Self::InvalidNetworkName(err) => write!(f, "invalid network name: {err}"), + Self::InvalidEpochNearingThreshold(err) => write!(f, "invalid epoch nearing threshold: {err}"), + Self::InvalidManaDecayFactors(err) => write!(f, "invalid mana decay factors: {err}"), + Self::InvalidLivenessThreshold(err) => write!(f, "invalid liveness threshold: {err}"), + Self::InvalidOption(err) => write!(f, "invalid work score structure: {err}"), Self::InvalidNftIndex(index) => write!(f, "invalid nft index: {index}"), Self::InvalidOutputAmount(amount) => write!(f, "invalid output amount: {amount}"), Self::InvalidOutputCount(count) => write!(f, "invalid output count: {count}"), @@ -360,6 +368,70 @@ impl fmt::Display for Error { } } +#[derive(Debug)] +pub struct UnpackOptionError(packable::option::UnpackOptionError); + +impl PartialEq for UnpackOptionError { + fn eq(&self, other: &Self) -> bool { + use packable::option::UnpackOptionError as OtherErr; + match (&self.0, &other.0) { + (OtherErr::UnknownTag(t1), OtherErr::UnknownTag(t2)) => t1 == t2, + (OtherErr::Inner(e1), OtherErr::Inner(e2)) => e1 == e2, + _ => false, + } + } +} +impl Eq for UnpackOptionError {} + +impl UnpackOptionError { + fn map_opt_err U, U>(self, f: F) -> UnpackOptionError { + use packable::option::UnpackOptionError as OtherErr; + UnpackOptionError(match self.0 { + OtherErr::UnknownTag(t) => OtherErr::UnknownTag(t), + OtherErr::Inner(e) => OtherErr::Inner(f(e)), + }) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for UnpackOptionError {} + +impl fmt::Display for UnpackOptionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use packable::option::UnpackOptionError as OtherErr; + match &self.0 { + OtherErr::UnknownTag(t) => write!(f, "unknown tag: {t}"), + OtherErr::Inner(e) => write!(f, "{e}"), + } + } +} + +pub(crate) trait UnpackPrefixOptionErrorExt { + fn into_opt_error(self) -> UnpackOptionError; +} + +impl UnpackPrefixOptionErrorExt for packable::option::UnpackOptionError { + fn into_opt_error(self) -> UnpackOptionError { + UnpackOptionError(self) + } +} + +impl UnpackPrefixOptionErrorExt for packable::option::UnpackOptionError> { + fn into_opt_error(self) -> UnpackOptionError { + use packable::option::UnpackOptionError as OtherErr; + UnpackOptionError(match self { + Self::UnknownTag(t) => OtherErr::UnknownTag(t), + Self::Inner(e) => OtherErr::Inner(e.into_item_err()), + }) + } +} + +impl> From> for Error { + fn from(value: packable::option::UnpackOptionError) -> Self { + Self::InvalidOption(value.into_opt_error().map_opt_err(Into::into).into()) + } +} + impl From for Error { fn from(error: CryptoError) -> Self { Self::Crypto(error) diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 475e08e034..6a377855dd 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -5,13 +5,20 @@ use alloc::string::String; use core::borrow::Borrow; use crypto::hashes::{blake2b::Blake2b256, Digest}; -use packable::{prefix::StringPrefix, Packable, PackableExt}; +use getset::{CopyGetters, Getters}; +use packable::{ + prefix::{BoxedSlicePrefix, StringPrefix}, + Packable, PackableExt, +}; use super::{address::Hrp, slot::SlotIndex}; -use crate::types::block::{helper::network_name_to_id, output::RentStructure, ConvertTo, Error, PROTOCOL_VERSION}; +use crate::types::block::{ + error::UnpackPrefixOptionErrorExt, helper::network_name_to_id, output::RentStructure, ConvertTo, Error, + PROTOCOL_VERSION, +}; /// Defines the parameters of the protocol. -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable)] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, Getters, CopyGetters)] #[packable(unpack_error = Error)] #[cfg_attr( feature = "serde", @@ -20,27 +27,82 @@ use crate::types::block::{helper::network_name_to_id, output::RentStructure, Con )] pub struct ProtocolParameters { // The version of the protocol running. - #[cfg_attr(feature = "serde", serde(rename = "version"))] - pub(crate) protocol_version: u8, + #[getset(get_copy = "pub")] + version: u8, // The human friendly name of the network. #[packable(unpack_error_with = |err| Error::InvalidNetworkName(err.into_item_err()))] - #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string_prefix"))] + #[serde(with = "crate::utils::serde::string_prefix")] + #[getset(skip)] network_name: StringPrefix, // The HRP prefix used for Bech32 addresses in the network. + #[getset(get_copy = "pub")] bech32_hrp: Hrp, - // The below max depth parameter of the network. - below_max_depth: u8, // The rent structure used by given node/network. + #[getset(get = "pub")] rent_structure: RentStructure, + // The work score structure used by the node/network. + #[getset(get = "pub")] + #[serde(default, skip_serializing_if = "Option::is_none")] + work_score_structure: Option, // TokenSupply defines the current token supply on the network. - #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + #[serde(with = "crate::utils::serde::string")] + #[getset(get_copy = "pub")] token_supply: u64, // Genesis timestamp at which the slots start to count. - #[cfg_attr(feature = "serde", serde(alias = "genesisUnixTimestamp"))] + #[serde(with = "crate::utils::serde::string")] + #[getset(get_copy = "pub")] genesis_unix_timestamp: u32, // Duration of each slot in seconds. - #[cfg_attr(feature = "serde", serde(alias = "slotDurationInSeconds"))] + #[getset(get_copy = "pub")] slot_duration_in_seconds: u8, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get_copy = "pub")] + slots_per_epoch_exponent: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get_copy = "pub")] + mana_generation_rate: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get_copy = "pub")] + mana_generation_rate_exponent: Option, + #[packable(unpack_error_with = |err| Error::InvalidManaDecayFactors(err.into_opt_error()))] + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(skip)] + mana_decay_factors: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get_copy = "pub")] + mana_decay_factors_exponent: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get_copy = "pub")] + mana_decay_factor_epochs_sum: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get_copy = "pub")] + mana_decay_factor_epochs_sum_exponent: Option, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "crate::utils::serde::option_string" + )] + #[getset(get_copy = "pub")] + staking_unbonding_period: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get_copy = "pub")] + eviction_age: Option, + // TODO: wtf are these? should they be strings? + #[packable(unpack_error_with = |err| Error::InvalidLivenessThreshold(err.into_opt_error()))] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "crate::utils::serde::option_string_prefix" + )] + #[getset(skip)] + liveness_threshold: Option>, + #[packable(unpack_error_with = |err| Error::InvalidEpochNearingThreshold(err.into_item_err()))] + #[serde(with = "crate::utils::serde::string_prefix")] + #[getset(skip)] + epoch_nearing_threshold: StringPrefix, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get = "pub")] + version_signaling: Option, } // This implementation is required to make [`ProtocolParameters`] a [`Packable`] visitor. @@ -52,18 +114,29 @@ impl Borrow<()> for ProtocolParameters { impl Default for ProtocolParameters { fn default() -> Self { - // PANIC: These values are known to be correct. - Self::new( - PROTOCOL_VERSION, - String::from("iota-core-testnet"), - "smr", - 15, - RentStructure::default(), - 1_813_620_509_061_365, - 1582328545, - 10, - ) - .unwrap() + Self { + version: PROTOCOL_VERSION, + // Unwrap: Known to be valid + network_name: "iota-core-testnet".to_owned().try_into().unwrap(), + bech32_hrp: Hrp::from_str_unchecked("smr"), + rent_structure: Default::default(), + work_score_structure: Default::default(), + token_supply: 1_813_620_509_061_365, + genesis_unix_timestamp: 1582328545, + slot_duration_in_seconds: 10, + slots_per_epoch_exponent: Default::default(), + mana_generation_rate: Default::default(), + mana_generation_rate_exponent: Default::default(), + mana_decay_factors: Default::default(), + mana_decay_factors_exponent: Default::default(), + mana_decay_factor_epochs_sum: Default::default(), + mana_decay_factor_epochs_sum_exponent: Default::default(), + staking_unbonding_period: Default::default(), + eviction_age: Default::default(), + liveness_threshold: Default::default(), + epoch_nearing_threshold: Default::default(), + version_signaling: Default::default(), + } } } @@ -71,32 +144,29 @@ impl ProtocolParameters { /// Creates a new [`ProtocolParameters`]. #[allow(clippy::too_many_arguments)] pub fn new( - protocol_version: u8, - network_name: String, + version: u8, + network_name: impl Into, bech32_hrp: impl ConvertTo, - below_max_depth: u8, rent_structure: RentStructure, token_supply: u64, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8, + epoch_nearing_threshold: impl Into, ) -> Result { Ok(Self { - protocol_version, - network_name: >::try_from(network_name).map_err(Error::InvalidStringPrefix)?, + version, + network_name: >::try_from(network_name.into()).map_err(Error::InvalidStringPrefix)?, bech32_hrp: bech32_hrp.convert()?, - below_max_depth, rent_structure, token_supply, genesis_unix_timestamp, slot_duration_in_seconds, + epoch_nearing_threshold: >::try_from(epoch_nearing_threshold.into()) + .map_err(Error::InvalidStringPrefix)?, + ..Default::default() }) } - /// Returns the protocol version of the [`ProtocolParameters`]. - pub fn protocol_version(&self) -> u8 { - self.protocol_version - } - /// Returns the network name of the [`ProtocolParameters`]. pub fn network_name(&self) -> &str { &self.network_name @@ -107,32 +177,19 @@ impl ProtocolParameters { network_name_to_id(&self.network_name) } - /// Returns the bech32 HRP of the [`ProtocolParameters`]. - pub fn bech32_hrp(&self) -> &Hrp { - &self.bech32_hrp - } - - /// Returns the below max depth of the [`ProtocolParameters`]. - pub fn below_max_depth(&self) -> u8 { - self.below_max_depth - } - - /// Returns the rent structure of the [`ProtocolParameters`]. - pub fn rent_structure(&self) -> &RentStructure { - &self.rent_structure + /// Returns the mana decay factors slice of the [`ProtocolParameters`]. + pub fn mana_decay_factors(&self) -> Option<&[u32]> { + self.mana_decay_factors.as_ref().map(|slice| slice.as_ref()) } - /// Returns the token supply of the [`ProtocolParameters`]. - pub fn token_supply(&self) -> u64 { - self.token_supply + /// Returns the liveness threshold of the [`ProtocolParameters`]. + pub fn liveness_threshold(&self) -> Option<&str> { + self.liveness_threshold.as_ref().map(|s| s.as_str()) } - pub fn genesis_unix_timestamp(&self) -> u32 { - self.genesis_unix_timestamp - } - - pub fn slot_duration_in_seconds(&self) -> u8 { - self.slot_duration_in_seconds + /// Returns the epoch nearing threshold of the [`ProtocolParameters`]. + pub fn epoch_nearing_threshold(&self) -> &str { + &self.epoch_nearing_threshold } pub fn slot_index(&self, timestamp: u64) -> SlotIndex { @@ -152,18 +209,67 @@ pub fn calc_slot_index(timestamp: u64, genesis_unix_timestamp: u32, slot_duratio (1 + (timestamp - genesis_unix_timestamp as u64) / slot_duration_in_seconds as u64).into() } +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +#[packable(unpack_error = Error)] +#[getset(get_copy = "pub")] +pub struct WorkScoreStructure { + /// Modifier for network traffic per byte. + data_byte: u32, + /// Modifier for work done to process a block. + block: u32, + /// Modifier for slashing when there are insufficient strong tips. + missing_parent: u32, + /// Modifier for loading UTXOs and performing mana calculations. + input: u32, + /// Modifier for loading and checking the context input. + context_input: u32, + /// Modifier for storing UTXOs. + output: u32, + /// Modifier for calculations using native tokens. + native_token: u32, + /// Modifier for storing staking features. + staking: u32, + /// Modifier for storing block issuer features. + block_issuer: u32, + /// Modifier for accessing the account-based ledger to transform mana to Block Issuance Credits. + allotment: u32, + /// Modifier for the block signature check. + signature_ed25519: u32, + /// The minimum count of strong parents in a basic block. + min_strong_parents_threshold: u32, +} + +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +#[packable(unpack_error = Error)] +#[getset(get_copy = "pub")] +pub struct VersionSignalingParameters { + window_size: u32, + window_target_ratio: u32, + activation_offset: u32, +} + /// Returns a [`ProtocolParameters`] for testing purposes. #[cfg(any(feature = "test", feature = "rand"))] pub fn protocol_parameters() -> ProtocolParameters { ProtocolParameters::new( 2, - String::from("testnet"), + "testnet", "rms", - 15, crate::types::block::output::RentStructure::new(500, 10, 1), 1_813_620_509_061_365, 1582328545, 10, + "TODO", ) .unwrap() } diff --git a/sdk/src/types/block/validation.rs b/sdk/src/types/block/validation.rs index 2428c1d065..92d4370220 100644 --- a/sdk/src/types/block/validation.rs +++ b/sdk/src/types/block/validation.rs @@ -22,6 +22,7 @@ pub type ValidationBlock = BlockWrapper; impl BlockBuilder { /// Creates a new [`BlockBuilder`] for a [`ValidationBlock`]. #[inline(always)] + #[allow(clippy::too_many_arguments)] pub fn new( protocol_params: ProtocolParameters, issuing_time: u64, @@ -182,9 +183,9 @@ impl Packable for ValidationBlock { let protocol_version = u8::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - if VERIFY && protocol_version != protocol_params.protocol_version() { + if VERIFY && protocol_version != protocol_params.version() { return Err(UnpackError::Packable(Error::ProtocolVersionMismatch { - expected: protocol_params.protocol_version(), + expected: protocol_params.version(), actual: protocol_version, })); } diff --git a/sdk/src/utils/serde.rs b/sdk/src/utils/serde.rs index b6363fef80..4e2859a46e 100644 --- a/sdk/src/utils/serde.rs +++ b/sdk/src/utils/serde.rs @@ -131,6 +131,33 @@ pub mod string_prefix { } } +pub mod option_string_prefix { + use alloc::string::String; + + use packable::{bounded::Bounded, prefix::StringPrefix}; + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &Option>, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(bytes) => super::string_prefix::serialize(bytes, serializer), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, T: Bounded, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + >::Error: core::fmt::Display, + { + Option::::deserialize(deserializer) + .map_err(de::Error::custom) + .and_then(|s| s.map(|s| s.try_into().map_err(de::Error::custom)).transpose()) + } +} + pub mod boxed_slice_prefix { use alloc::boxed::Box; diff --git a/sdk/src/wallet/account/mod.rs b/sdk/src/wallet/account/mod.rs index 9691263b10..83f3126c58 100644 --- a/sdk/src/wallet/account/mod.rs +++ b/sdk/src/wallet/account/mod.rs @@ -612,13 +612,13 @@ fn serialize() { let protocol_parameters = ProtocolParameters::new( 2, - String::from("testnet"), + "testnet", "rms", - 15, crate::types::block::output::RentStructure::new(500, 10, 1), 1_813_620_509_061_365, 1582328545, 10, + "TODO", ) .unwrap(); diff --git a/sdk/tests/types/block.rs b/sdk/tests/types/block.rs index 9400698076..201c31648a 100644 --- a/sdk/tests/types/block.rs +++ b/sdk/tests/types/block.rs @@ -110,7 +110,7 @@ fn getters() { .finish() .unwrap(); - assert_eq!(block.protocol_version(), protocol_parameters.protocol_version()); + assert_eq!(block.protocol_version(), protocol_parameters.version()); assert_eq!(*block.strong_parents(), parents); assert_eq!(*block.payload().as_ref().unwrap(), &payload); } @@ -160,7 +160,7 @@ fn dto_mismatch_version() { assert_eq!( block_res, Err(iota_sdk::types::block::Error::ProtocolVersionMismatch { - expected: protocol_parameters.protocol_version(), + expected: protocol_parameters.version(), actual: protocol_version }) ); From c4aac920f162df0c1da752b59ca78a2d69510a78 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 16 Aug 2023 12:34:40 -0400 Subject: [PATCH 02/28] display --- sdk/src/types/block/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index fb34f0e514..b3bb73985b 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -252,7 +252,7 @@ impl fmt::Display for Error { Self::InvalidEpochNearingThreshold(err) => write!(f, "invalid epoch nearing threshold: {err}"), Self::InvalidManaDecayFactors(err) => write!(f, "invalid mana decay factors: {err}"), Self::InvalidLivenessThreshold(err) => write!(f, "invalid liveness threshold: {err}"), - Self::InvalidOption(err) => write!(f, "invalid work score structure: {err}"), + Self::InvalidOption(err) => write!(f, "invalid optional field: {err}"), Self::InvalidNftIndex(index) => write!(f, "invalid nft index: {index}"), Self::InvalidOutputAmount(amount) => write!(f, "invalid output amount: {amount}"), Self::InvalidOutputCount(count) => write!(f, "invalid output count: {count}"), From 7f229d8a04ec2b13e89f2d6b2744d326de6cd589 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 16 Aug 2023 12:42:32 -0400 Subject: [PATCH 03/28] fix wasm, no_std and test --- sdk/src/client/core.rs | 2 +- sdk/src/types/api/core/response.rs | 2 +- sdk/src/types/block/error.rs | 9 ++++++--- sdk/src/types/block/protocol.rs | 2 +- sdk/tests/types/block_id.rs | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/sdk/src/client/core.rs b/sdk/src/client/core.rs index 40f1ab2c3e..309582cb30 100644 --- a/sdk/src/client/core.rs +++ b/sdk/src/client/core.rs @@ -109,7 +109,7 @@ impl ClientInner { } let info = self.get_info().await?.node_info; let mut client_network_info = self.network_info.write().await; - client_network_info.protocol_parameters = info.protocol.clone(); + client_network_info.protocol_parameters = info.protocol_parameters[0].parameters.clone(); *LAST_SYNC.lock().unwrap() = Some(current_time + CACHE_NETWORK_INFO_TIMEOUT_IN_SECONDS); } diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index 35dcce6d33..1687d3c76b 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -1,7 +1,7 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{string::String, vec::Vec}; +use alloc::{boxed::Box, string::String, vec::Vec}; use crate::types::block::{ output::{dto::OutputDto, OutputId, OutputMetadata, OutputWithMetadata}, diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index b3bb73985b..66324b8f21 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -1,7 +1,10 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::string::{FromUtf8Error, String}; +use alloc::{ + boxed::Box, + string::{FromUtf8Error, String}, +}; use core::{convert::Infallible, fmt}; use crypto::Error as CryptoError; @@ -394,9 +397,9 @@ impl UnpackOptionError { } #[cfg(feature = "std")] -impl std::error::Error for UnpackOptionError {} +impl std::error::Error for UnpackOptionError {} -impl fmt::Display for UnpackOptionError { +impl fmt::Display for UnpackOptionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use packable::option::UnpackOptionError as OtherErr; match &self.0 { diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 6a377855dd..ad1604648d 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -117,7 +117,7 @@ impl Default for ProtocolParameters { Self { version: PROTOCOL_VERSION, // Unwrap: Known to be valid - network_name: "iota-core-testnet".to_owned().try_into().unwrap(), + network_name: String::from("iota-core-testnet").try_into().unwrap(), bech32_hrp: Hrp::from_str_unchecked("smr"), rent_structure: Default::default(), work_score_structure: Default::default(), diff --git a/sdk/tests/types/block_id.rs b/sdk/tests/types/block_id.rs index a29cd309eb..a931a9b659 100644 --- a/sdk/tests/types/block_id.rs +++ b/sdk/tests/types/block_id.rs @@ -99,11 +99,11 @@ fn compute() { // TODO: Independently verify this value assert_eq!( block_id.to_string(), - "0x89664f06b89ec91ef53b69a6c5da763aad498da7fc344978cf09fcdcbbd6464a0b00000000000000" + "0x5966461e29acde4d3ee431fa740aff3092368de4a655b96b4177e88d7050431a0b00000000000000" ); assert_eq!( block_id.hash().to_string(), - "0x89664f06b89ec91ef53b69a6c5da763aad498da7fc344978cf09fcdcbbd6464a" + "0x5966461e29acde4d3ee431fa740aff3092368de4a655b96b4177e88d7050431a" ); assert_eq!(*block_id.slot_index(), slot_index); } From ec0a0f9b677481e141895884b63bfb00c7b4b9db Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Thu, 17 Aug 2023 08:17:28 -0400 Subject: [PATCH 04/28] fix one more test --- sdk/tests/client/client_builder.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/tests/client/client_builder.rs b/sdk/tests/client/client_builder.rs index 4f0fcd4470..5f0bf70607 100644 --- a/sdk/tests/client/client_builder.rs +++ b/sdk/tests/client/client_builder.rs @@ -34,18 +34,18 @@ async fn client_builder() { "quorumThreshold":66, "userAgent":"iota-client/2.0.1-rc.3", "protocolParameters":{ - "version":2, + "version":3, "networkName":"shimmer", "bech32Hrp":"smr", - "belowMaxDepth":15, "rentStructure":{ "vByteCost":100, "vByteFactorKey":10, "vByteFactorData":1 }, "tokenSupply":"1813620509061365", - "genesisUnixTimestamp":1582328545, - "slotDurationInSeconds":10 + "genesisUnixTimestamp":"1582328545", + "slotDurationInSeconds":10, + "epochNearingThreshold":"TODO" }, "apiTimeout":{ "secs":15, From 7c2dec2e320623d338addfeed618ac03ac1ecaef Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Mon, 21 Aug 2023 10:15:23 -0400 Subject: [PATCH 05/28] Implement mapped params, epoch index, helpers --- sdk/src/client/api/high_level.rs | 12 +- sdk/src/client/builder.rs | 10 +- sdk/src/client/error.rs | 6 +- sdk/src/client/node_manager/syncing.rs | 9 +- sdk/src/types/api/core/response.rs | 105 ++++++++++++++++-- sdk/src/types/block/error.rs | 4 - sdk/src/types/block/output/delegation.rs | 39 ++++--- sdk/src/types/block/output/feature/staking.rs | 27 +++-- sdk/src/types/block/protocol.rs | 53 +++++---- sdk/src/types/block/rand/output/feature.rs | 2 +- sdk/src/types/block/slot/epoch.rs | 29 +++++ sdk/src/types/block/slot/index.rs | 10 ++ sdk/src/types/block/slot/mod.rs | 5 +- sdk/src/wallet/account/mod.rs | 2 +- sdk/tests/client/client_builder.rs | 2 +- sdk/tests/client/error.rs | 2 +- 16 files changed, 225 insertions(+), 92 deletions(-) create mode 100644 sdk/src/types/block/slot/epoch.rs diff --git a/sdk/src/client/api/high_level.rs b/sdk/src/client/api/high_level.rs index 6efc46218f..ea65e0ee82 100644 --- a/sdk/src/client/api/high_level.rs +++ b/sdk/src/client/api/high_level.rs @@ -121,14 +121,20 @@ impl Client { let network_info = self.get_network_info().await?; - if let Some(latest_ms_timestamp) = network_info.latest_milestone_timestamp { + if let Some(latest_finalized_slot_timestamp) = network_info.latest_finalized_slot.map(|idx| { + idx.as_timestamp( + network_info.protocol_parameters.genesis_unix_timestamp(), + network_info.protocol_parameters.slot_duration_in_seconds(), + ) + }) { // Check the local time is in the range of +-5 minutes of the node to prevent locking funds by accident - if !(latest_ms_timestamp - FIVE_MINUTES_IN_SECONDS..latest_ms_timestamp + FIVE_MINUTES_IN_SECONDS) + if !(latest_finalized_slot_timestamp - FIVE_MINUTES_IN_SECONDS + ..latest_finalized_slot_timestamp + FIVE_MINUTES_IN_SECONDS) .contains(¤t_time) { return Err(Error::TimeNotSynced { current_time, - milestone_timestamp: latest_ms_timestamp, + latest_finalized_slot_timestamp, }); } } diff --git a/sdk/src/client/builder.rs b/sdk/src/client/builder.rs index 4bc781ad9b..46b2ed81ab 100644 --- a/sdk/src/client/builder.rs +++ b/sdk/src/client/builder.rs @@ -19,7 +19,7 @@ use crate::{ }, Client, }, - types::block::protocol::ProtocolParameters, + types::block::{protocol::ProtocolParameters, slot::SlotIndex}, }; /// Builder to construct client instance with sensible default values @@ -263,9 +263,9 @@ pub struct NetworkInfo { /// Protocol parameters. #[serde(default)] pub protocol_parameters: ProtocolParameters, - /// The latest cached milestone timestamp. + /// The latest finalized slot index. #[serde(skip)] - pub latest_milestone_timestamp: Option, + pub latest_finalized_slot: Option, } impl NetworkInfo { @@ -274,8 +274,8 @@ impl NetworkInfo { self } - pub fn with_latest_milestone_timestamp(mut self, latest_milestone_timestamp: impl Into>) -> Self { - self.latest_milestone_timestamp = latest_milestone_timestamp.into(); + pub fn with_latest_finalized_slot(mut self, latest_finalized_slot: impl Into) -> Self { + self.latest_finalized_slot = Some(latest_finalized_slot.into()); self } } diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs index 19c5fbf5e4..911c2153aa 100644 --- a/sdk/src/client/error.rs +++ b/sdk/src/client/error.rs @@ -130,13 +130,13 @@ pub enum Error { TaskJoin(#[from] tokio::task::JoinError), /// Local time doesn't match the time of the latest milestone timestamp #[error( - "local time {current_time} doesn't match the time of the latest milestone timestamp: {milestone_timestamp}" + "local time {current_time} doesn't match the time of the latest finalized slot timestamp: {latest_finalized_slot_timestamp}" )] TimeNotSynced { /// The local time. current_time: u32, - /// The timestamp of the latest milestone. - milestone_timestamp: u32, + /// The timestamp of the latest finalized slot. + latest_finalized_slot_timestamp: u32, }, /// The semantic validation of a transaction failed. #[error("the semantic validation of a transaction failed with conflict reason: {} - {0:?}", *.0 as u8)] diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index f496c10345..3bd9d4c363 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -73,8 +73,7 @@ impl ClientInner { match crate::client::Client::get_node_info(node.url.as_ref(), node.auth.clone()).await { Ok(info) => { if info.status.is_healthy || ignore_node_health { - // TODO: is it okay to index like this? - let network_name = info.protocol_parameters[0].parameters.network_name(); + let network_name = info.latest_protocol_parameters().network_name(); match network_nodes.get_mut(network_name) { Some(network_node_entry) => { network_node_entry.push((info, node.clone())); @@ -106,10 +105,8 @@ impl ClientInner { if let Some((info, _node_url)) = nodes.first() { let mut network_info = self.network_info.write().await; - // TODO change to one of the new timestamps, which ones ? - // TODO: okay to index here? - // network_info.latest_milestone_timestamp = info.status.latest_milestone.timestamp; - network_info.protocol_parameters = info.protocol_parameters[0].parameters.clone(); + network_info.latest_finalized_slot = Some(info.status.latest_finalized_slot); + network_info.protocol_parameters = info.latest_protocol_parameters().clone(); } for (info, node_url) in nodes { diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index 944be44bd8..b4fce9b4c3 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -1,13 +1,13 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{boxed::Box, string::String, vec::Vec}; +use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec}; use crate::types::block::{ output::{dto::OutputDto, OutputId, OutputMetadata, OutputWithMetadata}, parent::{ShallowLikeParents, StrongParents, WeakParents}, protocol::ProtocolParameters, - slot::{SlotCommitment, SlotCommitmentId, SlotIndex}, + slot::{EpochIndex, SlotCommitment, SlotCommitmentId, SlotIndex}, BlockId, }; @@ -24,11 +24,25 @@ pub struct InfoResponse { pub version: String, pub status: StatusResponse, pub metrics: MetricsResponse, - pub protocol_parameters: Box<[ProtocolParametersResponse]>, + pub protocol_parameters: ProtocolParametersResponse, pub base_token: BaseTokenResponse, pub features: Box<[String]>, } +impl InfoResponse { + pub fn latest_protocol_parameters(&self) -> &ProtocolParameters { + self.protocol_parameters.latest() + } + + pub fn parameters_by_version(&self, protocol_version: u8) -> Option<&ProtocolParameters> { + self.protocol_parameters.by_version(protocol_version) + } + + pub fn parameters_by_epoch(&self, epoch_index: EpochIndex) -> Option<&ProtocolParameters> { + self.protocol_parameters.by_epoch(epoch_index) + } +} + #[cfg(feature = "serde")] impl core::fmt::Display for InfoResponse { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -79,15 +93,84 @@ pub struct MetricsResponse { } #[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize), - serde(rename_all = "camelCase") -)] pub struct ProtocolParametersResponse { - // TODO: should this be a number? - pub start_epoch: String, - pub parameters: ProtocolParameters, + parameters: Box<[ProtocolParameters]>, + version_map: BTreeMap, + epoch_map: BTreeMap, +} + +impl ProtocolParametersResponse { + pub fn iter(&self) -> impl Iterator { + self.epoch_map.iter().map(|(&epoch, &i)| (epoch, &self.parameters[i])) + } + + pub fn latest(&self) -> &ProtocolParameters { + &self.parameters[*self.version_map.last_key_value().unwrap().1] + } + + pub fn by_version(&self, protocol_version: u8) -> Option<&ProtocolParameters> { + self.version_map.get(&protocol_version).map(|&i| &self.parameters[i]) + } + + pub fn by_epoch(&self, epoch_index: EpochIndex) -> Option<&ProtocolParameters> { + self.epoch_map.get(&epoch_index).map(|&i| &self.parameters[i]) + } +} + +#[cfg(feature = "serde")] +mod serde_protocol_params_response { + use alloc::borrow::Cow; + + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::*; + + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + struct ProtocolParametersResponseDto<'a> { + parameters: Cow<'a, ProtocolParameters>, + start_epoch: EpochIndex, + } + + impl Serialize for ProtocolParametersResponse { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_seq( + self.iter() + .map(|(start_epoch, parameters)| ProtocolParametersResponseDto { + parameters: Cow::Borrowed(parameters), + start_epoch, + }), + ) + } + } + + impl<'de> Deserialize<'de> for ProtocolParametersResponse { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let parameters = Vec::>::deserialize(deserializer)?; + let (mut version_map, mut epoch_map) = (BTreeMap::default(), BTreeMap::default()); + let parameters = parameters + .into_iter() + .enumerate() + .map(|(i, res)| { + let (start_epoch, parameters) = (res.start_epoch, res.parameters.into_owned()); + version_map.insert(parameters.version(), i); + epoch_map.insert(start_epoch, i); + parameters + }) + .collect(); + Ok(Self { + parameters, + version_map, + epoch_map, + }) + } + } } /// Returned in [`InfoResponse`]. diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 66324b8f21..847477f0cf 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -86,9 +86,7 @@ pub enum Error { InvalidMetadataFeatureLength(>::Error), InvalidNativeTokenCount(>::Error), InvalidNetworkName(FromUtf8Error), - InvalidEpochNearingThreshold(FromUtf8Error), InvalidManaDecayFactors(UnpackOptionError), - InvalidLivenessThreshold(UnpackOptionError), InvalidOption(Box>), InvalidNftIndex(>::Error), InvalidOutputAmount(u64), @@ -252,9 +250,7 @@ impl fmt::Display for Error { } Self::InvalidNativeTokenCount(count) => write!(f, "invalid native token count: {count}"), Self::InvalidNetworkName(err) => write!(f, "invalid network name: {err}"), - Self::InvalidEpochNearingThreshold(err) => write!(f, "invalid epoch nearing threshold: {err}"), Self::InvalidManaDecayFactors(err) => write!(f, "invalid mana decay factors: {err}"), - Self::InvalidLivenessThreshold(err) => write!(f, "invalid liveness threshold: {err}"), Self::InvalidOption(err) => write!(f, "invalid optional field: {err}"), Self::InvalidNftIndex(index) => write!(f, "invalid nft index: {index}"), Self::InvalidOutputAmount(amount) => write!(f, "invalid output amount: {amount}"), diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index c214193c29..a1a77c64ae 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -24,6 +24,7 @@ use crate::types::{ }, protocol::ProtocolParameters, semantic::{ConflictReason, ValidationContext}, + slot::EpochIndex, unlock::Unlock, Error, }, @@ -55,8 +56,8 @@ pub struct DelegationOutputBuilder { delegated_amount: u64, delegation_id: DelegationId, validator_id: AccountId, - start_epoch: u64, - end_epoch: u64, + start_epoch: EpochIndex, + end_epoch: EpochIndex, unlock_conditions: BTreeSet, } @@ -103,8 +104,8 @@ impl DelegationOutputBuilder { delegated_amount, delegation_id, validator_id, - start_epoch: 0, - end_epoch: 0, + start_epoch: 0.into(), + end_epoch: 0.into(), unlock_conditions: BTreeSet::new(), } } @@ -134,14 +135,14 @@ impl DelegationOutputBuilder { } /// Sets the start epoch to the provided value. - pub fn with_start_epoch(mut self, start_epoch: u64) -> Self { - self.start_epoch = start_epoch; + pub fn with_start_epoch(mut self, start_epoch: impl Into) -> Self { + self.start_epoch = start_epoch.into(); self } /// Sets the end epoch to the provided value. - pub fn with_end_epoch(mut self, end_epoch: u64) -> Self { - self.end_epoch = end_epoch; + pub fn with_end_epoch(mut self, end_epoch: impl Into) -> Self { + self.end_epoch = end_epoch.into(); self } @@ -244,9 +245,9 @@ pub struct DelegationOutput { /// The Account ID of the validator to which this output is delegating. validator_id: AccountId, /// The index of the first epoch for which this output delegates. - start_epoch: u64, + start_epoch: EpochIndex, /// The index of the last epoch for which this output delegates. - end_epoch: u64, + end_epoch: EpochIndex, unlock_conditions: UnlockConditions, } @@ -310,12 +311,12 @@ impl DelegationOutput { } /// Returns the start epoch of the [`DelegationOutput`]. - pub fn start_epoch(&self) -> u64 { + pub fn start_epoch(&self) -> EpochIndex { self.start_epoch } /// Returns the end epoch of the [`DelegationOutput`]. - pub fn end_epoch(&self) -> u64 { + pub fn end_epoch(&self) -> EpochIndex { self.end_epoch } @@ -382,8 +383,8 @@ impl Packable for DelegationOutput { let delegated_amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let delegation_id = DelegationId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let validator_id = AccountId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let start_epoch = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let end_epoch = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let start_epoch = EpochIndex::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let end_epoch = EpochIndex::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?; verify_unlock_conditions::(&unlock_conditions).map_err(UnpackError::Packable)?; @@ -438,10 +439,8 @@ pub(crate) mod dto { pub delegated_amount: u64, pub delegation_id: DelegationId, pub validator_id: AccountId, - #[serde(with = "string")] - start_epoch: u64, - #[serde(with = "string")] - end_epoch: u64, + start_epoch: EpochIndex, + end_epoch: EpochIndex, pub unlock_conditions: Vec, } @@ -492,8 +491,8 @@ pub(crate) mod dto { delegated_amount: u64, delegation_id: &DelegationId, validator_id: &AccountId, - start_epoch: u64, - end_epoch: u64, + start_epoch: impl Into, + end_epoch: impl Into, unlock_conditions: Vec, params: impl Into> + Send, ) -> Result { diff --git a/sdk/src/types/block/output/feature/staking.rs b/sdk/src/types/block/output/feature/staking.rs index 97f52f7e6b..11742f69df 100644 --- a/sdk/src/types/block/output/feature/staking.rs +++ b/sdk/src/types/block/output/feature/staking.rs @@ -1,6 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use crate::types::block::slot::EpochIndex; + /// Stakes coins to become eligible for committee selection, validate the network and receive Mana rewards. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] pub struct StakingFeature { @@ -9,9 +11,9 @@ pub struct StakingFeature { /// The fixed cost of the validator, which it receives as part of its Mana rewards. fixed_cost: u64, /// The epoch index in which the staking started. - start_epoch: u64, + start_epoch: EpochIndex, /// The epoch index in which the staking ends. - end_epoch: u64, + end_epoch: EpochIndex, } impl StakingFeature { @@ -19,12 +21,17 @@ impl StakingFeature { pub const KIND: u8 = 5; /// Creates a new [`StakingFeature`]. - pub fn new(staked_amount: u64, fixed_cost: u64, start_epoch: u64, end_epoch: u64) -> Self { + pub fn new( + staked_amount: u64, + fixed_cost: u64, + start_epoch: impl Into, + end_epoch: impl Into, + ) -> Self { Self { staked_amount, fixed_cost, - start_epoch, - end_epoch, + start_epoch: start_epoch.into(), + end_epoch: end_epoch.into(), } } @@ -39,12 +46,12 @@ impl StakingFeature { } /// Returns the start epoch of the [`StakingFeature`]. - pub fn start_epoch(&self) -> u64 { + pub fn start_epoch(&self) -> EpochIndex { self.start_epoch } /// Returns the end epoch of the [`StakingFeature`]. - pub fn end_epoch(&self) -> u64 { + pub fn end_epoch(&self) -> EpochIndex { self.end_epoch } } @@ -64,10 +71,8 @@ mod dto { staked_amount: u64, #[serde(with = "string")] fixed_cost: u64, - #[serde(with = "string")] - start_epoch: u64, - #[serde(with = "string")] - end_epoch: u64, + start_epoch: EpochIndex, + end_epoch: EpochIndex, } impl From<&StakingFeature> for StakingFeatureDto { diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index ad1604648d..3cbf12f2cf 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -11,12 +11,18 @@ use packable::{ Packable, PackableExt, }; -use super::{address::Hrp, slot::SlotIndex}; +use super::{ + address::Hrp, + slot::{EpochIndex, SlotIndex}, +}; use crate::types::block::{ error::UnpackPrefixOptionErrorExt, helper::network_name_to_id, output::RentStructure, ConvertTo, Error, PROTOCOL_VERSION, }; +// TODO: The API spec lists this field as optional, but is it really? And if so, what would the default be? +pub const DEFAULT_SLOTS_PER_EPOCH_EXPONENT: u32 = 10; + /// Defines the parameters of the protocol. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, Getters, CopyGetters)] #[packable(unpack_error = Error)] @@ -56,7 +62,7 @@ pub struct ProtocolParameters { #[getset(get_copy = "pub")] slot_duration_in_seconds: u8, #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get_copy = "pub")] + #[getset(skip)] slots_per_epoch_exponent: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] @@ -87,19 +93,9 @@ pub struct ProtocolParameters { #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] eviction_age: Option, - // TODO: wtf are these? should they be strings? - #[packable(unpack_error_with = |err| Error::InvalidLivenessThreshold(err.into_opt_error()))] - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "crate::utils::serde::option_string_prefix" - )] - #[getset(skip)] - liveness_threshold: Option>, - #[packable(unpack_error_with = |err| Error::InvalidEpochNearingThreshold(err.into_item_err()))] - #[serde(with = "crate::utils::serde::string_prefix")] - #[getset(skip)] - epoch_nearing_threshold: StringPrefix, + #[serde(default, skip_serializing_if = "Option::is_none")] + liveness_threshold: Option, + epoch_nearing_threshold: SlotIndex, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] version_signaling: Option, @@ -124,6 +120,7 @@ impl Default for ProtocolParameters { token_supply: 1_813_620_509_061_365, genesis_unix_timestamp: 1582328545, slot_duration_in_seconds: 10, + epoch_nearing_threshold: 20.into(), slots_per_epoch_exponent: Default::default(), mana_generation_rate: Default::default(), mana_generation_rate_exponent: Default::default(), @@ -134,7 +131,6 @@ impl Default for ProtocolParameters { staking_unbonding_period: Default::default(), eviction_age: Default::default(), liveness_threshold: Default::default(), - epoch_nearing_threshold: Default::default(), version_signaling: Default::default(), } } @@ -151,7 +147,7 @@ impl ProtocolParameters { token_supply: u64, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8, - epoch_nearing_threshold: impl Into, + epoch_nearing_threshold: impl Into, ) -> Result { Ok(Self { version, @@ -161,8 +157,7 @@ impl ProtocolParameters { token_supply, genesis_unix_timestamp, slot_duration_in_seconds, - epoch_nearing_threshold: >::try_from(epoch_nearing_threshold.into()) - .map_err(Error::InvalidStringPrefix)?, + epoch_nearing_threshold: epoch_nearing_threshold.into(), ..Default::default() }) } @@ -183,13 +178,18 @@ impl ProtocolParameters { } /// Returns the liveness threshold of the [`ProtocolParameters`]. - pub fn liveness_threshold(&self) -> Option<&str> { - self.liveness_threshold.as_ref().map(|s| s.as_str()) + pub fn liveness_threshold(&self) -> Option { + self.liveness_threshold } /// Returns the epoch nearing threshold of the [`ProtocolParameters`]. - pub fn epoch_nearing_threshold(&self) -> &str { - &self.epoch_nearing_threshold + pub fn epoch_nearing_threshold(&self) -> SlotIndex { + self.epoch_nearing_threshold + } + + pub fn slots_per_epoch_exponent(&self) -> u32 { + self.slots_per_epoch_exponent + .unwrap_or(DEFAULT_SLOTS_PER_EPOCH_EXPONENT) } pub fn slot_index(&self, timestamp: u64) -> SlotIndex { @@ -200,6 +200,11 @@ impl ProtocolParameters { ) } + pub fn epoch_index(&self, timestamp: u64) -> EpochIndex { + self.slot_index(timestamp) + .to_epoch_index(self.slots_per_epoch_exponent()) + } + pub fn hash(&self) -> ProtocolParametersHash { ProtocolParametersHash::new(Blake2b256::digest(self.pack_to_vec()).into()) } @@ -269,7 +274,7 @@ pub fn protocol_parameters() -> ProtocolParameters { 1_813_620_509_061_365, 1582328545, 10, - "TODO", + 20, ) .unwrap() } diff --git a/sdk/src/types/block/rand/output/feature.rs b/sdk/src/types/block/rand/output/feature.rs index 8b964a2cec..fcf1c4fafd 100644 --- a/sdk/src/types/block/rand/output/feature.rs +++ b/sdk/src/types/block/rand/output/feature.rs @@ -52,7 +52,7 @@ pub fn rand_block_issuer_feature() -> BlockIssuerFeature { /// Generates a random [`StakingFeature`]. pub fn rand_staking_feature() -> StakingFeature { - StakingFeature::new(rand_number(), rand_number(), rand_number(), rand_number()) + StakingFeature::new(rand_number(), rand_number(), rand_number::(), rand_number::()) } fn rand_feature_from_flag(flag: &FeatureFlags) -> Feature { diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs new file mode 100644 index 0000000000..2297b3684a --- /dev/null +++ b/sdk/src/types/block/slot/epoch.rs @@ -0,0 +1,29 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use derive_more::{Deref, Display, From, FromStr}; + +/// Timeline is divided into slots, and each epoch has a corresponding epoch index. +/// To calculate the epoch index of a timestamp, `slotsPerEpochExponent` and `slotDurationInSeconds` are needed. +/// An epoch consists of `2^slotsPerEpochExponent` slots. +#[derive( + Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, Deref, Display, FromStr, packable::Packable, +)] +#[repr(transparent)] +pub struct EpochIndex(u64); + +impl EpochIndex { + /// Creates a new [`EpochIndex`]. + pub fn new(index: u64) -> Self { + Self::from(index) + } +} + +impl From for u64 { + fn from(slot_index: EpochIndex) -> Self { + *slot_index + } +} + +#[cfg(feature = "serde")] +string_serde_impl!(EpochIndex); diff --git a/sdk/src/types/block/slot/index.rs b/sdk/src/types/block/slot/index.rs index 84625885de..af0f04a773 100644 --- a/sdk/src/types/block/slot/index.rs +++ b/sdk/src/types/block/slot/index.rs @@ -3,6 +3,8 @@ use derive_more::{Deref, Display, From, FromStr}; +use super::EpochIndex; + /// Timeline is divided into slots, and each slot has a corresponding slot index. /// To calculate the slot index of a timestamp, `genesisTimestamp` and the duration of a slot are needed. /// The slot index of timestamp `ts` is `(ts - genesisTimestamp)/duration + 1`. @@ -17,6 +19,14 @@ impl SlotIndex { pub fn new(index: u64) -> Self { Self::from(index) } + + pub fn to_epoch_index(self, slots_per_epoch_exponent: u32) -> EpochIndex { + EpochIndex::new((self.0 >> slots_per_epoch_exponent) + 1) + } + + pub fn as_timestamp(self, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> u32 { + (((self.0 - 1) * slot_duration_in_seconds as u64) + genesis_unix_timestamp as u64) as _ + } } impl From for u64 { diff --git a/sdk/src/types/block/slot/mod.rs b/sdk/src/types/block/slot/mod.rs index d3a21fc517..ef8e74277b 100644 --- a/sdk/src/types/block/slot/mod.rs +++ b/sdk/src/types/block/slot/mod.rs @@ -3,7 +3,10 @@ mod commitment; mod commitment_id; +mod epoch; mod index; mod roots_id; -pub use self::{commitment::SlotCommitment, commitment_id::SlotCommitmentId, index::SlotIndex, roots_id::RootsId}; +pub use self::{ + commitment::SlotCommitment, commitment_id::SlotCommitmentId, epoch::EpochIndex, index::SlotIndex, roots_id::RootsId, +}; diff --git a/sdk/src/wallet/account/mod.rs b/sdk/src/wallet/account/mod.rs index 83f3126c58..40f7cc1605 100644 --- a/sdk/src/wallet/account/mod.rs +++ b/sdk/src/wallet/account/mod.rs @@ -618,7 +618,7 @@ fn serialize() { 1_813_620_509_061_365, 1582328545, 10, - "TODO", + 20, ) .unwrap(); diff --git a/sdk/tests/client/client_builder.rs b/sdk/tests/client/client_builder.rs index 5f0bf70607..d14e4e7108 100644 --- a/sdk/tests/client/client_builder.rs +++ b/sdk/tests/client/client_builder.rs @@ -45,7 +45,7 @@ async fn client_builder() { "tokenSupply":"1813620509061365", "genesisUnixTimestamp":"1582328545", "slotDurationInSeconds":10, - "epochNearingThreshold":"TODO" + "epochNearingThreshold":"30" }, "apiTimeout":{ "secs":15, diff --git a/sdk/tests/client/error.rs b/sdk/tests/client/error.rs index ace934cc9f..b77cc7fd79 100644 --- a/sdk/tests/client/error.rs +++ b/sdk/tests/client/error.rs @@ -16,7 +16,7 @@ fn stringified_error() { let error = Error::TimeNotSynced { current_time: 0, - milestone_timestamp: 10000, + latest_finalized_slot_timestamp: 10000, }; assert_eq!( &serde_json::to_string(&error).unwrap(), From c342573f7504b3d9897966afe25a70a56729f108 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Mon, 21 Aug 2023 10:24:19 -0400 Subject: [PATCH 06/28] fix tests, wasm --- sdk/src/client/core.rs | 2 +- sdk/tests/client/error.rs | 2 +- sdk/tests/types/block_id.rs | 17 ++++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/sdk/src/client/core.rs b/sdk/src/client/core.rs index 309582cb30..d21ab3a995 100644 --- a/sdk/src/client/core.rs +++ b/sdk/src/client/core.rs @@ -109,7 +109,7 @@ impl ClientInner { } let info = self.get_info().await?.node_info; let mut client_network_info = self.network_info.write().await; - client_network_info.protocol_parameters = info.protocol_parameters[0].parameters.clone(); + client_network_info.protocol_parameters = info.latest_protocol_parameters().clone(); *LAST_SYNC.lock().unwrap() = Some(current_time + CACHE_NETWORK_INFO_TIMEOUT_IN_SECONDS); } diff --git a/sdk/tests/client/error.rs b/sdk/tests/client/error.rs index b77cc7fd79..fe951d8326 100644 --- a/sdk/tests/client/error.rs +++ b/sdk/tests/client/error.rs @@ -20,7 +20,7 @@ fn stringified_error() { }; assert_eq!( &serde_json::to_string(&error).unwrap(), - "{\"type\":\"timeNotSynced\",\"error\":\"local time 0 doesn't match the time of the latest milestone timestamp: 10000\"}" + "{\"type\":\"timeNotSynced\",\"error\":\"local time 0 doesn't match the time of the latest finalized slot timestamp: 10000\"}" ); let error = Error::PlaceholderSecretManager; diff --git a/sdk/tests/types/block_id.rs b/sdk/tests/types/block_id.rs index a931a9b659..2d77ae1577 100644 --- a/sdk/tests/types/block_id.rs +++ b/sdk/tests/types/block_id.rs @@ -4,7 +4,7 @@ use core::str::FromStr; use iota_sdk::types::block::{ - protocol::ProtocolParameters, rand::bytes::rand_bytes_array, Block, BlockDto, BlockHash, BlockId, + protocol::ProtocolParameters, rand::bytes::rand_bytes_array, slot::SlotIndex, Block, BlockDto, BlockHash, BlockId, }; use packable::PackableExt; @@ -67,10 +67,13 @@ fn memory_layout() { fn compute() { let protocol_parameters = ProtocolParameters::default(); let protocol_parameters_hash = protocol_parameters.hash(); - let slot_index = 11_u64; - let issuing_time = protocol_parameters.genesis_unix_timestamp() as u64 - + (slot_index - 1) * protocol_parameters.slot_duration_in_seconds() as u64; + let slot_index = SlotIndex::new(11_u64); + let issuing_time = slot_index.as_timestamp( + protocol_parameters.genesis_unix_timestamp(), + protocol_parameters.slot_duration_in_seconds(), + ); let network_id = protocol_parameters.network_id(); + let block_dto_json = serde_json::json!({ "protocolVersion": 3, "networkId": network_id.to_string(), @@ -99,11 +102,11 @@ fn compute() { // TODO: Independently verify this value assert_eq!( block_id.to_string(), - "0x5966461e29acde4d3ee431fa740aff3092368de4a655b96b4177e88d7050431a0b00000000000000" + "0xb33499435538bae18845c5c80f7ff66495a336f8a08b75eea9b8699b4b38c9fc0b00000000000000" ); assert_eq!( block_id.hash().to_string(), - "0x5966461e29acde4d3ee431fa740aff3092368de4a655b96b4177e88d7050431a" + "0xb33499435538bae18845c5c80f7ff66495a336f8a08b75eea9b8699b4b38c9fc" ); - assert_eq!(*block_id.slot_index(), slot_index); + assert_eq!(block_id.slot_index(), slot_index); } From f8010d1bf2dc0ae587267ac3c3acf7967aed87bc Mon Sep 17 00:00:00 2001 From: Alexandcoats Date: Tue, 22 Aug 2023 08:03:31 -0400 Subject: [PATCH 07/28] copy/paste oopsie Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- sdk/src/types/block/slot/epoch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 2297b3684a..febd2dd0fc 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -20,8 +20,8 @@ impl EpochIndex { } impl From for u64 { - fn from(slot_index: EpochIndex) -> Self { - *slot_index + fn from(epoch_index: EpochIndex) -> Self { + *epoch_index } } From 4a3c84557a511799b24642a9707c1bf612e8332a Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Tue, 22 Aug 2023 09:41:45 -0400 Subject: [PATCH 08/28] tangle time --- sdk/src/client/api/high_level.rs | 8 ++------ sdk/src/client/builder.rs | 10 +++++----- sdk/src/client/node_manager/syncing.rs | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/sdk/src/client/api/high_level.rs b/sdk/src/client/api/high_level.rs index ea65e0ee82..cecc035088 100644 --- a/sdk/src/client/api/high_level.rs +++ b/sdk/src/client/api/high_level.rs @@ -121,12 +121,8 @@ impl Client { let network_info = self.get_network_info().await?; - if let Some(latest_finalized_slot_timestamp) = network_info.latest_finalized_slot.map(|idx| { - idx.as_timestamp( - network_info.protocol_parameters.genesis_unix_timestamp(), - network_info.protocol_parameters.slot_duration_in_seconds(), - ) - }) { + if let Some(latest_finalized_slot_timestamp) = network_info.tangle_time { + let latest_finalized_slot_timestamp = latest_finalized_slot_timestamp as u32; // Check the local time is in the range of +-5 minutes of the node to prevent locking funds by accident if !(latest_finalized_slot_timestamp - FIVE_MINUTES_IN_SECONDS ..latest_finalized_slot_timestamp + FIVE_MINUTES_IN_SECONDS) diff --git a/sdk/src/client/builder.rs b/sdk/src/client/builder.rs index 46b2ed81ab..5a7407e629 100644 --- a/sdk/src/client/builder.rs +++ b/sdk/src/client/builder.rs @@ -19,7 +19,7 @@ use crate::{ }, Client, }, - types::block::{protocol::ProtocolParameters, slot::SlotIndex}, + types::block::protocol::ProtocolParameters, }; /// Builder to construct client instance with sensible default values @@ -263,9 +263,9 @@ pub struct NetworkInfo { /// Protocol parameters. #[serde(default)] pub protocol_parameters: ProtocolParameters, - /// The latest finalized slot index. + /// The current tangle time. #[serde(skip)] - pub latest_finalized_slot: Option, + pub tangle_time: Option, } impl NetworkInfo { @@ -274,8 +274,8 @@ impl NetworkInfo { self } - pub fn with_latest_finalized_slot(mut self, latest_finalized_slot: impl Into) -> Self { - self.latest_finalized_slot = Some(latest_finalized_slot.into()); + pub fn with_tangle_time(mut self, tangle_time: u64) -> Self { + self.tangle_time = Some(tangle_time.into()); self } } diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index 3bd9d4c363..0eb2b7ed0c 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -105,7 +105,7 @@ impl ClientInner { if let Some((info, _node_url)) = nodes.first() { let mut network_info = self.network_info.write().await; - network_info.latest_finalized_slot = Some(info.status.latest_finalized_slot); + network_info.tangle_time = Some(info.status.relative_accepted_tangle_time); network_info.protocol_parameters = info.latest_protocol_parameters().clone(); } From a96ad21a03ae554224b04d65e36392e9b6759209 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Tue, 22 Aug 2023 10:14:00 -0400 Subject: [PATCH 09/28] suggestions --- sdk/src/client/api/high_level.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sdk/src/client/api/high_level.rs b/sdk/src/client/api/high_level.rs index cecc035088..6d2485cd69 100644 --- a/sdk/src/client/api/high_level.rs +++ b/sdk/src/client/api/high_level.rs @@ -121,16 +121,13 @@ impl Client { let network_info = self.get_network_info().await?; - if let Some(latest_finalized_slot_timestamp) = network_info.tangle_time { - let latest_finalized_slot_timestamp = latest_finalized_slot_timestamp as u32; + if let Some(tangle_time) = network_info.tangle_time { + let tangle_time = tangle_time as u32; // Check the local time is in the range of +-5 minutes of the node to prevent locking funds by accident - if !(latest_finalized_slot_timestamp - FIVE_MINUTES_IN_SECONDS - ..latest_finalized_slot_timestamp + FIVE_MINUTES_IN_SECONDS) - .contains(¤t_time) - { + if !(tangle_time - FIVE_MINUTES_IN_SECONDS..tangle_time + FIVE_MINUTES_IN_SECONDS).contains(¤t_time) { return Err(Error::TimeNotSynced { current_time, - latest_finalized_slot_timestamp, + latest_finalized_slot_timestamp: tangle_time, }); } } From c3da95edbce2226df86255864e43f51fe59060ba Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Tue, 22 Aug 2023 15:29:12 -0400 Subject: [PATCH 10/28] more renames --- sdk/src/client/error.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs index baab10e02f..dad148f96e 100644 --- a/sdk/src/client/error.rs +++ b/sdk/src/client/error.rs @@ -128,15 +128,13 @@ pub enum Error { /// Tokio task join error #[error("{0}")] TaskJoin(#[from] tokio::task::JoinError), - /// Local time doesn't match the time of the latest milestone timestamp - #[error( - "local time {current_time} doesn't match the time of the latest finalized slot timestamp: {latest_finalized_slot_timestamp}" - )] + /// Local time doesn't match the network time + #[error("local time {current_time} doesn't match the tangle time: {tangle_time}")] TimeNotSynced { /// The local time. current_time: u32, - /// The timestamp of the latest finalized slot. - latest_finalized_slot_timestamp: u32, + /// The tangle time. + tangle_time: u32, }, /// The semantic validation of a transaction failed. #[error("the semantic validation of a transaction failed with conflict reason: {} - {0:?}", *.0 as u8)] From 7e11228375dbd2f827c8144032372f1cee158102 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 08:52:42 -0400 Subject: [PATCH 11/28] More cleanup --- sdk/src/types/block/slot/epoch.rs | 3 ++- sdk/src/types/block/slot/index.rs | 5 +++-- sdk/src/utils/serde.rs | 27 --------------------------- sdk/tests/client/error.rs | 2 +- 4 files changed, 6 insertions(+), 31 deletions(-) diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index febd2dd0fc..9872db9ef3 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -3,7 +3,8 @@ use derive_more::{Deref, Display, From, FromStr}; -/// Timeline is divided into slots, and each epoch has a corresponding epoch index. +/// The tangle timeline is divided into epochs, and each epoch has a corresponding epoch index. Epochs are further +/// subdivided into slots, each with a [`SlotIndex`](super::SlotIndex). /// To calculate the epoch index of a timestamp, `slotsPerEpochExponent` and `slotDurationInSeconds` are needed. /// An epoch consists of `2^slotsPerEpochExponent` slots. #[derive( diff --git a/sdk/src/types/block/slot/index.rs b/sdk/src/types/block/slot/index.rs index af0f04a773..36874ef1b9 100644 --- a/sdk/src/types/block/slot/index.rs +++ b/sdk/src/types/block/slot/index.rs @@ -5,8 +5,9 @@ use derive_more::{Deref, Display, From, FromStr}; use super::EpochIndex; -/// Timeline is divided into slots, and each slot has a corresponding slot index. -/// To calculate the slot index of a timestamp, `genesisTimestamp` and the duration of a slot are needed. +/// The tangle timeline is divided into epochs, and each epoch has a corresponding [`EpochIndex`]. Epochs are further +/// subdivided into slots, each with a slot index. +/// To calculate the slot index of a timestamp, `genesisUnixTimestamp` and the `slotDurationInSeconds` are needed. /// The slot index of timestamp `ts` is `(ts - genesisTimestamp)/duration + 1`. #[derive( Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, Deref, Display, FromStr, packable::Packable, diff --git a/sdk/src/utils/serde.rs b/sdk/src/utils/serde.rs index 4e2859a46e..b6363fef80 100644 --- a/sdk/src/utils/serde.rs +++ b/sdk/src/utils/serde.rs @@ -131,33 +131,6 @@ pub mod string_prefix { } } -pub mod option_string_prefix { - use alloc::string::String; - - use packable::{bounded::Bounded, prefix::StringPrefix}; - use serde::{de, Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &Option>, serializer: S) -> Result - where - S: Serializer, - { - match value { - Some(bytes) => super::string_prefix::serialize(bytes, serializer), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, T: Bounded, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - >::Error: core::fmt::Display, - { - Option::::deserialize(deserializer) - .map_err(de::Error::custom) - .and_then(|s| s.map(|s| s.try_into().map_err(de::Error::custom)).transpose()) - } -} - pub mod boxed_slice_prefix { use alloc::boxed::Box; diff --git a/sdk/tests/client/error.rs b/sdk/tests/client/error.rs index 531b5fc8e8..e89240d421 100644 --- a/sdk/tests/client/error.rs +++ b/sdk/tests/client/error.rs @@ -20,7 +20,7 @@ fn stringified_error() { }; assert_eq!( &serde_json::to_string(&error).unwrap(), - "{\"type\":\"timeNotSynced\",\"error\":\"local time 0 doesn't match the time of the latest finalized slot timestamp: 10000\"}" + "{\"type\":\"timeNotSynced\",\"error\":\"local time 0 doesn't match the tangle time: 10000\"}" ); let error = Error::PlaceholderSecretManager; From d68245081bc0985cd5eb659e2cd06a23fa507c7d Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 12:30:25 -0400 Subject: [PATCH 12/28] as -> to --- sdk/src/types/block/slot/index.rs | 2 +- sdk/tests/types/block_id.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/block/slot/index.rs b/sdk/src/types/block/slot/index.rs index 36874ef1b9..f2241bb47e 100644 --- a/sdk/src/types/block/slot/index.rs +++ b/sdk/src/types/block/slot/index.rs @@ -25,7 +25,7 @@ impl SlotIndex { EpochIndex::new((self.0 >> slots_per_epoch_exponent) + 1) } - pub fn as_timestamp(self, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> u32 { + pub fn to_timestamp(self, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> u32 { (((self.0 - 1) * slot_duration_in_seconds as u64) + genesis_unix_timestamp as u64) as _ } } diff --git a/sdk/tests/types/block_id.rs b/sdk/tests/types/block_id.rs index 2d77ae1577..81ee39b788 100644 --- a/sdk/tests/types/block_id.rs +++ b/sdk/tests/types/block_id.rs @@ -68,7 +68,7 @@ fn compute() { let protocol_parameters = ProtocolParameters::default(); let protocol_parameters_hash = protocol_parameters.hash(); let slot_index = SlotIndex::new(11_u64); - let issuing_time = slot_index.as_timestamp( + let issuing_time = slot_index.to_timestamp( protocol_parameters.genesis_unix_timestamp(), protocol_parameters.slot_duration_in_seconds(), ); From e4cc3a5f7b6a0826944ccef637df5804dcb20ed5 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 14:02:51 -0400 Subject: [PATCH 13/28] harmony and peace --- sdk/src/types/api/core/response.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index b67cf28676..5adc04a814 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -35,11 +35,11 @@ impl InfoResponse { self.protocol_parameters.latest() } - pub fn parameters_by_version(&self, protocol_version: u8) -> Option<&ProtocolParameters> { + pub fn protocol_parameters_by_version(&self, protocol_version: u8) -> Option<&ProtocolParameters> { self.protocol_parameters.by_version(protocol_version) } - pub fn parameters_by_epoch(&self, epoch_index: EpochIndex) -> Option<&ProtocolParameters> { + pub fn protocol_parameters_by_epoch(&self, epoch_index: EpochIndex) -> Option<&ProtocolParameters> { self.protocol_parameters.by_epoch(epoch_index) } } From 13b2a7c2169fae0862831bdfeb8048a1e116ec56 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 14:27:26 -0400 Subject: [PATCH 14/28] derive --- sdk/src/types/block/protocol.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 3cbf12f2cf..6a6f419447 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -94,6 +94,7 @@ pub struct ProtocolParameters { #[getset(get_copy = "pub")] eviction_age: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + #[getset(get_copy = "pub")] liveness_threshold: Option, epoch_nearing_threshold: SlotIndex, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -177,11 +178,6 @@ impl ProtocolParameters { self.mana_decay_factors.as_ref().map(|slice| slice.as_ref()) } - /// Returns the liveness threshold of the [`ProtocolParameters`]. - pub fn liveness_threshold(&self) -> Option { - self.liveness_threshold - } - /// Returns the epoch nearing threshold of the [`ProtocolParameters`]. pub fn epoch_nearing_threshold(&self) -> SlotIndex { self.epoch_nearing_threshold From 668b190fa5a10bd8759c80e3f03602d10bc8f492 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 14:29:43 -0400 Subject: [PATCH 15/28] suggestion --- sdk/src/types/block/protocol.rs | 6 +----- sdk/src/types/block/slot/index.rs | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 6a6f419447..fec8b5dce9 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -189,7 +189,7 @@ impl ProtocolParameters { } pub fn slot_index(&self, timestamp: u64) -> SlotIndex { - calc_slot_index( + SlotIndex::from_timestamp( timestamp, self.genesis_unix_timestamp(), self.slot_duration_in_seconds(), @@ -206,10 +206,6 @@ impl ProtocolParameters { } } -pub fn calc_slot_index(timestamp: u64, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> SlotIndex { - (1 + (timestamp - genesis_unix_timestamp as u64) / slot_duration_in_seconds as u64).into() -} - #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] #[cfg_attr( feature = "serde", diff --git a/sdk/src/types/block/slot/index.rs b/sdk/src/types/block/slot/index.rs index f2241bb47e..f8f95a5151 100644 --- a/sdk/src/types/block/slot/index.rs +++ b/sdk/src/types/block/slot/index.rs @@ -25,6 +25,10 @@ impl SlotIndex { EpochIndex::new((self.0 >> slots_per_epoch_exponent) + 1) } + pub fn from_timestamp(timestamp: u64, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> SlotIndex { + (1 + (timestamp - genesis_unix_timestamp as u64) / slot_duration_in_seconds as u64).into() + } + pub fn to_timestamp(self, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> u32 { (((self.0 - 1) * slot_duration_in_seconds as u64) + genesis_unix_timestamp as u64) as _ } From ecb32124d0bc0339dd9f02bf52ee72fa3a4385dd Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 14:30:19 -0400 Subject: [PATCH 16/28] another missed derive --- sdk/src/types/block/protocol.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index fec8b5dce9..210b7f2c5d 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -96,6 +96,7 @@ pub struct ProtocolParameters { #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] liveness_threshold: Option, + #[getset(get_copy = "pub")] epoch_nearing_threshold: SlotIndex, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] @@ -178,11 +179,6 @@ impl ProtocolParameters { self.mana_decay_factors.as_ref().map(|slice| slice.as_ref()) } - /// Returns the epoch nearing threshold of the [`ProtocolParameters`]. - pub fn epoch_nearing_threshold(&self) -> SlotIndex { - self.epoch_nearing_threshold - } - pub fn slots_per_epoch_exponent(&self) -> u32 { self.slots_per_epoch_exponent .unwrap_or(DEFAULT_SLOTS_PER_EPOCH_EXPONENT) From 2f77a9cb0f40e0bb8c6d0420b05971145eb93094 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 14:45:00 -0400 Subject: [PATCH 17/28] oops --- sdk/src/client/node_manager/syncing.rs | 1 + sdk/src/types/api/core/response.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index 0eb2b7ed0c..d28ddbf8a3 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -73,6 +73,7 @@ impl ClientInner { match crate::client::Client::get_node_info(node.url.as_ref(), node.auth.clone()).await { Ok(info) => { if info.status.is_healthy || ignore_node_health { + // TODO: What about future parameters? let network_name = info.latest_protocol_parameters().network_name(); match network_nodes.get_mut(network_name) { Some(network_node_entry) => { diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index 5adc04a814..49362ac485 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -71,8 +71,8 @@ pub struct StatusResponse { pub relative_confirmed_tangle_time: u64, pub latest_commitment_id: SlotCommitmentId, pub latest_finalized_slot: SlotIndex, - pub latest_accepted_block_slot: BlockId, - pub latest_confirmed_block_slot: BlockId, + pub latest_accepted_block_slot: SlotIndex, + pub latest_confirmed_block_slot: SlotIndex, pub pruning_slot: SlotIndex, } From bf7d87bacd4a7d4899daf91b4ca45e0503f584e4 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 17:45:36 -0400 Subject: [PATCH 18/28] Always get v3 params for now. Add flexible epoch index calculation for future proofing --- sdk/src/client/node_manager/syncing.rs | 20 +++++-- sdk/src/types/api/core/response.rs | 75 ++++++++++++-------------- sdk/src/types/block/error.rs | 3 ++ sdk/src/types/block/protocol.rs | 13 ++--- sdk/src/types/block/slot/epoch.rs | 42 ++++++++++++++- sdk/src/types/block/slot/index.rs | 15 ++++-- 6 files changed, 109 insertions(+), 59 deletions(-) diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index d28ddbf8a3..bc13413663 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -9,7 +9,10 @@ use { }; use super::{Node, NodeManager}; -use crate::client::{Client, ClientInner, Error, Result}; +use crate::{ + client::{Client, ClientInner, Error, Result}, + types::block::PROTOCOL_VERSION, +}; impl ClientInner { /// Get a node candidate from the healthy node pool. @@ -73,8 +76,12 @@ impl ClientInner { match crate::client::Client::get_node_info(node.url.as_ref(), node.auth.clone()).await { Ok(info) => { if info.status.is_healthy || ignore_node_health { - // TODO: What about future parameters? - let network_name = info.latest_protocol_parameters().network_name(); + // Unwrap: We should always have parameters for this version. If we don't we can't recover. + let network_name = info + .protocol_parameters_by_version(PROTOCOL_VERSION) + .expect("missing v3 protocol parameters") + .parameters + .network_name(); match network_nodes.get_mut(network_name) { Some(network_node_entry) => { network_node_entry.push((info, node.clone())); @@ -107,7 +114,12 @@ impl ClientInner { let mut network_info = self.network_info.write().await; network_info.tangle_time = Some(info.status.relative_accepted_tangle_time); - network_info.protocol_parameters = info.latest_protocol_parameters().clone(); + // Unwrap: We should always have parameters for this version. If we don't we can't recover. + network_info.protocol_parameters = info + .protocol_parameters_by_version(PROTOCOL_VERSION) + .expect("missing v3 protocol parameters") + .parameters + .clone(); } for (info, node_url) in nodes { diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index 49362ac485..b661f0451b 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -25,21 +25,21 @@ pub struct InfoResponse { pub version: String, pub status: StatusResponse, pub metrics: MetricsResponse, - pub protocol_parameters: ProtocolParametersResponse, + pub protocol_parameters: ProtocolParametersMap, pub base_token: BaseTokenResponse, pub features: Box<[String]>, } impl InfoResponse { - pub fn latest_protocol_parameters(&self) -> &ProtocolParameters { + pub fn latest_protocol_parameters(&self) -> &ProtocolParametersResponse { self.protocol_parameters.latest() } - pub fn protocol_parameters_by_version(&self, protocol_version: u8) -> Option<&ProtocolParameters> { + pub fn protocol_parameters_by_version(&self, protocol_version: u8) -> Option<&ProtocolParametersResponse> { self.protocol_parameters.by_version(protocol_version) } - pub fn protocol_parameters_by_epoch(&self, epoch_index: EpochIndex) -> Option<&ProtocolParameters> { + pub fn protocol_parameters_by_epoch(&self, epoch_index: EpochIndex) -> Option<&ProtocolParametersResponse> { self.protocol_parameters.by_epoch(epoch_index) } } @@ -94,77 +94,70 @@ pub struct MetricsResponse { } #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct ProtocolParametersResponse { - parameters: Box<[ProtocolParameters]>, + pub parameters: ProtocolParameters, + pub start_epoch: EpochIndex, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolParametersMap { + parameters: Box<[ProtocolParametersResponse]>, version_map: BTreeMap, epoch_map: BTreeMap, } -impl ProtocolParametersResponse { - pub fn iter(&self) -> impl Iterator { - self.epoch_map.iter().map(|(&epoch, &i)| (epoch, &self.parameters[i])) +impl ProtocolParametersMap { + pub fn iter(&self) -> impl Iterator { + self.parameters.iter() } - pub fn latest(&self) -> &ProtocolParameters { + pub fn latest(&self) -> &ProtocolParametersResponse { &self.parameters[*self.version_map.last_key_value().unwrap().1] } - pub fn by_version(&self, protocol_version: u8) -> Option<&ProtocolParameters> { + pub fn by_version(&self, protocol_version: u8) -> Option<&ProtocolParametersResponse> { self.version_map.get(&protocol_version).map(|&i| &self.parameters[i]) } - pub fn by_epoch(&self, epoch_index: EpochIndex) -> Option<&ProtocolParameters> { - self.epoch_map.get(&epoch_index).map(|&i| &self.parameters[i]) + pub fn by_epoch(&self, epoch_index: EpochIndex) -> Option<&ProtocolParametersResponse> { + self.epoch_map + .range(..=epoch_index) + .map(|(_, &i)| &self.parameters[i]) + .last() } } #[cfg(feature = "serde")] mod serde_protocol_params_response { - use alloc::borrow::Cow; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; use super::*; - #[derive(Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - struct ProtocolParametersResponseDto<'a> { - parameters: Cow<'a, ProtocolParameters>, - start_epoch: EpochIndex, - } - - impl Serialize for ProtocolParametersResponse { + impl Serialize for ProtocolParametersMap { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - serializer.collect_seq( - self.iter() - .map(|(start_epoch, parameters)| ProtocolParametersResponseDto { - parameters: Cow::Borrowed(parameters), - start_epoch, - }), - ) + serializer.collect_seq(self.iter()) } } - impl<'de> Deserialize<'de> for ProtocolParametersResponse { + impl<'de> Deserialize<'de> for ProtocolParametersMap { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - let parameters = Vec::>::deserialize(deserializer)?; + let parameters = Box::<[ProtocolParametersResponse]>::deserialize(deserializer)?; let (mut version_map, mut epoch_map) = (BTreeMap::default(), BTreeMap::default()); - let parameters = parameters - .into_iter() - .enumerate() - .map(|(i, res)| { - let (start_epoch, parameters) = (res.start_epoch, res.parameters.into_owned()); - version_map.insert(parameters.version(), i); - epoch_map.insert(start_epoch, i); - parameters - }) - .collect(); + for (i, res) in parameters.iter().enumerate() { + version_map.insert(res.parameters.version(), i); + epoch_map.insert(res.start_epoch, i); + } Ok(Self { parameters, version_map, diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index b83ab7e937..903abeef9a 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -12,6 +12,7 @@ use packable::prefix::UnpackPrefixError; use prefix_hex::Error as HexError; use primitive_types::U256; +use super::slot::EpochIndex; use crate::types::block::{ context_input::RewardContextInputIndex, input::UtxoInput, @@ -113,6 +114,7 @@ pub enum Error { InvalidSignature, InvalidSignatureKind(u8), InvalidPublicKeyKind(u8), + InvalidStartEpoch(EpochIndex), InvalidStringPrefix(>::Error), InvalidTaggedDataLength(>::Error), InvalidTagFeatureLength(>::Error), @@ -280,6 +282,7 @@ impl fmt::Display for Error { Self::InvalidSignature => write!(f, "invalid signature provided"), Self::InvalidSignatureKind(k) => write!(f, "invalid signature kind: {k}"), Self::InvalidPublicKeyKind(k) => write!(f, "invalid public key kind: {k}"), + Self::InvalidStartEpoch(index) => write!(f, "invalid start epoch: {index}"), Self::InvalidStringPrefix(p) => write!(f, "invalid string prefix: {p}"), Self::InvalidTaggedDataLength(length) => { write!(f, "invalid tagged data length {length}") diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 210b7f2c5d..d384ffa57e 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -11,10 +11,7 @@ use packable::{ Packable, PackableExt, }; -use super::{ - address::Hrp, - slot::{EpochIndex, SlotIndex}, -}; +use super::{address::Hrp, slot::SlotIndex}; use crate::types::block::{ error::UnpackPrefixOptionErrorExt, helper::network_name_to_id, output::RentStructure, ConvertTo, Error, PROTOCOL_VERSION, @@ -179,11 +176,13 @@ impl ProtocolParameters { self.mana_decay_factors.as_ref().map(|slice| slice.as_ref()) } + /// Returns the slots per epoch exponent of the [`ProtocolParameters`]. pub fn slots_per_epoch_exponent(&self) -> u32 { self.slots_per_epoch_exponent .unwrap_or(DEFAULT_SLOTS_PER_EPOCH_EXPONENT) } + /// Gets a [`SlotIndex`] from a unix timestamp. pub fn slot_index(&self, timestamp: u64) -> SlotIndex { SlotIndex::from_timestamp( timestamp, @@ -192,11 +191,7 @@ impl ProtocolParameters { ) } - pub fn epoch_index(&self, timestamp: u64) -> EpochIndex { - self.slot_index(timestamp) - .to_epoch_index(self.slots_per_epoch_exponent()) - } - + /// Returns the hash of the [`ProtocolParameters`]. pub fn hash(&self) -> ProtocolParametersHash { ProtocolParametersHash::new(Blake2b256::digest(self.pack_to_vec()).into()) } diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 9872db9ef3..4dc3c58fd3 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -3,8 +3,11 @@ use derive_more::{Deref, Display, From, FromStr}; +use super::SlotIndex; +use crate::types::block::Error; + /// The tangle timeline is divided into epochs, and each epoch has a corresponding epoch index. Epochs are further -/// subdivided into slots, each with a [`SlotIndex`](super::SlotIndex). +/// subdivided into slots, each with a [`SlotIndex`]. /// To calculate the epoch index of a timestamp, `slotsPerEpochExponent` and `slotDurationInSeconds` are needed. /// An epoch consists of `2^slotsPerEpochExponent` slots. #[derive( @@ -18,6 +21,43 @@ impl EpochIndex { pub fn new(index: u64) -> Self { Self::from(index) } + + /// Gets the epoch index given a [`SlotIndex`]. + pub fn from_slot_index( + slot_index: SlotIndex, + slots_per_epoch_exponent_iter: impl Iterator, + ) -> Result { + let mut slot_index = *slot_index; + // TODO: Should this start at 1? + let mut res = 0; + let mut last = None; + for (start_epoch, exponent) in slots_per_epoch_exponent_iter { + if *start_epoch > res { + // We can't calculate the epoch if we don't have the exponent for the containing range + if slot_index > 0 { + return Err(Error::InvalidStartEpoch(start_epoch)); + } else { + break; + } + } + if let Some((last_start_epoch, last_exponent)) = last { + // Get the number of slots this range of epochs represents + let slots_in_range = (*start_epoch - last_start_epoch) << last_exponent; + // Check whether the slot index is contained in this range + if slot_index > slots_in_range { + // Update the slot index so it is in the context of the next epoch + slot_index -= slots_in_range; + } + } + res = *start_epoch; + last = Some((*start_epoch, exponent)); + } + if let Some((_, exponent)) = last { + res += slot_index >> exponent; + } + // TODO: Do we need to add one? + Ok(Self(res + 1)) + } } impl From for u64 { diff --git a/sdk/src/types/block/slot/index.rs b/sdk/src/types/block/slot/index.rs index f8f95a5151..3f79c98c55 100644 --- a/sdk/src/types/block/slot/index.rs +++ b/sdk/src/types/block/slot/index.rs @@ -4,6 +4,7 @@ use derive_more::{Deref, Display, From, FromStr}; use super::EpochIndex; +use crate::types::block::Error; /// The tangle timeline is divided into epochs, and each epoch has a corresponding [`EpochIndex`]. Epochs are further /// subdivided into slots, each with a slot index. @@ -21,16 +22,22 @@ impl SlotIndex { Self::from(index) } - pub fn to_epoch_index(self, slots_per_epoch_exponent: u32) -> EpochIndex { - EpochIndex::new((self.0 >> slots_per_epoch_exponent) + 1) + /// Gets the [`EpochIndex`] of this slot. + pub fn to_epoch_index( + self, + slots_per_epoch_exponent_iter: impl Iterator, + ) -> Result { + EpochIndex::from_slot_index(self, slots_per_epoch_exponent_iter) } + /// Gets the slot index of a unix timestamp. pub fn from_timestamp(timestamp: u64, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> SlotIndex { (1 + (timestamp - genesis_unix_timestamp as u64) / slot_duration_in_seconds as u64).into() } - pub fn to_timestamp(self, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> u32 { - (((self.0 - 1) * slot_duration_in_seconds as u64) + genesis_unix_timestamp as u64) as _ + /// Converts the slot index into the corresponding unix timestamp. + pub fn to_timestamp(self, genesis_unix_timestamp: u32, slot_duration_in_seconds: u8) -> u64 { + ((self.0 - 1) * slot_duration_in_seconds as u64) + genesis_unix_timestamp as u64 } } From b58353a455d44e182206315740dfa8ae58550475 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 23 Aug 2023 17:50:54 -0400 Subject: [PATCH 19/28] fix wasm --- sdk/src/client/core.rs | 8 ++++++-- sdk/src/client/node_manager/syncing.rs | 6 ++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/sdk/src/client/core.rs b/sdk/src/client/core.rs index d21ab3a995..f0222c11d1 100644 --- a/sdk/src/client/core.rs +++ b/sdk/src/client/core.rs @@ -14,7 +14,7 @@ use { }; #[cfg(target_family = "wasm")] -use crate::client::constants::CACHE_NETWORK_INFO_TIMEOUT_IN_SECONDS; +use crate::{client::constants::CACHE_NETWORK_INFO_TIMEOUT_IN_SECONDS, types::block::PROTOCOL_VERSION}; use crate::{ client::{ builder::{ClientBuilder, NetworkInfo}, @@ -109,7 +109,11 @@ impl ClientInner { } let info = self.get_info().await?.node_info; let mut client_network_info = self.network_info.write().await; - client_network_info.protocol_parameters = info.latest_protocol_parameters().clone(); + client_network_info.protocol_parameters = info + .protocol_parameters_by_version(PROTOCOL_VERSION) + .expect("missing v3 protocol parameters") + .parameters + .clone(); *LAST_SYNC.lock().unwrap() = Some(current_time + CACHE_NETWORK_INFO_TIMEOUT_IN_SECONDS); } diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index bc13413663..fc9b358278 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -4,15 +4,13 @@ #[cfg(not(target_family = "wasm"))] use { crate::types::api::core::response::InfoResponse, + crate::types::block::PROTOCOL_VERSION, std::{collections::HashSet, time::Duration}, tokio::time::sleep, }; use super::{Node, NodeManager}; -use crate::{ - client::{Client, ClientInner, Error, Result}, - types::block::PROTOCOL_VERSION, -}; +use crate::client::{Client, ClientInner, Error, Result}; impl ClientInner { /// Get a node candidate from the healthy node pool. From a2f7132aa95ebdfe5c1e1f2e7e060eff07e9337b Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Thu, 24 Aug 2023 13:28:04 -0400 Subject: [PATCH 20/28] add a test and impl suggestions --- sdk/src/client/api/high_level.rs | 2 +- sdk/src/client/node_manager/syncing.rs | 2 +- sdk/src/types/api/core/response.rs | 20 ++++---- sdk/src/types/block/protocol.rs | 45 ++++++++++-------- sdk/src/types/block/slot/epoch.rs | 65 ++++++++++++++++++++------ 5 files changed, 88 insertions(+), 46 deletions(-) diff --git a/sdk/src/client/api/high_level.rs b/sdk/src/client/api/high_level.rs index edc5af168e..70118b6c9e 100644 --- a/sdk/src/client/api/high_level.rs +++ b/sdk/src/client/api/high_level.rs @@ -114,7 +114,7 @@ impl Client { Ok(selected_inputs) } - /// Returns the local time checked with the timestamp of the latest milestone, if the difference is larger than 5 + /// Returns the local time checked with the tangle timestamp. If the difference is larger than 5 /// minutes an error is returned to prevent locking outputs by accident for a wrong time. pub async fn get_time_checked(&self) -> Result { let current_time = unix_timestamp_now().as_secs() as u32; diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index fc9b358278..24fa9caaeb 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -111,7 +111,7 @@ impl ClientInner { if let Some((info, _node_url)) = nodes.first() { let mut network_info = self.network_info.write().await; - network_info.tangle_time = Some(info.status.relative_accepted_tangle_time); + network_info.tangle_time = info.status.relative_accepted_tangle_time; // Unwrap: We should always have parameters for this version. If we don't we can't recover. network_info.protocol_parameters = info .protocol_parameters_by_version(PROTOCOL_VERSION) diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index b661f0451b..3571765db6 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -61,18 +61,18 @@ impl core::fmt::Display for InfoResponse { )] pub struct StatusResponse { pub is_healthy: bool, - #[serde(with = "crate::utils::serde::string")] - pub accepted_tangle_time: u64, - #[serde(with = "crate::utils::serde::string")] - pub relative_accepted_tangle_time: u64, - #[serde(with = "crate::utils::serde::string")] - pub confirmed_tangle_time: u64, - #[serde(with = "crate::utils::serde::string")] - pub relative_confirmed_tangle_time: u64, + #[serde(with = "crate::utils::serde::option_string")] + pub accepted_tangle_time: Option, + #[serde(with = "crate::utils::serde::option_string")] + pub relative_accepted_tangle_time: Option, + #[serde(with = "crate::utils::serde::option_string")] + pub confirmed_tangle_time: Option, + #[serde(with = "crate::utils::serde::option_string")] + pub relative_confirmed_tangle_time: Option, pub latest_commitment_id: SlotCommitmentId, pub latest_finalized_slot: SlotIndex, - pub latest_accepted_block_slot: SlotIndex, - pub latest_confirmed_block_slot: SlotIndex, + pub latest_accepted_block_slot: Option, + pub latest_confirmed_block_slot: Option, pub pruning_slot: SlotIndex, } diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index d384ffa57e..09694c4b47 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -31,73 +31,73 @@ pub const DEFAULT_SLOTS_PER_EPOCH_EXPONENT: u32 = 10; pub struct ProtocolParameters { // The version of the protocol running. #[getset(get_copy = "pub")] - version: u8, + pub(crate) version: u8, // The human friendly name of the network. #[packable(unpack_error_with = |err| Error::InvalidNetworkName(err.into_item_err()))] #[serde(with = "crate::utils::serde::string_prefix")] #[getset(skip)] - network_name: StringPrefix, + pub(crate) network_name: StringPrefix, // The HRP prefix used for Bech32 addresses in the network. #[getset(get_copy = "pub")] - bech32_hrp: Hrp, + pub(crate) bech32_hrp: Hrp, // The rent structure used by given node/network. #[getset(get = "pub")] - rent_structure: RentStructure, + pub(crate) rent_structure: RentStructure, // The work score structure used by the node/network. #[getset(get = "pub")] #[serde(default, skip_serializing_if = "Option::is_none")] - work_score_structure: Option, + pub(crate) work_score_structure: Option, // TokenSupply defines the current token supply on the network. #[serde(with = "crate::utils::serde::string")] #[getset(get_copy = "pub")] - token_supply: u64, + pub(crate) token_supply: u64, // Genesis timestamp at which the slots start to count. #[serde(with = "crate::utils::serde::string")] #[getset(get_copy = "pub")] - genesis_unix_timestamp: u32, + pub(crate) genesis_unix_timestamp: u32, // Duration of each slot in seconds. #[getset(get_copy = "pub")] - slot_duration_in_seconds: u8, + pub(crate) slot_duration_in_seconds: u8, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(skip)] - slots_per_epoch_exponent: Option, + pub(crate) slots_per_epoch_exponent: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] - mana_generation_rate: Option, + pub(crate) mana_generation_rate: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] - mana_generation_rate_exponent: Option, + pub(crate) mana_generation_rate_exponent: Option, #[packable(unpack_error_with = |err| Error::InvalidManaDecayFactors(err.into_opt_error()))] #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(skip)] - mana_decay_factors: Option>, + pub(crate) mana_decay_factors: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] - mana_decay_factors_exponent: Option, + pub(crate) mana_decay_factors_exponent: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] - mana_decay_factor_epochs_sum: Option, + pub(crate) mana_decay_factor_epochs_sum: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] - mana_decay_factor_epochs_sum_exponent: Option, + pub(crate) mana_decay_factor_epochs_sum_exponent: Option, #[serde( default, skip_serializing_if = "Option::is_none", with = "crate::utils::serde::option_string" )] #[getset(get_copy = "pub")] - staking_unbonding_period: Option, + pub(crate) staking_unbonding_period: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] - eviction_age: Option, + pub(crate) eviction_age: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get_copy = "pub")] - liveness_threshold: Option, + pub(crate) liveness_threshold: Option, #[getset(get_copy = "pub")] - epoch_nearing_threshold: SlotIndex, + pub(crate) epoch_nearing_threshold: SlotIndex, #[serde(default, skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] - version_signaling: Option, + pub(crate) version_signaling: Option, } // This implementation is required to make [`ProtocolParameters`] a [`Packable`] visitor. @@ -176,6 +176,11 @@ impl ProtocolParameters { self.mana_decay_factors.as_ref().map(|slice| slice.as_ref()) } + /// Returns the slots per epoch of the [`ProtocolParameters`]. + pub fn slots_per_epoch(&self) -> u64 { + 2_u64.pow(self.slots_per_epoch_exponent()) + } + /// Returns the slots per epoch exponent of the [`ProtocolParameters`]. pub fn slots_per_epoch_exponent(&self) -> u32 { self.slots_per_epoch_exponent diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 4dc3c58fd3..028bc558dc 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -32,14 +32,6 @@ impl EpochIndex { let mut res = 0; let mut last = None; for (start_epoch, exponent) in slots_per_epoch_exponent_iter { - if *start_epoch > res { - // We can't calculate the epoch if we don't have the exponent for the containing range - if slot_index > 0 { - return Err(Error::InvalidStartEpoch(start_epoch)); - } else { - break; - } - } if let Some((last_start_epoch, last_exponent)) = last { // Get the number of slots this range of epochs represents let slots_in_range = (*start_epoch - last_start_epoch) << last_exponent; @@ -47,16 +39,23 @@ impl EpochIndex { if slot_index > slots_in_range { // Update the slot index so it is in the context of the next epoch slot_index -= slots_in_range; + } else { + break; } } - res = *start_epoch; + if *start_epoch > res { + // We can't calculate the epoch if we don't have the exponent for the containing range + if slot_index > 0 { + return Err(Error::InvalidStartEpoch(start_epoch)); + } else { + break; + } + } + res = *start_epoch + (slot_index >> exponent); last = Some((*start_epoch, exponent)); } - if let Some((_, exponent)) = last { - res += slot_index >> exponent; - } - // TODO: Do we need to add one? - Ok(Self(res + 1)) + // TODO: Or do we need to add one here? + Ok(Self(res)) } } @@ -68,3 +67,41 @@ impl From for u64 { #[cfg(feature = "serde")] string_serde_impl!(EpochIndex); + +#[cfg(test)] +mod test { + use super::*; + use crate::types::block::protocol::ProtocolParameters; + + #[test] + fn epoch_index_from_slot() { + let v3_params = ProtocolParameters { + version: 3, + slots_per_epoch_exponent: Some(10), + ..Default::default() + }; + let v4_params = ProtocolParameters { + version: 4, + slots_per_epoch_exponent: Some(11), + ..Default::default() + }; + let params = [(EpochIndex(0), v3_params.clone()), (EpochIndex(10), v4_params)]; + let slots_per_epoch_exponent_iter = params + .iter() + .map(|(start_index, params)| (*start_index, params.slots_per_epoch_exponent())); + + let slot_index = SlotIndex::new(3000); + let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter.clone()); + assert_eq!(epoch_index, Ok(EpochIndex(2))); + + let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter.clone().skip(1)); + assert_eq!(epoch_index, Err(Error::InvalidStartEpoch(EpochIndex(10)))); + + let slot_index = SlotIndex::new(10 * v3_params.slots_per_epoch() + 3000); + let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter.clone()); + assert_eq!(epoch_index, Ok(EpochIndex(11))); + + let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter.skip(1)); + assert_eq!(epoch_index, Err(Error::InvalidStartEpoch(EpochIndex(10)))); + } +} From 3668f72ad8791dfefabc72c9d573b5f8cf1e5bbe Mon Sep 17 00:00:00 2001 From: Alexandcoats Date: Fri, 25 Aug 2023 08:28:43 -0400 Subject: [PATCH 21/28] Update sdk/src/types/block/slot/epoch.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- sdk/src/types/block/slot/epoch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 028bc558dc..9fe978c0cc 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -51,7 +51,7 @@ impl EpochIndex { break; } } - res = *start_epoch + (slot_index >> exponent); + res = *start_epoch + (slot_index >> exponent) + 1; last = Some((*start_epoch, exponent)); } // TODO: Or do we need to add one here? From 2beba6bcf79d25a6dc197cfb60f21786b5339237 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Fri, 25 Aug 2023 09:05:59 -0400 Subject: [PATCH 22/28] Revert "Update sdk/src/types/block/slot/epoch.rs" This reverts commit 3668f72ad8791dfefabc72c9d573b5f8cf1e5bbe. --- sdk/src/types/block/slot/epoch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 9fe978c0cc..028bc558dc 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -51,7 +51,7 @@ impl EpochIndex { break; } } - res = *start_epoch + (slot_index >> exponent) + 1; + res = *start_epoch + (slot_index >> exponent); last = Some((*start_epoch, exponent)); } // TODO: Or do we need to add one here? From 5532ffba289645d0ed8db6c43c031b20c67954a3 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Fri, 25 Aug 2023 09:22:27 -0400 Subject: [PATCH 23/28] add another fail condition, a test, and remove TODOs --- sdk/src/types/block/slot/epoch.rs | 59 +++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 028bc558dc..a11d018a8c 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -28,11 +28,13 @@ impl EpochIndex { slots_per_epoch_exponent_iter: impl Iterator, ) -> Result { let mut slot_index = *slot_index; - // TODO: Should this start at 1? let mut res = 0; let mut last = None; for (start_epoch, exponent) in slots_per_epoch_exponent_iter { if let Some((last_start_epoch, last_exponent)) = last { + if *start_epoch <= last_start_epoch { + return Err(Error::InvalidStartEpoch(start_epoch)); + } // Get the number of slots this range of epochs represents let slots_in_range = (*start_epoch - last_start_epoch) << last_exponent; // Check whether the slot index is contained in this range @@ -54,7 +56,6 @@ impl EpochIndex { res = *start_epoch + (slot_index >> exponent); last = Some((*start_epoch, exponent)); } - // TODO: Or do we need to add one here? Ok(Self(res)) } } @@ -94,14 +95,60 @@ mod test { let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter.clone()); assert_eq!(epoch_index, Ok(EpochIndex(2))); - let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter.clone().skip(1)); - assert_eq!(epoch_index, Err(Error::InvalidStartEpoch(EpochIndex(10)))); - let slot_index = SlotIndex::new(10 * v3_params.slots_per_epoch() + 3000); let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter.clone()); assert_eq!(epoch_index, Ok(EpochIndex(11))); + } + + #[test] + fn invalid_params() { + let v3_params = ProtocolParameters { + version: 3, + slots_per_epoch_exponent: Some(10), + ..Default::default() + }; + let v4_params = ProtocolParameters { + version: 4, + slots_per_epoch_exponent: Some(11), + ..Default::default() + }; + let v5_params = ProtocolParameters { + version: 5, + slots_per_epoch_exponent: Some(12), + ..Default::default() + }; + let slot_index = SlotIndex::new(100000); + + // Params must cover the entire history starting at epoch 0 + let params = [(EpochIndex(10), v4_params.clone()), (EpochIndex(20), v5_params.clone())]; + let slots_per_epoch_exponent_iter = params + .iter() + .map(|(start_index, params)| (*start_index, params.slots_per_epoch_exponent())); + let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter); + assert_eq!(epoch_index, Err(Error::InvalidStartEpoch(EpochIndex(10)))); - let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter.skip(1)); + // Params must not contain duplicate start epochs + let params = [ + (EpochIndex(0), v3_params.clone()), + (EpochIndex(10), v4_params.clone()), + (EpochIndex(10), v5_params.clone()), + ]; + let slots_per_epoch_exponent_iter = params + .iter() + .map(|(start_index, params)| (*start_index, params.slots_per_epoch_exponent())); + let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter); + assert_eq!(epoch_index, Err(Error::InvalidStartEpoch(EpochIndex(10)))); + + // Params must be properly ordered + let params = [ + (EpochIndex(10), v4_params), + (EpochIndex(0), v3_params), + (EpochIndex(20), v5_params), + ]; + let slots_per_epoch_exponent_iter = params + .iter() + .map(|(start_index, params)| (*start_index, params.slots_per_epoch_exponent())); + let epoch_index = EpochIndex::from_slot_index(slot_index, slots_per_epoch_exponent_iter); assert_eq!(epoch_index, Err(Error::InvalidStartEpoch(EpochIndex(10)))); } } From 6611c698fda5bef12a5c00579cb295aee902f648 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Fri, 25 Aug 2023 10:11:13 -0400 Subject: [PATCH 24/28] Updates and add example comments --- sdk/src/types/api/core/response.rs | 2 +- sdk/src/types/block/slot/epoch.rs | 21 +++++++++++++++++++++ sdk/src/types/block/slot/index.rs | 12 ++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index 3571765db6..481e66189c 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -90,7 +90,7 @@ pub struct MetricsResponse { #[serde(with = "crate::utils::serde::string")] pub confirmed_blocks_per_second: f64, #[serde(with = "crate::utils::serde::string")] - pub confirmed_rate: f64, + pub confirmation_rate: f64, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index a11d018a8c..0fe2cf1b96 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -10,6 +10,27 @@ use crate::types::block::Error; /// subdivided into slots, each with a [`SlotIndex`]. /// To calculate the epoch index of a timestamp, `slotsPerEpochExponent` and `slotDurationInSeconds` are needed. /// An epoch consists of `2^slotsPerEpochExponent` slots. +/// +/// # Examples +/// +/// Given `slotDurationInSeconds == 10` and `slotsPerEpochExponent == 3` +/// +/// ## Slots +/// +/// | slot
index | start timestamp
(inclusive) | end timestamp
(exclusive) | +/// | :- | :------------ | :------------ | +/// | 0 | -infinity | genesis | +/// | 1 | genesis | genesis + 10s | +/// | 2 | genesis + 10s | genesis + 20s | +/// +/// ## Epochs +/// +/// | epoch
index | start slot
(inclusive) | end slot
(exclusive) | +/// | :- | :-- | :-- | +/// | 0 | 0 | 8 | +/// | 1 | 8 | 16 | +/// | 2 | 16 | 24 | +// ... #[derive( Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, Deref, Display, FromStr, packable::Packable, )] diff --git a/sdk/src/types/block/slot/index.rs b/sdk/src/types/block/slot/index.rs index 3f79c98c55..7eba8d7d65 100644 --- a/sdk/src/types/block/slot/index.rs +++ b/sdk/src/types/block/slot/index.rs @@ -10,6 +10,18 @@ use crate::types::block::Error; /// subdivided into slots, each with a slot index. /// To calculate the slot index of a timestamp, `genesisUnixTimestamp` and the `slotDurationInSeconds` are needed. /// The slot index of timestamp `ts` is `(ts - genesisTimestamp)/duration + 1`. +/// +/// # Examples +/// +/// Given `slotDurationInSeconds == 10` +/// +/// ## Slots +/// +/// | slot
index | start timestamp
(inclusive) | end timestamp
(exclusive) | +/// | :- | :------------ | :------------ | +/// | 0 | -infinity | genesis | +/// | 1 | genesis | genesis + 10s | +/// | 2 | genesis + 10s | genesis + 20s | #[derive( Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, Deref, Display, FromStr, packable::Packable, )] From e8bd69f48641fa983ca48416f29ed701534724c6 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Fri, 25 Aug 2023 10:42:39 -0400 Subject: [PATCH 25/28] Updates from TIP --- bindings/core/src/method_handler/client.rs | 2 +- bindings/core/src/method_handler/utils.rs | 2 +- .../input_selection/remainder.rs | 4 +- sdk/src/client/core.rs | 2 +- sdk/src/types/block/error.rs | 76 +------- sdk/src/types/block/output/account.rs | 6 +- sdk/src/types/block/output/basic.rs | 6 +- sdk/src/types/block/output/delegation.rs | 2 +- sdk/src/types/block/output/foundry.rs | 6 +- sdk/src/types/block/output/mod.rs | 4 +- sdk/src/types/block/output/nft.rs | 6 +- sdk/src/types/block/output/rent.rs | 10 +- .../payload/transaction/essence/regular.rs | 2 +- sdk/src/types/block/protocol.rs | 166 +++++++++++------- sdk/src/types/block/slot/epoch.rs | 10 +- sdk/src/wallet/account/operations/balance.rs | 2 +- .../account/operations/transaction/mod.rs | 5 +- .../operations/transaction/prepare_output.rs | 2 +- .../client/input_selection/account_outputs.rs | 8 +- .../client/input_selection/nft_outputs.rs | 17 +- sdk/tests/types/rent.rs | 2 +- 21 files changed, 148 insertions(+), 192 deletions(-) diff --git a/bindings/core/src/method_handler/client.rs b/bindings/core/src/method_handler/client.rs index db03905021..b14f958664 100644 --- a/bindings/core/src/method_handler/client.rs +++ b/bindings/core/src/method_handler/client.rs @@ -289,7 +289,7 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM let output = Output::try_from_dto_with_params(output, client.get_token_supply().await?)?; let rent_structure = client.get_rent_structure().await?; - let minimum_storage_deposit = output.rent_cost(&rent_structure); + let minimum_storage_deposit = output.rent_cost(rent_structure); Response::MinimumRequiredStorageDeposit(minimum_storage_deposit.to_string()) } diff --git a/bindings/core/src/method_handler/utils.rs b/bindings/core/src/method_handler/utils.rs index 88683b0c38..de11e6ac0f 100644 --- a/bindings/core/src/method_handler/utils.rs +++ b/bindings/core/src/method_handler/utils.rs @@ -80,7 +80,7 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result { let out = Output::try_from_dto(output)?; - Response::MinimumRequiredStorageDeposit(out.rent_cost(&rent).to_string()) + Response::MinimumRequiredStorageDeposit(out.rent_cost(rent).to_string()) } UtilsMethod::VerifyMnemonic { mnemonic } => { let mnemonic = Mnemonic::from(mnemonic); diff --git a/sdk/src/client/api/block_builder/input_selection/remainder.rs b/sdk/src/client/api/block_builder/input_selection/remainder.rs index 046886d14a..03f34bd2cc 100644 --- a/sdk/src/client/api/block_builder/input_selection/remainder.rs +++ b/sdk/src/client/api/block_builder/input_selection/remainder.rs @@ -65,7 +65,7 @@ impl InputSelection { let native_tokens_remainder = native_tokens_diff.is_some(); let mut remainder_builder = - BasicOutputBuilder::new_with_minimum_storage_deposit(*self.protocol_parameters.rent_structure()) + BasicOutputBuilder::new_with_minimum_storage_deposit(self.protocol_parameters.rent_structure()) .add_unlock_condition(AddressUnlockCondition::new(Address::from(Ed25519Address::from( [0; 32], )))); @@ -144,7 +144,7 @@ impl InputSelection { log::debug!("Created remainder output of {diff} for {remainder_address:?}"); remainder.verify_storage_deposit( - *self.protocol_parameters.rent_structure(), + self.protocol_parameters.rent_structure(), self.protocol_parameters.token_supply(), )?; diff --git a/sdk/src/client/core.rs b/sdk/src/client/core.rs index f0222c11d1..6a18c03192 100644 --- a/sdk/src/client/core.rs +++ b/sdk/src/client/core.rs @@ -148,7 +148,7 @@ impl ClientInner { /// Gets the rent structure of the node we're connecting to. pub async fn get_rent_structure(&self) -> Result { - Ok(*self.get_network_info().await?.protocol_parameters.rent_structure()) + Ok(self.get_network_info().await?.protocol_parameters.rent_structure()) } /// Gets the token supply of the node we're connecting to. diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 903abeef9a..1a58681fc8 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -1,14 +1,10 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{ - boxed::Box, - string::{FromUtf8Error, String}, -}; +use alloc::string::{FromUtf8Error, String}; use core::{convert::Infallible, fmt}; use crypto::Error as CryptoError; -use packable::prefix::UnpackPrefixError; use prefix_hex::Error as HexError; use primitive_types::U256; @@ -89,8 +85,7 @@ pub enum Error { InvalidMetadataFeatureLength(>::Error), InvalidNativeTokenCount(>::Error), InvalidNetworkName(FromUtf8Error), - InvalidManaDecayFactors(UnpackOptionError), - InvalidOption(Box>), + InvalidManaDecayFactors, InvalidNftIndex(>::Error), InvalidOutputAmount(u64), InvalidOutputCount(>::Error), @@ -257,8 +252,7 @@ impl fmt::Display for Error { } Self::InvalidNativeTokenCount(count) => write!(f, "invalid native token count: {count}"), Self::InvalidNetworkName(err) => write!(f, "invalid network name: {err}"), - Self::InvalidManaDecayFactors(err) => write!(f, "invalid mana decay factors: {err}"), - Self::InvalidOption(err) => write!(f, "invalid optional field: {err}"), + Self::InvalidManaDecayFactors => write!(f, "invalid mana decay factors"), Self::InvalidNftIndex(index) => write!(f, "invalid nft index: {index}"), Self::InvalidOutputAmount(amount) => write!(f, "invalid output amount: {amount}"), Self::InvalidOutputCount(count) => write!(f, "invalid output count: {count}"), @@ -375,70 +369,6 @@ impl fmt::Display for Error { } } -#[derive(Debug)] -pub struct UnpackOptionError(packable::option::UnpackOptionError); - -impl PartialEq for UnpackOptionError { - fn eq(&self, other: &Self) -> bool { - use packable::option::UnpackOptionError as OtherErr; - match (&self.0, &other.0) { - (OtherErr::UnknownTag(t1), OtherErr::UnknownTag(t2)) => t1 == t2, - (OtherErr::Inner(e1), OtherErr::Inner(e2)) => e1 == e2, - _ => false, - } - } -} -impl Eq for UnpackOptionError {} - -impl UnpackOptionError { - fn map_opt_err U, U>(self, f: F) -> UnpackOptionError { - use packable::option::UnpackOptionError as OtherErr; - UnpackOptionError(match self.0 { - OtherErr::UnknownTag(t) => OtherErr::UnknownTag(t), - OtherErr::Inner(e) => OtherErr::Inner(f(e)), - }) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for UnpackOptionError {} - -impl fmt::Display for UnpackOptionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use packable::option::UnpackOptionError as OtherErr; - match &self.0 { - OtherErr::UnknownTag(t) => write!(f, "unknown tag: {t}"), - OtherErr::Inner(e) => write!(f, "{e}"), - } - } -} - -pub(crate) trait UnpackPrefixOptionErrorExt { - fn into_opt_error(self) -> UnpackOptionError; -} - -impl UnpackPrefixOptionErrorExt for packable::option::UnpackOptionError { - fn into_opt_error(self) -> UnpackOptionError { - UnpackOptionError(self) - } -} - -impl UnpackPrefixOptionErrorExt for packable::option::UnpackOptionError> { - fn into_opt_error(self) -> UnpackOptionError { - use packable::option::UnpackOptionError as OtherErr; - UnpackOptionError(match self { - Self::UnknownTag(t) => OtherErr::UnknownTag(t), - Self::Inner(e) => OtherErr::Inner(e.into_item_err()), - }) - } -} - -impl> From> for Error { - fn from(value: packable::option::UnpackOptionError) -> Self { - Self::InvalidOption(value.into_opt_error().map_opt_err(Into::into).into()) - } -} - impl From for Error { fn from(error: CryptoError) -> Self { Self::Crypto(error) diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 62c03b434f..d16b30b103 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -295,7 +295,7 @@ impl AccountOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Account(output.clone()).rent_cost(&rent_structure) + Output::Account(output.clone()).rent_cost(rent_structure) } }; @@ -954,7 +954,7 @@ mod tests { let metadata = rand_metadata_feature(); let output = builder - .with_minimum_storage_deposit(*protocol_parameters.rent_structure()) + .with_minimum_storage_deposit(protocol_parameters.rent_structure()) .add_unlock_condition(rand_state_controller_address_unlock_condition_different_from( &account_id, )) @@ -1041,7 +1041,7 @@ mod tests { test_split_dto(builder); let builder = - AccountOutput::build_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), account_id) + AccountOutput::build_with_minimum_storage_deposit(protocol_parameters.rent_structure(), account_id) .add_native_token(NativeToken::new(TokenId::from(foundry_id), 1000).unwrap()) .add_unlock_condition(gov_address) .add_unlock_condition(state_address) diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 8a30f337ae..cf8feb66e1 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -173,7 +173,7 @@ impl BasicOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Basic(output.clone()).rent_cost(&rent_structure) + Output::Basic(output.clone()).rent_cost(rent_structure) } }; @@ -500,7 +500,7 @@ mod tests { let metadata = rand_metadata_feature(); let output = builder - .with_minimum_storage_deposit(*protocol_parameters.rent_structure()) + .with_minimum_storage_deposit(protocol_parameters.rent_structure()) .add_unlock_condition(rand_address_unlock_condition()) .with_features([Feature::from(metadata.clone()), sender_1.into()]) .finish_with_params(ValidationParams::default().with_protocol_parameters(protocol_parameters.clone())) @@ -569,7 +569,7 @@ mod tests { .with_features(rand_allowed_features(BasicOutput::ALLOWED_FEATURES)); test_split_dto(builder); - let builder = BasicOutput::build_with_minimum_storage_deposit(*protocol_parameters.rent_structure()) + let builder = BasicOutput::build_with_minimum_storage_deposit(protocol_parameters.rent_structure()) .add_native_token(NativeToken::new(TokenId::from(foundry_id), 1000).unwrap()) .add_unlock_condition(address) .with_features(rand_allowed_features(BasicOutput::ALLOWED_FEATURES)); diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 1f6efb8197..f3a1ff8520 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -192,7 +192,7 @@ impl DelegationOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Delegation(output.clone()).rent_cost(&rent_structure) + Output::Delegation(output.clone()).rent_cost(rent_structure) } }; diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index c9b3c2f7e0..df0fe7e03c 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -237,7 +237,7 @@ impl FoundryOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Foundry(output.clone()).rent_cost(&rent_structure) + Output::Foundry(output.clone()).rent_cost(rent_structure) } }; @@ -793,7 +793,7 @@ mod tests { assert!(output.immutable_features().is_empty()); let output = builder - .with_minimum_storage_deposit(*protocol_parameters.rent_structure()) + .with_minimum_storage_deposit(protocol_parameters.rent_structure()) .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(rand_account_address())) .finish_with_params(&protocol_parameters) .unwrap(); @@ -851,7 +851,7 @@ mod tests { test_split_dto(builder); let builder = FoundryOutput::build_with_minimum_storage_deposit( - *protocol_parameters.rent_structure(), + protocol_parameters.rent_structure(), 123, rand_token_scheme(), ) diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 6fa5c0c972..2773f1d932 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -385,7 +385,7 @@ impl Output { /// If there is a [`StorageDepositReturnUnlockCondition`](unlock_condition::StorageDepositReturnUnlockCondition), /// its amount is also checked. pub fn verify_storage_deposit(&self, rent_structure: RentStructure, token_supply: u64) -> Result<(), Error> { - let required_output_amount = self.rent_cost(&rent_structure); + let required_output_amount = self.rent_cost(rent_structure); if self.amount() < required_output_amount { return Err(Error::InsufficientStorageDepositAmount { @@ -470,7 +470,7 @@ impl Packable for Output { } impl Rent for Output { - fn weighted_bytes(&self, rent_structure: &RentStructure) -> u64 { + fn weighted_bytes(&self, rent_structure: RentStructure) -> u64 { self.packed_len() as u64 * rent_structure.byte_factor_data() as u64 } } diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index c76975ea57..12a176c24c 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -220,7 +220,7 @@ impl NftOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Nft(output.clone()).rent_cost(&rent_structure) + Output::Nft(output.clone()).rent_cost(rent_structure) } }; @@ -673,7 +673,7 @@ mod tests { assert!(output.immutable_features().is_empty()); let output = builder - .with_minimum_storage_deposit(*protocol_parameters.rent_structure()) + .with_minimum_storage_deposit(protocol_parameters.rent_structure()) .add_unlock_condition(rand_address_unlock_condition()) .finish_with_params(protocol_parameters.token_supply()) .unwrap(); @@ -741,7 +741,7 @@ mod tests { test_split_dto(builder); let builder = - NftOutput::build_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), NftId::null()) + NftOutput::build_with_minimum_storage_deposit(protocol_parameters.rent_structure(), NftId::null()) .add_native_token(NativeToken::new(TokenId::from(foundry_id), 1000).unwrap()) .add_unlock_condition(rand_address_unlock_condition()) .with_features(rand_allowed_features(NftOutput::ALLOWED_FEATURES)) diff --git a/sdk/src/types/block/output/rent.rs b/sdk/src/types/block/output/rent.rs index 938200e34a..eb11f69527 100644 --- a/sdk/src/types/block/output/rent.rs +++ b/sdk/src/types/block/output/rent.rs @@ -125,7 +125,7 @@ impl Packable for RentStructure { /// A trait to facilitate the computation of the byte cost of block outputs, which is central to dust protection. pub trait Rent { /// Computes the byte offset given a [`RentStructure`]. - fn byte_offset(&self, rent_structure: &RentStructure) -> u32 { + fn byte_offset(&self, rent_structure: RentStructure) -> u32 { // The ID of the output. size_of::() as u32 * rent_structure.v_byte_factor_key as u32 // The ID of the block in which the transaction payload that created this output was included. @@ -137,17 +137,17 @@ pub trait Rent { } /// Different fields in a type lead to different storage requirements for the ledger state. - fn weighted_bytes(&self, config: &RentStructure) -> u64; + fn weighted_bytes(&self, config: RentStructure) -> u64; /// Computes the rent cost given a [`RentStructure`]. - fn rent_cost(&self, rent_structure: &RentStructure) -> u64 { + fn rent_cost(&self, rent_structure: RentStructure) -> u64 { rent_structure.v_byte_cost as u64 * (self.weighted_bytes(rent_structure) + self.byte_offset(rent_structure) as u64) } } impl Rent for [T; N] { - fn weighted_bytes(&self, config: &RentStructure) -> u64 { + fn weighted_bytes(&self, config: RentStructure) -> u64 { self.iter().map(|elem| elem.weighted_bytes(config)).sum() } } @@ -196,6 +196,6 @@ impl MinimumStorageDepositBasicOutput { } pub fn finish(self) -> Result { - Ok(self.builder.finish_output(self.token_supply)?.rent_cost(&self.config)) + Ok(self.builder.finish_output(self.token_supply)?.rent_cost(self.config)) } } diff --git a/sdk/src/types/block/payload/transaction/essence/regular.rs b/sdk/src/types/block/payload/transaction/essence/regular.rs index bf19f228a2..8fb7b5900e 100644 --- a/sdk/src/types/block/payload/transaction/essence/regular.rs +++ b/sdk/src/types/block/payload/transaction/essence/regular.rs @@ -391,7 +391,7 @@ fn verify_outputs(outputs: &[Output], visitor: &ProtocolPara } } - output.verify_storage_deposit(*visitor.rent_structure(), visitor.token_supply())?; + output.verify_storage_deposit(visitor.rent_structure(), visitor.token_supply())?; } } diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 09694c4b47..21f3b41798 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -11,14 +11,11 @@ use packable::{ Packable, PackableExt, }; -use super::{address::Hrp, slot::SlotIndex}; -use crate::types::block::{ - error::UnpackPrefixOptionErrorExt, helper::network_name_to_id, output::RentStructure, ConvertTo, Error, - PROTOCOL_VERSION, +use super::{ + address::Hrp, + slot::{EpochIndex, SlotIndex}, }; - -// TODO: The API spec lists this field as optional, but is it really? And if so, what would the default be? -pub const DEFAULT_SLOTS_PER_EPOCH_EXPONENT: u32 = 10; +use crate::types::block::{helper::network_name_to_id, output::RentStructure, ConvertTo, Error, PROTOCOL_VERSION}; /// Defines the parameters of the protocol. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, Getters, CopyGetters)] @@ -28,9 +25,9 @@ pub const DEFAULT_SLOTS_PER_EPOCH_EXPONENT: u32 = 10; derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] +#[getset(get_copy = "pub")] pub struct ProtocolParameters { // The version of the protocol running. - #[getset(get_copy = "pub")] pub(crate) version: u8, // The human friendly name of the network. #[packable(unpack_error_with = |err| Error::InvalidNetworkName(err.into_item_err()))] @@ -38,66 +35,35 @@ pub struct ProtocolParameters { #[getset(skip)] pub(crate) network_name: StringPrefix, // The HRP prefix used for Bech32 addresses in the network. - #[getset(get_copy = "pub")] pub(crate) bech32_hrp: Hrp, // The rent structure used by given node/network. - #[getset(get = "pub")] pub(crate) rent_structure: RentStructure, // The work score structure used by the node/network. - #[getset(get = "pub")] - #[serde(default, skip_serializing_if = "Option::is_none")] - pub(crate) work_score_structure: Option, + pub(crate) work_score_structure: WorkScoreStructure, // TokenSupply defines the current token supply on the network. #[serde(with = "crate::utils::serde::string")] - #[getset(get_copy = "pub")] pub(crate) token_supply: u64, // Genesis timestamp at which the slots start to count. #[serde(with = "crate::utils::serde::string")] - #[getset(get_copy = "pub")] pub(crate) genesis_unix_timestamp: u32, // Duration of each slot in seconds. - #[getset(get_copy = "pub")] pub(crate) slot_duration_in_seconds: u8, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(skip)] - pub(crate) slots_per_epoch_exponent: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get_copy = "pub")] - pub(crate) mana_generation_rate: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get_copy = "pub")] - pub(crate) mana_generation_rate_exponent: Option, - #[packable(unpack_error_with = |err| Error::InvalidManaDecayFactors(err.into_opt_error()))] - #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) slots_per_epoch_exponent: u32, + pub(crate) mana_generation_rate: u32, + pub(crate) mana_generation_rate_exponent: u32, + #[packable(unpack_error_with = |_| Error::InvalidManaDecayFactors)] #[getset(skip)] - pub(crate) mana_decay_factors: Option>, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get_copy = "pub")] - pub(crate) mana_decay_factors_exponent: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get_copy = "pub")] - pub(crate) mana_decay_factor_epochs_sum: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get_copy = "pub")] - pub(crate) mana_decay_factor_epochs_sum_exponent: Option, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "crate::utils::serde::option_string" - )] - #[getset(get_copy = "pub")] - pub(crate) staking_unbonding_period: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get_copy = "pub")] - pub(crate) eviction_age: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get_copy = "pub")] - pub(crate) liveness_threshold: Option, - #[getset(get_copy = "pub")] + pub(crate) mana_decay_factors: BoxedSlicePrefix, + pub(crate) mana_decay_factors_exponent: u32, + pub(crate) mana_decay_factor_epochs_sum: u32, + pub(crate) mana_decay_factor_epochs_sum_exponent: u32, + pub(crate) staking_unbonding_period: EpochIndex, + pub(crate) liveness_threshold: SlotIndex, + pub(crate) min_committable_age: SlotIndex, + pub(crate) max_committable_age: SlotIndex, pub(crate) epoch_nearing_threshold: SlotIndex, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[getset(get = "pub")] - pub(crate) version_signaling: Option, + pub(crate) congestion_control_parameters: CongestionControlParameters, + pub(crate) version_signaling: VersionSignalingParameters, } // This implementation is required to make [`ProtocolParameters`] a [`Packable`] visitor. @@ -127,9 +93,11 @@ impl Default for ProtocolParameters { mana_decay_factors_exponent: Default::default(), mana_decay_factor_epochs_sum: Default::default(), mana_decay_factor_epochs_sum_exponent: Default::default(), - staking_unbonding_period: Default::default(), - eviction_age: Default::default(), - liveness_threshold: Default::default(), + staking_unbonding_period: 10.into(), + liveness_threshold: 5.into(), + min_committable_age: 10.into(), + max_committable_age: 20.into(), + congestion_control_parameters: Default::default(), version_signaling: Default::default(), } } @@ -172,8 +140,8 @@ impl ProtocolParameters { } /// Returns the mana decay factors slice of the [`ProtocolParameters`]. - pub fn mana_decay_factors(&self) -> Option<&[u32]> { - self.mana_decay_factors.as_ref().map(|slice| slice.as_ref()) + pub fn mana_decay_factors(&self) -> &[u32] { + &self.mana_decay_factors } /// Returns the slots per epoch of the [`ProtocolParameters`]. @@ -181,12 +149,6 @@ impl ProtocolParameters { 2_u64.pow(self.slots_per_epoch_exponent()) } - /// Returns the slots per epoch exponent of the [`ProtocolParameters`]. - pub fn slots_per_epoch_exponent(&self) -> u32 { - self.slots_per_epoch_exponent - .unwrap_or(DEFAULT_SLOTS_PER_EPOCH_EXPONENT) - } - /// Gets a [`SlotIndex`] from a unix timestamp. pub fn slot_index(&self, timestamp: u64) -> SlotIndex { SlotIndex::from_timestamp( @@ -202,7 +164,7 @@ impl ProtocolParameters { } } -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -212,7 +174,7 @@ impl ProtocolParameters { #[getset(get_copy = "pub")] pub struct WorkScoreStructure { /// Modifier for network traffic per byte. - data_byte: u32, + data_kilobyte: u32, /// Modifier for work done to process a block. block: u32, /// Modifier for slashing when there are insufficient strong tips. @@ -237,7 +199,65 @@ pub struct WorkScoreStructure { min_strong_parents_threshold: u32, } -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] +impl Default for WorkScoreStructure { + fn default() -> Self { + Self { + data_kilobyte: 0, + block: 100, + missing_parent: 500, + input: 20, + context_input: 20, + output: 20, + native_token: 20, + staking: 100, + block_issuer: 100, + allotment: 100, + signature_ed25519: 200, + min_strong_parents_threshold: 4, + } + } +} + +/// Defines the parameters used to calculate the Reference Mana Cost (RMC). +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +#[packable(unpack_error = Error)] +#[getset(get_copy = "pub")] +pub struct CongestionControlParameters { + #[serde(with = "crate::utils::serde::string")] + rmc_min: u64, + #[serde(with = "crate::utils::serde::string")] + increase: u64, + #[serde(with = "crate::utils::serde::string")] + decrease: u64, + increase_threshold: u32, + decrease_threshold: u32, + scheduler_rate: u32, + #[serde(with = "crate::utils::serde::string")] + min_mana: u64, + max_buffer_size: u32, +} + +impl Default for CongestionControlParameters { + fn default() -> Self { + Self { + rmc_min: 500, + increase: 500, + decrease: 500, + increase_threshold: 800000, + decrease_threshold: 500000, + scheduler_rate: 100000, + min_mana: 1, + max_buffer_size: 3276800, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -251,6 +271,16 @@ pub struct VersionSignalingParameters { activation_offset: u32, } +impl Default for VersionSignalingParameters { + fn default() -> Self { + Self { + window_size: 7, + window_target_ratio: 5, + activation_offset: 7, + } + } +} + /// Returns a [`ProtocolParameters`] for testing purposes. #[cfg(any(feature = "test", feature = "rand"))] pub fn protocol_parameters() -> ProtocolParameters { diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 0fe2cf1b96..47857c394f 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -99,12 +99,12 @@ mod test { fn epoch_index_from_slot() { let v3_params = ProtocolParameters { version: 3, - slots_per_epoch_exponent: Some(10), + slots_per_epoch_exponent: 10, ..Default::default() }; let v4_params = ProtocolParameters { version: 4, - slots_per_epoch_exponent: Some(11), + slots_per_epoch_exponent: 11, ..Default::default() }; let params = [(EpochIndex(0), v3_params.clone()), (EpochIndex(10), v4_params)]; @@ -125,17 +125,17 @@ mod test { fn invalid_params() { let v3_params = ProtocolParameters { version: 3, - slots_per_epoch_exponent: Some(10), + slots_per_epoch_exponent: 10, ..Default::default() }; let v4_params = ProtocolParameters { version: 4, - slots_per_epoch_exponent: Some(11), + slots_per_epoch_exponent: 11, ..Default::default() }; let v5_params = ProtocolParameters { version: 5, - slots_per_epoch_exponent: Some(12), + slots_per_epoch_exponent: 12, ..Default::default() }; let slot_index = SlotIndex::new(100000); diff --git a/sdk/src/wallet/account/operations/balance.rs b/sdk/src/wallet/account/operations/balance.rs index 09df04f29a..e45485efda 100644 --- a/sdk/src/wallet/account/operations/balance.rs +++ b/sdk/src/wallet/account/operations/balance.rs @@ -88,7 +88,7 @@ where } let output = &data.output; - let rent = output.rent_cost(&rent_structure); + let rent = output.rent_cost(rent_structure); // Add account and foundry outputs here because they can't have a // [`StorageDepositReturnUnlockCondition`] or time related unlock conditions diff --git a/sdk/src/wallet/account/operations/transaction/mod.rs b/sdk/src/wallet/account/operations/transaction/mod.rs index 756f6522cb..4f3f44fe35 100644 --- a/sdk/src/wallet/account/operations/transaction/mod.rs +++ b/sdk/src/wallet/account/operations/transaction/mod.rs @@ -72,10 +72,7 @@ where // Check if the outputs have enough amount to cover the storage deposit for output in &outputs { - output.verify_storage_deposit( - *protocol_parameters.rent_structure(), - protocol_parameters.token_supply(), - )?; + output.verify_storage_deposit(protocol_parameters.rent_structure(), protocol_parameters.token_supply())?; } self.finish_transaction(outputs, options).await diff --git a/sdk/src/wallet/account/operations/transaction/prepare_output.rs b/sdk/src/wallet/account/operations/transaction/prepare_output.rs index b622ea54e7..4464a84f78 100644 --- a/sdk/src/wallet/account/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/account/operations/transaction/prepare_output.rs @@ -171,7 +171,7 @@ where // If we're sending an existing NFT, its minimum required storage deposit is not part of the available base_coin // balance, so we add it here if let Some(existing_nft_output_data) = existing_nft_output_data { - available_base_coin += existing_nft_output_data.output.rent_cost(&rent_structure); + available_base_coin += existing_nft_output_data.output.rent_cost(rent_structure); } if final_amount > available_base_coin { diff --git a/sdk/tests/client/input_selection/account_outputs.rs b/sdk/tests/client/input_selection/account_outputs.rs index 7577fa1571..dc06483db3 100644 --- a/sdk/tests/client/input_selection/account_outputs.rs +++ b/sdk/tests/client/input_selection/account_outputs.rs @@ -2313,7 +2313,7 @@ fn new_state_metadata() { let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let account_output = - AccountOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), account_id_1) + AccountOutputBuilder::new_with_minimum_storage_deposit(protocol_parameters.rent_structure(), account_id_1) .with_state_metadata([1, 2, 3]) .add_unlock_condition(StateControllerAddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), @@ -2332,7 +2332,7 @@ fn new_state_metadata() { // New account output, with updated state index let updated_account_output = AccountOutputBuilder::from(account_output.as_account()) - .with_minimum_storage_deposit(*protocol_parameters.rent_structure()) + .with_minimum_storage_deposit(protocol_parameters.rent_structure()) .with_state_metadata([3, 4, 5]) .with_state_index(account_output.as_account().state_index() + 1) .finish_output(protocol_parameters.token_supply()) @@ -2359,7 +2359,7 @@ fn new_state_metadata_but_same_state_index() { let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let account_output = - AccountOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), account_id_1) + AccountOutputBuilder::new_with_minimum_storage_deposit(protocol_parameters.rent_structure(), account_id_1) .with_state_metadata([1, 2, 3]) .add_unlock_condition(StateControllerAddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), @@ -2378,7 +2378,7 @@ fn new_state_metadata_but_same_state_index() { // New account output, without updated state index let updated_account_output = AccountOutputBuilder::from(account_output.as_account()) - .with_minimum_storage_deposit(*protocol_parameters.rent_structure()) + .with_minimum_storage_deposit(protocol_parameters.rent_structure()) .with_state_metadata([3, 4, 5]) .finish_output(protocol_parameters.token_supply()) .unwrap(); diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index 594b96318e..9da0b86a1b 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/input_selection/nft_outputs.rs @@ -1187,14 +1187,13 @@ fn changed_immutable_metadata() { let protocol_parameters = protocol_parameters(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); - let nft_output = - NftOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), nft_id_1) - .with_immutable_features(MetadataFeature::new([1, 2, 3])) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .finish_output(protocol_parameters.token_supply()) - .unwrap(); + let nft_output = NftOutputBuilder::new_with_minimum_storage_deposit(protocol_parameters.rent_structure(), nft_id_1) + .with_immutable_features(MetadataFeature::new([1, 2, 3])) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output(protocol_parameters.token_supply()) + .unwrap(); let inputs = [InputSigningData { output: nft_output.clone(), @@ -1204,7 +1203,7 @@ fn changed_immutable_metadata() { // New nft output with changed immutable metadata feature let updated_account_output = NftOutputBuilder::from(nft_output.as_nft()) - .with_minimum_storage_deposit(*protocol_parameters.rent_structure()) + .with_minimum_storage_deposit(protocol_parameters.rent_structure()) .with_immutable_features(MetadataFeature::new([4, 5, 6])) .finish_output(protocol_parameters.token_supply()) .unwrap(); diff --git a/sdk/tests/types/rent.rs b/sdk/tests/types/rent.rs index 7467503011..ae4faec7fb 100644 --- a/sdk/tests/types/rent.rs +++ b/sdk/tests/types/rent.rs @@ -16,7 +16,7 @@ fn config() -> RentStructure { } fn output_in_range(output: Output, range: std::ops::RangeInclusive) { - let cost = output.rent_cost(&config()); + let cost = output.rent_cost(config()); assert!(range.contains(&cost), "{output:#?} has a required byte cost of {cost}"); } From 18c2236f4e0a3c2f7e6fe0da36439b1c270a3684 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Fri, 25 Aug 2023 11:03:15 -0400 Subject: [PATCH 26/28] doc comments --- sdk/src/types/block/protocol.rs | 47 ++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 21f3b41798..79b1030140 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -17,7 +17,7 @@ use super::{ }; use crate::types::block::{helper::network_name_to_id, output::RentStructure, ConvertTo, Error, PROTOCOL_VERSION}; -/// Defines the parameters of the protocol. +/// Defines the parameters of the protocol at a particular version. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, Getters, CopyGetters)] #[packable(unpack_error = Error)] #[cfg_attr( @@ -27,42 +27,57 @@ use crate::types::block::{helper::network_name_to_id, output::RentStructure, Con )] #[getset(get_copy = "pub")] pub struct ProtocolParameters { - // The version of the protocol running. + /// The version of the protocol running. pub(crate) version: u8, - // The human friendly name of the network. + /// The human friendly name of the network. #[packable(unpack_error_with = |err| Error::InvalidNetworkName(err.into_item_err()))] #[serde(with = "crate::utils::serde::string_prefix")] #[getset(skip)] pub(crate) network_name: StringPrefix, - // The HRP prefix used for Bech32 addresses in the network. + /// The HRP prefix used for Bech32 addresses in the network. pub(crate) bech32_hrp: Hrp, - // The rent structure used by given node/network. + /// The rent structure used by given node/network. pub(crate) rent_structure: RentStructure, - // The work score structure used by the node/network. + /// The work score structure used by the node/network. pub(crate) work_score_structure: WorkScoreStructure, - // TokenSupply defines the current token supply on the network. + /// TokenSupply defines the current token supply on the network. #[serde(with = "crate::utils::serde::string")] pub(crate) token_supply: u64, - // Genesis timestamp at which the slots start to count. + /// Genesis timestamp at which the slots start to count. #[serde(with = "crate::utils::serde::string")] pub(crate) genesis_unix_timestamp: u32, - // Duration of each slot in seconds. + /// Duration of each slot in seconds. pub(crate) slot_duration_in_seconds: u8, + /// The number of slots in an epoch expressed as an exponent of 2. pub(crate) slots_per_epoch_exponent: u32, + /// The amount of potential Mana generated by 1 IOTA in 1 slot. pub(crate) mana_generation_rate: u32, + /// The scaling of `mana_generation_rate` expressed as an exponent of 2. pub(crate) mana_generation_rate_exponent: u32, #[packable(unpack_error_with = |_| Error::InvalidManaDecayFactors)] #[getset(skip)] + /// A lookup table of epoch index diff to mana decay factor. pub(crate) mana_decay_factors: BoxedSlicePrefix, + /// The scaling of `mana_decay_factors` expressed as an exponent of 2. pub(crate) mana_decay_factors_exponent: u32, + /// An integer approximation of the sum of decay over epochs. pub(crate) mana_decay_factor_epochs_sum: u32, + /// The scaling of `mana_decay_factor_epochs_sum` expressed as an exponent of 2. pub(crate) mana_decay_factor_epochs_sum_exponent: u32, + /// The unbonding period in epochs before an account can stop staking. pub(crate) staking_unbonding_period: EpochIndex, + /// TODO pub(crate) liveness_threshold: SlotIndex, + /// Minimum age relative to the accepted tangle time slot index that a slot can be committed. pub(crate) min_committable_age: SlotIndex, + /// Maximum age for a slot commitment to be included in a block relative to the slot index of the block issuing + /// time. pub(crate) max_committable_age: SlotIndex, + /// TODO pub(crate) epoch_nearing_threshold: SlotIndex, + /// Parameters used by to calculate the Reference Mana Cost (RMC). pub(crate) congestion_control_parameters: CongestionControlParameters, + /// TODO pub(crate) version_signaling: VersionSignalingParameters, } @@ -144,6 +159,11 @@ impl ProtocolParameters { &self.mana_decay_factors } + /// Returns the mana decay factor for the given epoch index. + pub fn mana_decay_factor_at(&self, epoch_index: EpochIndex) -> Option { + self.mana_decay_factors.get(*epoch_index as usize).copied() + } + /// Returns the slots per epoch of the [`ProtocolParameters`]. pub fn slots_per_epoch(&self) -> u64 { 2_u64.pow(self.slots_per_epoch_exponent()) @@ -228,17 +248,25 @@ impl Default for WorkScoreStructure { #[packable(unpack_error = Error)] #[getset(get_copy = "pub")] pub struct CongestionControlParameters { + /// Minimum value of the RMC. #[serde(with = "crate::utils::serde::string")] rmc_min: u64, + /// Increase step size of the RMC. #[serde(with = "crate::utils::serde::string")] increase: u64, + /// Decrease step size of the RMC. #[serde(with = "crate::utils::serde::string")] decrease: u64, + /// Threshold for increasing the RMC. increase_threshold: u32, + /// Threshold for decreasing the RMC. decrease_threshold: u32, + /// Rate at which the scheduler runs (in workscore units per second). scheduler_rate: u32, + /// Minimum amount of Mana that an account must have to schedule a block. #[serde(with = "crate::utils::serde::string")] min_mana: u64, + /// Maximum size of the buffer. TODO what buffer? max_buffer_size: u32, } @@ -257,6 +285,7 @@ impl Default for CongestionControlParameters { } } +// TODO docs #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] #[cfg_attr( feature = "serde", From 3da8e7f1aa51b1bd9a84f6eb14b330b7daed1562 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Fri, 25 Aug 2023 11:50:41 -0400 Subject: [PATCH 27/28] fix test and comment --- sdk/src/types/block/protocol.rs | 2 +- sdk/tests/client/client_builder.rs | 52 ++++++++++++------------------ 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 79b1030140..a8133b6037 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -75,7 +75,7 @@ pub struct ProtocolParameters { pub(crate) max_committable_age: SlotIndex, /// TODO pub(crate) epoch_nearing_threshold: SlotIndex, - /// Parameters used by to calculate the Reference Mana Cost (RMC). + /// Parameters used to calculate the Reference Mana Cost (RMC). pub(crate) congestion_control_parameters: CongestionControlParameters, /// TODO pub(crate) version_signaling: VersionSignalingParameters, diff --git a/sdk/tests/client/client_builder.rs b/sdk/tests/client/client_builder.rs index d14e4e7108..22b07a23f1 100644 --- a/sdk/tests/client/client_builder.rs +++ b/sdk/tests/client/client_builder.rs @@ -1,7 +1,10 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::client::{Client, ClientBuilder}; +use iota_sdk::{ + client::{Client, ClientBuilder}, + types::block::protocol::ProtocolParameters, +}; #[tokio::test] async fn invalid_url() { @@ -17,41 +20,28 @@ async fn valid_url() { #[tokio::test] async fn client_builder() { - let client_builder_json = r#"{ - "nodes":[ + let client_builder_json = serde_json::json!({ + "nodes": [ { "url":"http://localhost:14265/", - "disabled":false + "disabled": false } ], - "ignoreNodeHealth":true, - "nodeSyncInterval":{ - "secs":60, - "nanos":0 + "ignoreNodeHealth": true, + "nodeSyncInterval": { + "secs": 60, + "nanos": 0 }, - "quorum":false, - "minQuorumSize":3, - "quorumThreshold":66, - "userAgent":"iota-client/2.0.1-rc.3", - "protocolParameters":{ - "version":3, - "networkName":"shimmer", - "bech32Hrp":"smr", - "rentStructure":{ - "vByteCost":100, - "vByteFactorKey":10, - "vByteFactorData":1 - }, - "tokenSupply":"1813620509061365", - "genesisUnixTimestamp":"1582328545", - "slotDurationInSeconds":10, - "epochNearingThreshold":"30" - }, - "apiTimeout":{ - "secs":15, - "nanos":0 + "quorum": false, + "minQuorumSize": 3, + "quorumThreshold": 66, + "userAgent": "iota-client/2.0.1-rc.3", + "protocolParameters": ProtocolParameters::default(), + "apiTimeout": { + "secs": 15, + "nanos": 0 } - }"#; + }); - let _client_builder = serde_json::from_str::(client_builder_json).unwrap(); + let _client_builder = serde_json::from_value::(client_builder_json).unwrap(); } From 24d04e17d8721c4ff19ea01225d6499c69cde31b Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Fri, 25 Aug 2023 11:51:18 -0400 Subject: [PATCH 28/28] fix other test --- sdk/tests/types/block_id.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/tests/types/block_id.rs b/sdk/tests/types/block_id.rs index 81ee39b788..f78fa76d76 100644 --- a/sdk/tests/types/block_id.rs +++ b/sdk/tests/types/block_id.rs @@ -102,11 +102,11 @@ fn compute() { // TODO: Independently verify this value assert_eq!( block_id.to_string(), - "0xb33499435538bae18845c5c80f7ff66495a336f8a08b75eea9b8699b4b38c9fc0b00000000000000" + "0x0cf6544579470043791cadc15284fd231b577d63bb780f9f615e54761c2eb8b30b00000000000000" ); assert_eq!( block_id.hash().to_string(), - "0xb33499435538bae18845c5c80f7ff66495a336f8a08b75eea9b8699b4b38c9fc" + "0x0cf6544579470043791cadc15284fd231b577d63bb780f9f615e54761c2eb8b3" ); assert_eq!(block_id.slot_index(), slot_index); }