From 6961e39800a95dcdfc21851131c9b204676b5932 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 26 Sep 2023 15:09:22 +0200 Subject: [PATCH 1/5] Finish Delegation TIP compliance (#1306) * Docs clean * Remove immutable feature flags * Add NullDelegationValidatorId error * impl StateTransitionVerifier for DelegationOutput * Add some checks * Add NonDelayedClaimingTransition * Add todo * Fmt * Update sdk/src/types/block/output/delegation.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * IOTA tokens -> coins * validator_id -> validator_address --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- sdk/src/types/api/core/response.rs | 4 +- sdk/src/types/block/error.rs | 2 + sdk/src/types/block/output/account.rs | 11 +- sdk/src/types/block/output/basic.rs | 2 +- sdk/src/types/block/output/delegation.rs | 148 ++++++++++++------ sdk/src/types/block/output/foundry.rs | 2 +- sdk/src/types/block/output/mod.rs | 5 + sdk/src/types/block/output/nft.rs | 2 +- .../types/block/output/state_transition.rs | 3 + sdk/src/types/block/slot/epoch.rs | 6 + 10 files changed, 124 insertions(+), 61 deletions(-) diff --git a/sdk/src/types/api/core/response.rs b/sdk/src/types/api/core/response.rs index 43ef4ea3b3..7998f3f271 100644 --- a/sdk/src/types/api/core/response.rs +++ b/sdk/src/types/api/core/response.rs @@ -206,10 +206,10 @@ pub struct ManaRewardsResponse { pub struct CommitteeResponse { /// The epoch index of the committee. pub epoch_index: EpochIndex, - /// The total amount of delegated and staked IOTA tokens in the selected committee. + /// The total amount of delegated and staked IOTA coins in the selected committee. #[serde(with = "string")] pub total_stake: u64, - /// The total amount of staked IOTA tokens in the selected committee. + /// The total amount of staked IOTA coins in the selected committee. #[serde(with = "string")] pub total_validator_stake: u64, /// The validators of the committee. diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index fa060a803f..0ac9c990b0 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -171,6 +171,7 @@ pub enum Error { UnsupportedOutputKind(u8), DuplicateOutputChain(ChainId), InvalidField(&'static str), + NullDelegationValidatorId, } #[cfg(feature = "std")] @@ -371,6 +372,7 @@ impl fmt::Display for Error { Self::UnsupportedOutputKind(k) => write!(f, "unsupported output kind: {k}"), Self::DuplicateOutputChain(chain_id) => write!(f, "duplicate output chain {chain_id}"), Self::InvalidField(field) => write!(f, "invalid field: {field}"), + Self::NullDelegationValidatorId => write!(f, "null delegation validator ID"), } } } diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 20b30b17ae..5e1d832928 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -370,7 +370,7 @@ pub(crate) type StateMetadataLength = BoundedU16<0, { AccountOutput::STATE_METAD /// Describes an account in the ledger that can be controlled by the state and governance controllers. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct AccountOutput { - // Amount of IOTA tokens held by the output. + // Amount of IOTA coins held by the output. amount: u64, mana: u64, // Native tokens held by the output. @@ -780,29 +780,20 @@ pub(crate) mod dto { pub struct AccountOutputDto { #[serde(rename = "type")] pub kind: u8, - // Amount of IOTA tokens held by the output. #[serde(with = "string")] pub amount: u64, #[serde(with = "string")] pub mana: u64, - // Native tokens held by the output. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub native_tokens: Vec, - // Unique identifier of the account. pub account_id: AccountId, - // A counter that must increase by 1 every time the account is state transitioned. pub state_index: u32, - // Metadata that can only be changed by the state controller. #[serde(skip_serializing_if = "<[_]>::is_empty", default, with = "prefix_hex_bytes")] pub state_metadata: Box<[u8]>, - // A counter that denotes the number of foundries created by this account. pub foundry_counter: u32, - // pub unlock_conditions: Vec, - // #[serde(skip_serializing_if = "Vec::is_empty", default)] pub features: Vec, - // #[serde(skip_serializing_if = "Vec::is_empty", default)] pub immutable_features: Vec, } diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 51a26add26..e28f0a4cc2 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -215,7 +215,7 @@ impl From<&BasicOutput> for BasicOutputBuilder { #[packable(unpack_error = Error)] #[packable(unpack_visitor = ProtocolParameters)] pub struct BasicOutput { - /// Amount of IOTA tokens to deposit with this output. + /// Amount of IOTA coins to deposit with this output. #[packable(verify_with = verify_output_amount_packable)] amount: u64, /// Amount of stored Mana held by this output. diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index d3aae55fd9..5c926cd55f 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -12,16 +12,14 @@ use packable::{ use crate::types::{ block::{ - address::Address, + address::{AccountAddress, Address}, output::{ - account::AccountId, chain_id::ChainId, - feature::FeatureFlags, unlock_condition::{ verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, verify_output_amount_min, verify_output_amount_packable, verify_output_amount_supply, Output, - OutputBuilderAmount, OutputId, Rent, RentStructure, + OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, }, protocol::ProtocolParameters, semantic::{TransactionFailureReason, ValidationContext}, @@ -56,7 +54,7 @@ pub struct DelegationOutputBuilder { amount: OutputBuilderAmount, delegated_amount: u64, delegation_id: DelegationId, - validator_id: AccountId, + validator_address: AccountAddress, start_epoch: EpochIndex, end_epoch: EpochIndex, unlock_conditions: BTreeSet, @@ -68,13 +66,13 @@ impl DelegationOutputBuilder { amount: u64, delegated_amount: u64, delegation_id: DelegationId, - validator_id: AccountId, + validator_address: AccountAddress, ) -> Self { Self::new( OutputBuilderAmount::Amount(amount), delegated_amount, delegation_id, - validator_id, + validator_address, ) } @@ -84,13 +82,13 @@ impl DelegationOutputBuilder { rent_structure: RentStructure, delegated_amount: u64, delegation_id: DelegationId, - validator_id: AccountId, + validator_address: AccountAddress, ) -> Self { Self::new( OutputBuilderAmount::MinimumStorageDeposit(rent_structure), delegated_amount, delegation_id, - validator_id, + validator_address, ) } @@ -98,13 +96,13 @@ impl DelegationOutputBuilder { amount: OutputBuilderAmount, delegated_amount: u64, delegation_id: DelegationId, - validator_id: AccountId, + validator_address: AccountAddress, ) -> Self { Self { amount, delegated_amount, delegation_id, - validator_id, + validator_address, start_epoch: 0.into(), end_epoch: 0.into(), unlock_conditions: BTreeSet::new(), @@ -129,9 +127,9 @@ impl DelegationOutputBuilder { self } - /// Sets the validator ID to the provided value. - pub fn with_validator_id(mut self, validator_id: AccountId) -> Self { - self.validator_id = validator_id; + /// Sets the validator address to the provided value. + pub fn with_validator_address(mut self, validator_address: AccountAddress) -> Self { + self.validator_address = validator_address; self } @@ -174,8 +172,12 @@ impl DelegationOutputBuilder { self } - /// Finishes the builder into a [`DelegationOutput`] without amount verification. + /// Finishes the builder into a [`DelegationOutput`] without parameters verification. pub fn finish(self) -> Result { + if self.validator_address.is_null() { + return Err(Error::NullDelegationValidatorId); + } + let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; verify_unlock_conditions::(&unlock_conditions)?; @@ -184,7 +186,7 @@ impl DelegationOutputBuilder { amount: 1u64, delegated_amount: self.delegated_amount, delegation_id: self.delegation_id, - validator_id: self.validator_id, + validator_address: self.validator_address, start_epoch: self.start_epoch, end_epoch: self.end_epoch, unlock_conditions, @@ -202,7 +204,7 @@ impl DelegationOutputBuilder { Ok(output) } - /// Finishes the builder into a [`DelegationOutput`] with amount verification. + /// Finishes the builder into a [`DelegationOutput`] with parameters verification. pub fn finish_with_params<'a>( self, params: impl Into> + Send, @@ -228,7 +230,7 @@ impl From<&DelegationOutput> for DelegationOutputBuilder { amount: OutputBuilderAmount::Amount(output.amount), delegated_amount: output.delegated_amount, delegation_id: output.delegation_id, - validator_id: output.validator_id, + validator_address: output.validator_address, start_epoch: output.start_epoch, end_epoch: output.end_epoch, unlock_conditions: output.unlock_conditions.iter().cloned().collect(), @@ -236,21 +238,22 @@ impl From<&DelegationOutput> for DelegationOutputBuilder { } } -/// Describes a Delegation output, which delegates its contained IOTA tokens as voting power to a validator. +/// An output which delegates its contained IOTA coins as voting power to a validator. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct DelegationOutput { - // Amount of IOTA tokens held by the output. + /// Amount of IOTA coins to deposit with this output. amount: u64, - /// The amount of delegated coins. + /// Amount of delegated IOTA coins. delegated_amount: u64, - /// Unique identifier of the Delegation Output, which is the BLAKE2b-256 hash of the Output ID that created it. + /// Unique identifier of the delegation output. delegation_id: DelegationId, - /// 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. + /// Account address of the validator to which this output is delegating. + validator_address: AccountAddress, + /// Index of the first epoch for which this output delegates. start_epoch: EpochIndex, - /// The index of the last epoch for which this output delegates. + /// Index of the last epoch for which this output delegates. end_epoch: EpochIndex, + /// Define how the output can be unlocked in a transaction. unlock_conditions: UnlockConditions, } @@ -259,17 +262,15 @@ impl DelegationOutput { pub const KIND: u8 = 7; /// The set of allowed [`UnlockCondition`]s for a [`DelegationOutput`]. pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS; - /// The set of allowed immutable [`Feature`]s for a [`DelegationOutput`]. - pub const ALLOWED_IMMUTABLE_FEATURES: FeatureFlags = FeatureFlags::ISSUER; /// Creates a new [`DelegationOutputBuilder`] with a provided amount. pub fn build_with_amount( amount: u64, delegated_amount: u64, delegation_id: DelegationId, - validator_id: AccountId, + validator_address: AccountAddress, ) -> DelegationOutputBuilder { - DelegationOutputBuilder::new_with_amount(amount, delegated_amount, delegation_id, validator_id) + DelegationOutputBuilder::new_with_amount(amount, delegated_amount, delegation_id, validator_address) } /// Creates a new [`DelegationOutputBuilder`] with a provided rent structure. @@ -278,13 +279,13 @@ impl DelegationOutput { rent_structure: RentStructure, delegated_amount: u64, delegation_id: DelegationId, - validator_id: AccountId, + validator_address: AccountAddress, ) -> DelegationOutputBuilder { DelegationOutputBuilder::new_with_minimum_storage_deposit( rent_structure, delegated_amount, delegation_id, - validator_id, + validator_address, ) } @@ -308,9 +309,9 @@ impl DelegationOutput { self.delegation_id.or_from_output_id(output_id) } - /// Returns the validator ID of the [`DelegationOutput`]. - pub fn validator_id(&self) -> &AccountId { - &self.validator_id + /// Returns the validator address of the [`DelegationOutput`]. + pub fn validator_address(&self) -> &AccountAddress { + &self.validator_address } /// Returns the start epoch of the [`DelegationOutput`]. @@ -355,6 +356,53 @@ impl DelegationOutput { .locked_address(self.address(), context.essence.creation_slot()) .unlock(unlock, inputs, context) } + + // Transition, just without full ValidationContext. + pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), StateTransitionError> { + if !(current_state.delegation_id.is_null() && !next_state.delegation_id().is_null()) { + return Err(StateTransitionError::NonDelayedClaimingTransition); + } + + if current_state.delegated_amount != next_state.delegated_amount + || current_state.start_epoch != next_state.start_epoch + || current_state.validator_address != next_state.validator_address + { + return Err(StateTransitionError::MutatedImmutableField); + } + // TODO add end_epoch validation rules + Ok(()) + } +} + +impl StateTransitionVerifier for DelegationOutput { + fn creation(next_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + if !next_state.delegation_id.is_null() { + return Err(StateTransitionError::NonZeroCreatedId); + } + + if next_state.amount != next_state.delegated_amount { + return Err(StateTransitionError::InvalidDelegatedAmount); + } + + if next_state.end_epoch != 0 { + return Err(StateTransitionError::NonZeroDelegationEndEpoch); + } + + Ok(()) + } + + fn transition( + current_state: &Self, + next_state: &Self, + _context: &ValidationContext<'_>, + ) -> Result<(), StateTransitionError> { + Self::transition_inner(current_state, next_state) + } + + fn destruction(_current_state: &Self, _context: &ValidationContext<'_>) -> Result<(), StateTransitionError> { + // TODO handle mana rewards + Ok(()) + } } impl Packable for DelegationOutput { @@ -365,7 +413,7 @@ impl Packable for DelegationOutput { self.amount.pack(packer)?; self.delegated_amount.pack(packer)?; self.delegation_id.pack(packer)?; - self.validator_id.pack(packer)?; + self.validator_address.pack(packer)?; self.start_epoch.pack(packer)?; self.end_epoch.pack(packer)?; self.unlock_conditions.pack(packer)?; @@ -383,7 +431,12 @@ 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 validator_address = AccountAddress::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + if validator_address.is_null() { + return Err(Error::NullDelegationValidatorId).map_err(UnpackError::Packable); + } + let start_epoch = EpochIndex::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let end_epoch = EpochIndex::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?; @@ -394,7 +447,7 @@ impl Packable for DelegationOutput { amount, delegated_amount, delegation_id, - validator_id, + validator_address, start_epoch, end_epoch, unlock_conditions, @@ -442,7 +495,7 @@ pub(crate) mod dto { #[serde(with = "string")] pub delegated_amount: u64, pub delegation_id: DelegationId, - pub validator_id: AccountId, + pub validator_address: AccountAddress, start_epoch: EpochIndex, end_epoch: EpochIndex, pub unlock_conditions: Vec, @@ -455,7 +508,7 @@ pub(crate) mod dto { amount: value.amount(), delegated_amount: value.delegated_amount(), delegation_id: *value.delegation_id(), - validator_id: *value.validator_id(), + validator_address: *value.validator_address(), start_epoch: value.start_epoch(), end_epoch: value.end_epoch(), unlock_conditions: value.unlock_conditions().iter().map(Into::into).collect::<_>(), @@ -475,7 +528,7 @@ pub(crate) mod dto { dto.amount, dto.delegated_amount, dto.delegation_id, - dto.validator_id, + dto.validator_address, ) .with_start_epoch(dto.start_epoch) .with_end_epoch(dto.end_epoch); @@ -494,7 +547,7 @@ pub(crate) mod dto { amount: OutputBuilderAmount, delegated_amount: u64, delegation_id: &DelegationId, - validator_id: &AccountId, + validator_address: &AccountAddress, start_epoch: impl Into, end_epoch: impl Into, unlock_conditions: Vec, @@ -502,15 +555,18 @@ pub(crate) mod dto { ) -> Result { let params = params.into(); let mut builder = match amount { - OutputBuilderAmount::Amount(amount) => { - DelegationOutputBuilder::new_with_amount(amount, delegated_amount, *delegation_id, *validator_id) - } + OutputBuilderAmount::Amount(amount) => DelegationOutputBuilder::new_with_amount( + amount, + delegated_amount, + *delegation_id, + *validator_address, + ), OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { DelegationOutputBuilder::new_with_minimum_storage_deposit( rent_structure, delegated_amount, *delegation_id, - *validator_id, + *validator_address, ) } } diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index ae07e1ac26..cc75a4b8cf 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -329,7 +329,7 @@ impl From<&FoundryOutput> for FoundryOutputBuilder { /// Describes a foundry output that is controlled by an account. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct FoundryOutput { - /// Amount of IOTA tokens to deposit with this output. + /// Amount of IOTA coins to deposit with this output. amount: u64, /// Native tokens held by this output. native_tokens: NativeTokens, diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 65b93ad1e2..c9829eb5a0 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -352,6 +352,7 @@ impl Output { (None, Some(Self::Account(next_state))) => AccountOutput::creation(next_state, context), (None, Some(Self::Foundry(next_state))) => FoundryOutput::creation(next_state, context), (None, Some(Self::Nft(next_state))) => NftOutput::creation(next_state, context), + (None, Some(Self::Delegation(next_state))) => DelegationOutput::creation(next_state, context), // Transitions. (Some(Self::Account(current_state)), Some(Self::Account(next_state))) => { @@ -363,11 +364,15 @@ impl Output { (Some(Self::Nft(current_state)), Some(Self::Nft(next_state))) => { NftOutput::transition(current_state, next_state, context) } + (Some(Self::Delegation(current_state)), Some(Self::Delegation(next_state))) => { + DelegationOutput::transition(current_state, next_state, context) + } // Destructions. (Some(Self::Account(current_state)), None) => AccountOutput::destruction(current_state, context), (Some(Self::Foundry(current_state)), None) => FoundryOutput::destruction(current_state, context), (Some(Self::Nft(current_state)), None) => NftOutput::destruction(current_state, context), + (Some(Self::Delegation(current_state)), None) => DelegationOutput::destruction(current_state, context), // Unsupported. _ => Err(StateTransitionError::UnsupportedStateTransition), diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index d905af23de..957e384e51 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -287,7 +287,7 @@ impl From<&NftOutput> for NftOutputBuilder { /// Describes an NFT output, a globally unique token with metadata attached. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct NftOutput { - /// Amount of IOTA tokens to deposit with this output. + /// Amount of IOTA coins to deposit with this output. amount: u64, /// Amount of stored Mana held by this output. mana: u64, diff --git a/sdk/src/types/block/output/state_transition.rs b/sdk/src/types/block/output/state_transition.rs index b5b5b99163..55dae28e86 100644 --- a/sdk/src/types/block/output/state_transition.rs +++ b/sdk/src/types/block/output/state_transition.rs @@ -14,14 +14,17 @@ pub enum StateTransitionError { InconsistentNativeTokensMint, InconsistentNativeTokensTransition, InconsistentNativeTokensMeltBurn, + InvalidDelegatedAmount, IssuerNotUnlocked, MissingAccountForFoundry, MutatedFieldWithoutRights, MutatedImmutableField, + NonDelayedClaimingTransition, NonMonotonicallyIncreasingNativeTokens, NonZeroCreatedId, NonZeroCreatedFoundryCounter, NonZeroCreatedStateIndex, + NonZeroDelegationEndEpoch, UnsortedCreatedFoundries, UnsupportedStateIndexOperation { current_state: u32, next_state: u32 }, UnsupportedStateTransition, diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 197fa3d99d..e6afa3bd54 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -87,6 +87,12 @@ impl From for u64 { } } +impl PartialEq for EpochIndex { + fn eq(&self, other: &u64) -> bool { + self.0 == *other + } +} + #[cfg(feature = "serde")] string_serde_impl!(EpochIndex); From 54338318e5f978dc645bd9a26689d22838ff6cd4 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Tue, 26 Sep 2023 15:39:02 +0200 Subject: [PATCH 2/5] feat/node: Update protocol parameters (#1294) * update * fix * update to tip * line, grr --------- Co-authored-by: Thibault Martinez --- bindings/nodejs/lib/client/client.ts | 2 +- .../types/models/info/node-info-protocol.ts | 119 +++++++++++------- .../nodejs/lib/types/models/rent-structure.ts | 4 + 3 files changed, 78 insertions(+), 47 deletions(-) diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index 8c347f5247..ba58c6d234 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -340,7 +340,7 @@ export class Client { /** * Get the token supply. */ - async getTokenSupply(): Promise { + async getTokenSupply(): Promise { return (await this.getProtocolParameters()).tokenSupply; } diff --git a/bindings/nodejs/lib/types/models/info/node-info-protocol.ts b/bindings/nodejs/lib/types/models/info/node-info-protocol.ts index 2eb172a15b..a01da73a24 100644 --- a/bindings/nodejs/lib/types/models/info/node-info-protocol.ts +++ b/bindings/nodejs/lib/types/models/info/node-info-protocol.ts @@ -1,6 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { u64 } from '../../utils'; import type { RentStructure } from '../rent-structure'; /** @@ -21,6 +22,10 @@ export interface ProtocolInfo { * The Protocol Parameters. */ export interface ProtocolParameters { + /** + * Set to value 0 to denote a IOTA 2.0 protocol parameter. + */ + type: number; /** * Protocol version used by the network. */ @@ -42,13 +47,13 @@ export interface ProtocolParameters { */ workScoreStructure: WorkScoreStructure; /** - * Current supply of base token. Plain string encoded number. + * Current supply of base token. */ - tokenSupply: string; + tokenSupply: u64; /** * The genesis timestamp at which the slots start to count. */ - genesisUnixTimestamp: string; + genesisUnixTimestamp: u64; /** * The duration of a slot, in seconds. */ @@ -58,59 +63,43 @@ export interface ProtocolParameters { */ slotsPerEpochExponent: number; /** - * The number of bits used to represent Mana expressed as an exponent of 2. - */ - manaBitsExponent: number; - /** - * The amount of potential Mana generated by 1 IOTA in 1 slot. - */ - manaGenerationRate: number; - /** - * The scaling of ManaGenerationRate expressed as an exponent of 2. - */ - manaGenerationRateExponent: number; - /** - * A lookup table of epoch index diff to mana decay factor (slice index 0 = 1 epoch). - */ - manaDecayFactors: number[]; - /** - * The scaling of ManaDecayFactors expressed as an exponent of 2. + * The parameters used by mana calculation. */ - manaDecayFactorsExponent: number; + manaStructure: ManaStructure; /** - * An integer approximation of the sum of decay over epochs. + * The unbonding period in epochs before an account can stop staking. */ - manaDecayFactorEpochsSum: number; + stakingUnbondingPeriod: u64; /** - * The scaling of ManaDecayFactorEpochsSum expressed as an exponent of 2. + * The number of validation blocks that each validator should issue each slot. */ - manaDecayFactorEpochsSumExponent: number; + validationBlocksPerSlot: number; /** - * The unbonding period in epochs before an account can stop staking. + * The number of epochs worth of Mana that a node is punished with for each additional validation block it issues. */ - stakingUnbondingPeriod: string; + punishmentEpochs: u64; /** * Determine if a block is eligible by evaluating issuingTime and commitments in its pastcone to ATT and lastCommittedSlot respectively. */ - livenessThreshold: string; + livenessThreshold: u64; /** * MinCommittableAge is the minimum age relative to the accepted tangle time slot index that a slot can be committed. */ - minCommittableAge: string; + minCommittableAge: u64; /** * MaxCommittableAge is the maximum age for a slot commitment to be included in a block relative to the slot index of the block issuing time. */ - maxCommittableAge: string; + maxCommittableAge: u64; /** * Determine the slot that should trigger a new committee selection for the next and upcoming epoch. */ - epochNearingThreshold: string; + epochNearingThreshold: u64; /** * Congestion Control Parameters defines the parameters used to calculate the Reference Mana Cost (RMC). */ congestionControlParameters: CongestionControlParameters; /** - * The version signaling parameters. + * The parameters used by signaling protocol parameters upgrade. */ versionSignaling: VersionSignalingParameters; } @@ -120,9 +109,9 @@ export interface ProtocolParameters { */ export interface WorkScoreStructure { /** - * DataKilobyte accounts for the network traffic per kilobyte. + * DataByte accounts for the network traffic per kibibyte. */ - dataKilobyte: number; + dataByte: number; /** * Block accounts for work done to process a block in the node software. */ @@ -169,42 +158,80 @@ export interface WorkScoreStructure { minStrongParentsThreshold: number; } +/** + * Mana Structure defines the parameters used by mana calculation. + */ +export interface ManaStructure { + /** + * The number of bits used to represent Mana. + */ + bitsCount: number; + /** + * The amount of potential Mana generated by 1 IOTA in 1 slot. + */ + generationRate: number; + /** + * The scaling of ManaGenerationRate expressed as an exponent of 2. + */ + generationRateExponent: number; + /** + * A lookup table of epoch index diff to mana decay factor (slice index 0 = 1 epoch). + */ + decayFactors: number[]; + /** + * The scaling of ManaDecayFactors expressed as an exponent of 2. + */ + decayFactorsExponent: number; + /** + * An integer approximation of the sum of decay over epochs. + */ + decayFactorEpochsSum: number; + /** + * The scaling of ManaDecayFactorEpochsSum expressed as an exponent of 2. + */ + decayFactorEpochsSumExponent: number; +} + /** * Congestion Control Parameters defines the parameters used to calculate the Reference Mana Cost (RMC). */ export interface CongestionControlParameters { /** - * RMCMin is the minimum value of the reference Mana cost. + * The minimum value of the reference Mana cost. */ - rmcMin: string; + minReferenceManaCost: u64; /** - * Increase is the increase step size of the reference Mana cost. + * The increase step size of the reference Mana cost. */ - increase: string; + increase: u64; /** - * Decrease is the decrease step size of the reference Mana cost. + * The decrease step size of the reference Mana cost. */ - decrease: string; + decrease: u64; /** - * IncreaseThreshold is the threshold for increasing the reference Mana cost. + * The threshold for increasing the reference Mana cost. */ increaseThreshold: number; /** - * DecreaseThreshold is the threshold for decreasing the reference Mana cost. + * The threshold for decreasing the reference Mana cost. */ decreaseThreshold: number; /** - * SchedulerRate is the rate at which the scheduler runs in workscore units per second. + * The rate at which the scheduler runs in workscore units per second. */ schedulerRate: number; /** - * MinMana is the minimum amount of Mana that an account must have to have a block scheduled. + * The minimum amount of Mana that an account must have to have a block scheduled. */ - minMana: string; + minMana: u64; /** - * MaxBufferSize is the maximum size of the buffer. + * The maximum size of the buffer. */ maxBufferSize: number; + /** + * The maximum number of blocks in the validation buffer. + */ + maxValidationBufferSize: number; } /** diff --git a/bindings/nodejs/lib/types/models/rent-structure.ts b/bindings/nodejs/lib/types/models/rent-structure.ts index ba471f02f4..14bb53eb99 100644 --- a/bindings/nodejs/lib/types/models/rent-structure.ts +++ b/bindings/nodejs/lib/types/models/rent-structure.ts @@ -25,4 +25,8 @@ export interface RentStructure { * Defines the factor to be used for staking feature. */ vByteFactorStakingFeature: number; + /** + * Defines the factor to be used for delegation output. + */ + vByteFactorDelegation: number; } From f0b92eba6642ef0498c1593855b66c188f888fc8 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 26 Sep 2023 15:40:06 +0200 Subject: [PATCH 3/5] Some BlockWrapper ID/params/dto nits (#1329) --- bindings/core/src/method_handler/client.rs | 13 +++++-------- bindings/core/src/method_handler/utils.rs | 2 +- sdk/src/types/block/output/foundry.rs | 2 +- sdk/tests/types/block.rs | 4 ++-- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/bindings/core/src/method_handler/client.rs b/bindings/core/src/method_handler/client.rs index 40347336dc..bfae35f84b 100644 --- a/bindings/core/src/method_handler/client.rs +++ b/bindings/core/src/method_handler/client.rs @@ -316,14 +316,11 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM .await?; Response::CustomJson(data) } - ClientMethod::BlockId { block } => Response::BlockId( - client - .block_id(&BlockWrapper::try_from_dto_with_params( - block, - client.get_protocol_parameters().await?, - )?) - .await?, - ), + ClientMethod::BlockId { block } => { + let protocol_parameters = client.get_protocol_parameters().await?; + let block = BlockWrapper::try_from_dto_with_params(block, &protocol_parameters)?; + Response::BlockId(block.id(&protocol_parameters)) + } }; Ok(response) } diff --git a/bindings/core/src/method_handler/utils.rs b/bindings/core/src/method_handler/utils.rs index 2ab9d4de68..9b35265845 100644 --- a/bindings/core/src/method_handler/utils.rs +++ b/bindings/core/src/method_handler/utils.rs @@ -41,7 +41,7 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result { - let block = BlockWrapper::try_from_dto_with_params(block, protocol_parameters.clone())?; + let block = BlockWrapper::try_from_dto_with_params(block, &protocol_parameters)?; Response::BlockId(block.id(&protocol_parameters)) } UtilsMethod::TransactionId { payload } => { diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index cc75a4b8cf..f8b0854ee5 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -815,7 +815,7 @@ mod tests { let dto = OutputDto::Foundry((&output).into()); let output_unver = Output::try_from_dto(dto.clone()).unwrap(); assert_eq!(&output, output_unver.as_foundry()); - let output_ver = Output::try_from_dto_with_params(dto, protocol_parameters.clone()).unwrap(); + let output_ver = Output::try_from_dto_with_params(dto, &protocol_parameters).unwrap(); assert_eq!(&output, output_ver.as_foundry()); let foundry_id = FoundryId::build(&rand_account_address(), 0, SimpleTokenScheme::KIND); diff --git a/sdk/tests/types/block.rs b/sdk/tests/types/block.rs index a9c9a187b2..a2939defba 100644 --- a/sdk/tests/types/block.rs +++ b/sdk/tests/types/block.rs @@ -152,7 +152,7 @@ fn dto_mismatch_version() { } }); let block_dto = serde_json::from_value::(block_dto_json).unwrap(); - let block_res = BlockWrapper::try_from_dto_with_params(block_dto, protocol_parameters.clone()); + let block_res = BlockWrapper::try_from_dto_with_params(block_dto, &protocol_parameters); assert_eq!( block_res, @@ -193,7 +193,7 @@ fn dto_mismatch_network_id() { } }); let block_dto = serde_json::from_value::(block_dto_json).unwrap(); - let block_res = BlockWrapper::try_from_dto_with_params(block_dto, protocol_parameters.clone()); + let block_res = BlockWrapper::try_from_dto_with_params(block_dto, &protocol_parameters); assert_eq!( block_res, From cb7c60f12a38d2e560bc501c5c2e485476ea95cc Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 26 Sep 2023 16:51:06 +0200 Subject: [PATCH 4/5] No mana in simple deposits (#1327) --- sdk/src/types/block/output/basic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index e28f0a4cc2..7fed97f268 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -314,7 +314,7 @@ impl BasicOutput { /// features. They are used to return storage deposits. pub fn simple_deposit_address(&self) -> Option<&Address> { if let [UnlockCondition::Address(address)] = self.unlock_conditions().as_ref() { - if self.native_tokens.is_empty() && self.features.is_empty() { + if self.mana == 0 && self.native_tokens.is_empty() && self.features.is_empty() { return Some(address.address()); } } From 1e954c4028b2951259634f46fa90aee6dff2dd7b Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 26 Sep 2023 16:51:25 +0200 Subject: [PATCH 5/5] Add rewards params (#1302) * Add RewardsParameters * Fix test * Update sdk/src/types/block/mana/rewards.rs Co-authored-by: DaughterOfMars * Update sdk/src/types/block/mana/rewards.rs Co-authored-by: DaughterOfMars * Update sdk/src/types/block/mana/rewards.rs Co-authored-by: DaughterOfMars * Update sdk/src/types/block/mana/rewards.rs Co-authored-by: DaughterOfMars --------- Co-authored-by: DaughterOfMars --- sdk/src/types/block/mana/mod.rs | 5 +- sdk/src/types/block/mana/rewards.rs | 49 +++++++++ .../block/mana/{protocol.rs => structure.rs} | 0 sdk/src/types/block/protocol.rs | 5 +- sdk/src/types/block/slot/epoch.rs | 2 +- sdk/tests/types/protocol.rs | 100 ++++++++++-------- 6 files changed, 112 insertions(+), 49 deletions(-) create mode 100644 sdk/src/types/block/mana/rewards.rs rename sdk/src/types/block/mana/{protocol.rs => structure.rs} (100%) diff --git a/sdk/src/types/block/mana/mod.rs b/sdk/src/types/block/mana/mod.rs index 1c3007aec1..80320ac623 100644 --- a/sdk/src/types/block/mana/mod.rs +++ b/sdk/src/types/block/mana/mod.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 mod allotment; -mod protocol; +mod rewards; +mod structure; use alloc::{boxed::Box, collections::BTreeSet, vec::Vec}; use core::ops::RangeInclusive; @@ -13,7 +14,7 @@ use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; #[cfg(feature = "serde")] pub use self::allotment::dto::ManaAllotmentDto; -pub use self::{allotment::ManaAllotment, protocol::ManaStructure}; +pub use self::{allotment::ManaAllotment, rewards::RewardsParameters, structure::ManaStructure}; use super::{output::AccountId, protocol::ProtocolParameters, Error}; pub(crate) type ManaAllotmentCount = diff --git a/sdk/src/types/block/mana/rewards.rs b/sdk/src/types/block/mana/rewards.rs new file mode 100644 index 0000000000..ae87db2ec6 --- /dev/null +++ b/sdk/src/types/block/mana/rewards.rs @@ -0,0 +1,49 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use getset::CopyGetters; +use packable::Packable; + +use crate::types::block::{slot::EpochIndex, Error}; + +#[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 RewardsParameters { + /// The number of validation blocks that should be issued by a selected validator per slot during its epoch duties. + validator_blocks_per_slot: u8, + /// Used for shift operation during calculation of profit margin. + profit_margin_exponent: u8, + /// The length of the bootstrapping phase in epochs. + bootstrapping_duration: EpochIndex, + /// The coefficient used for calculation of initial rewards. + #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + mana_share_coefficient: u64, + /// The exponent used for calculation of the initial reward. + decay_balancing_constant_exponent: u8, + /// An integer approximation which is calculated using the `decay_balancing_constant_exponent`. + #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + decay_balancing_constant: u64, + /// The exponent used for shifting operation during the pool rewards calculations. + pool_coefficient_exponent: u8, +} + +impl Default for RewardsParameters { + fn default() -> Self { + // TODO: use actual values + Self { + validator_blocks_per_slot: Default::default(), + profit_margin_exponent: Default::default(), + bootstrapping_duration: Default::default(), + mana_share_coefficient: Default::default(), + decay_balancing_constant_exponent: Default::default(), + decay_balancing_constant: Default::default(), + pool_coefficient_exponent: Default::default(), + } + } +} diff --git a/sdk/src/types/block/mana/protocol.rs b/sdk/src/types/block/mana/structure.rs similarity index 100% rename from sdk/src/types/block/mana/protocol.rs rename to sdk/src/types/block/mana/structure.rs diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 5a972efb30..32d882a839 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -10,7 +10,7 @@ use packable::{prefix::StringPrefix, Packable, PackableExt}; use super::{ address::Hrp, - mana::ManaStructure, + mana::{ManaStructure, RewardsParameters}, slot::{EpochIndex, SlotIndex}, }; use crate::types::block::{helper::network_name_to_id, output::RentStructure, ConvertTo, Error, PROTOCOL_VERSION}; @@ -76,6 +76,8 @@ pub struct ProtocolParameters { pub(crate) congestion_control_parameters: CongestionControlParameters, /// Defines the parameters used to signal a protocol parameters upgrade. pub(crate) version_signaling: VersionSignalingParameters, + /// Defines the parameters used for reward calculation. + pub(crate) rewards_parameters: RewardsParameters, } // This implementation is required to make [`ProtocolParameters`] a [`Packable`] visitor. @@ -109,6 +111,7 @@ impl Default for ProtocolParameters { max_committable_age: 20.into(), congestion_control_parameters: Default::default(), version_signaling: Default::default(), + rewards_parameters: Default::default(), } } } diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index e6afa3bd54..0e47f94f78 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -32,7 +32,7 @@ use crate::types::block::Error; /// | 2 | 16 | 24 | // ... #[derive( - Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, Deref, Display, FromStr, packable::Packable, + Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, From, Deref, Display, FromStr, packable::Packable, )] #[repr(transparent)] pub struct EpochIndex(u64); diff --git a/sdk/tests/types/protocol.rs b/sdk/tests/types/protocol.rs index f7df7919c0..1034190026 100644 --- a/sdk/tests/types/protocol.rs +++ b/sdk/tests/types/protocol.rs @@ -11,54 +11,54 @@ fn params_serde_hash() { { "type":0, "version":3, - "networkName":"xxxNetwork", - "bech32Hrp":"xxx", - "rentStructure": { - "vByteCost":6, - "vByteFactorData":7, - "vByteFactorKey":8, - "vByteFactorBlockIssuerKey":9, - "vByteFactorStakingFeature":10, - "vByteFactorDelegation":10 + "networkName":"TestJungle", + "bech32Hrp":"tgl", + "rentStructure":{ + "vByteCost":0, + "vByteFactorData":0, + "vByteFactorKey":0, + "vByteFactorBlockIssuerKey":0, + "vByteFactorStakingFeature":0, + "vByteFactorDelegation":0 }, "workScoreStructure":{ - "dataByte":1, - "block":2, - "missingParent":3, - "input":4, - "contextInput":5, - "output":6, - "nativeToken":7, - "staking":8, - "blockIssuer":9, - "allotment":10, - "signatureEd25519":11, - "minStrongParentsThreshold":12 + "dataByte":0, + "block":1, + "missingParent":0, + "input":0, + "contextInput":0, + "output":0, + "nativeToken":0, + "staking":0, + "blockIssuer":0, + "allotment":0, + "signatureEd25519":0, + "minStrongParentsThreshold":0 }, - "tokenSupply":"1234567890987654321", - "genesisUnixTimestamp":"1681373293", + "tokenSupply":"2779530283277761", + "genesisUnixTimestamp":"1695275822", "slotDurationInSeconds":10, "slotsPerEpochExponent":13, - "manaStructure": { - "bitsCount":1, + "manaStructure":{ + "bitsCount":63, "generationRate":1, - "generationRateExponent":27, + "generationRateExponent":17, "decayFactors":[10,20], "decayFactorsExponent":32, - "decayFactorEpochsSum":1337, - "decayFactorEpochsSumExponent":20 + "decayFactorEpochsSum":2420916375u32, + "decayFactorEpochsSumExponent":21 }, - "stakingUnbondingPeriod":"11", + "stakingUnbondingPeriod":"10", "validationBlocksPerSlot":10, - "punishmentEpochs":"9", + "punishmentEpochs":"10", "livenessThreshold":"3", "minCommittableAge":"10", "maxCommittableAge":"20", "epochNearingThreshold":"24", - "congestionControlParameters": { - "minReferenceManaCost":"500", - "increase":"500", - "decrease":"500", + "congestionControlParameters":{ + "minReferenceManaCost":"1", + "increase":"0", + "decrease":"0", "increaseThreshold":800000, "decreaseThreshold":500000, "schedulerRate":100000, @@ -66,10 +66,19 @@ fn params_serde_hash() { "maxBufferSize":1000, "maxValidationBufferSize":100 }, - "versionSignaling": { - "windowSize":3, - "windowTargetRatio":4, - "activationOffset":1 + "versionSignaling":{ + "windowSize":7, + "windowTargetRatio":5, + "activationOffset":7 + }, + "rewardsParameters":{ + "validatorBlocksPerSlot":10, + "profitMarginExponent":8, + "bootstrappingDuration":"1154", + "manaShareCoefficient":"2", + "decayBalancingConstantExponent":8, + "decayBalancingConstant":"1", + "poolCoefficientExponent":31 } } ); @@ -79,13 +88,14 @@ fn params_serde_hash() { assert_eq!( protocol_params_bytes, [ - 0, 3, 10, 120, 120, 120, 78, 101, 116, 119, 111, 114, 107, 3, 120, 120, 120, 6, 0, 0, 0, 7, 8, 9, 10, 10, - 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, - 10, 0, 0, 0, 11, 0, 0, 0, 12, 177, 28, 108, 177, 244, 16, 34, 17, 109, 184, 55, 100, 0, 0, 0, 0, 10, 13, 1, - 1, 27, 2, 0, 10, 0, 0, 0, 20, 0, 0, 0, 32, 57, 5, 0, 0, 20, 11, 0, 0, 0, 0, 0, 0, 0, 10, 0, 9, 0, 0, 0, 0, + 0, 3, 10, 84, 101, 115, 116, 74, 117, 110, 103, 108, 101, 3, 116, 103, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 193, 93, 45, 211, 247, 223, 9, 0, 46, 219, 11, 101, 0, 0, 0, 0, 10, 13, 63, 1, 17, + 2, 0, 10, 0, 0, 0, 20, 0, 0, 0, 32, 151, 64, 76, 144, 21, 10, 0, 0, 0, 0, 0, 0, 0, 10, 0, 10, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, - 244, 1, 0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, 0, 53, 12, 0, 32, 161, 7, 0, - 160, 134, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 232, 3, 0, 0, 100, 0, 0, 0, 3, 4, 1 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53, 12, 0, 32, 161, 7, 0, 160, + 134, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 232, 3, 0, 0, 100, 0, 0, 0, 7, 5, 7, 10, 8, 130, 4, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 0, 31 ] ); @@ -93,6 +103,6 @@ fn params_serde_hash() { assert_eq!( hash.to_string(), - "0x9d3e39699e38db1d6e6777a40f82b1b5030e645cffb08dba3a157105bd6bfac8" + "0xc82143973a4a4c93dce9fe99d26d4cdc68d44b7688abcbdf6ba8ab405de2b63b" ); }