Skip to content

Commit

Permalink
Merge branch '2.0' into tx-lengths-validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Thoralf-M committed Mar 13, 2024
2 parents 0fd3738 + 8a3ec9e commit 9a9dfd3
Show file tree
Hide file tree
Showing 19 changed files with 408 additions and 53 deletions.
3 changes: 0 additions & 3 deletions bindings/core/src/method/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ pub enum ClientMethod {
// If not provided, minimum amount will be used
#[serde(default, with = "option_string")]
amount: Option<u64>,
// TODO: Determine if `default` is wanted here
#[serde(default, with = "string")]
mana: u64,
account_id: AccountId,
Expand All @@ -58,7 +57,6 @@ pub enum ClientMethod {
// If not provided, minimum amount will be used
#[serde(default, with = "option_string")]
amount: Option<u64>,
// TODO: Determine if `default` is wanted here
#[serde(default, with = "string")]
mana: u64,
unlock_conditions: Vec<UnlockCondition>,
Expand Down Expand Up @@ -86,7 +84,6 @@ pub enum ClientMethod {
// If not provided, minimum amount will be used
#[serde(default, with = "option_string")]
amount: Option<u64>,
// TODO: Determine if `default` is wanted here
#[serde(default, with = "string")]
mana: u64,
nft_id: NftId,
Expand Down
39 changes: 32 additions & 7 deletions sdk/examples/client/block/02_block_custom_parents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ use iota_sdk::{
secret::{SecretManager, SignBlock},
Client,
},
types::block::output::AccountId,
types::block::{
core::{basic::MaxBurnedManaAmount, BasicBlockBodyBuilder, BlockHeader},
output::AccountId,
UnsignedBlock,
},
};

#[tokio::main]
Expand All @@ -39,16 +43,37 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let issuance = client.get_issuance().await?;
println!("Issuance:\n{issuance:#?}");

let protocol_params = client.get_protocol_parameters().await?;

// Create and send the block with custom parents.
// TODO build block with custom parents, but without `build_basic_block()`
let block = client
.build_basic_block(issuer_id, None)
.await?
.sign_ed25519(&secret_manager, Bip44::new(IOTA_COIN_TYPE))
.await?;
let block = UnsignedBlock::new(
BlockHeader::new(
protocol_params.version(),
protocol_params.network_id(),
time::OffsetDateTime::now_utc().unix_timestamp_nanos() as _,
issuance.latest_commitment.id(),
issuance.latest_finalized_slot,
issuer_id,
),
BasicBlockBodyBuilder::new(
issuance.strong_parents()?,
MaxBurnedManaAmount::MinimumAmount {
params: protocol_params.work_score_parameters(),
reference_mana_cost: client
.get_account_congestion(&issuer_id, None)
.await?
.reference_mana_cost,
},
)
.finish_block_body()?,
)
.sign_ed25519(&secret_manager, Bip44::new(IOTA_COIN_TYPE))
.await?;

println!("{block:#?}");

client.post_block(&block).await?;

println!(
"Block with custom parents sent: {}/block/{}",
std::env::var("EXPLORER_URL").unwrap(),
Expand Down
9 changes: 2 additions & 7 deletions sdk/src/client/api/block_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,10 @@ impl Client {
let issuance = self.get_issuance().await?;

let issuing_time = {
#[cfg(feature = "std")]
let issuing_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
let issuing_time = instant::SystemTime::now()
.duration_since(instant::SystemTime::UNIX_EPOCH)
.expect("Time went backwards")
.as_nanos() as u64;
// TODO no_std way to have a nanosecond timestamp
// https://github.com/iotaledger/iota-sdk/issues/647
#[cfg(not(feature = "std"))]
let issuing_time = 0;

// Check that the issuing_time is in the range of +-5 minutes of the node to prevent potential issues
if !(issuance.latest_parent_block_issuing_time - FIVE_MINUTES_IN_NANOSECONDS
Expand Down
5 changes: 3 additions & 2 deletions sdk/src/client/api/block_builder/transaction_builder/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub enum TransactionBuilderError {
found: u64,
/// The required amount.
required: u64,
/// The number of slots remaining before this transaction will have generated enough mana.
slots_remaining: u32,
},
/// Insufficient native token amount provided.
#[error("insufficient native token amount: found {found}, required {required}")]
Expand Down Expand Up @@ -76,8 +78,7 @@ pub enum TransactionBuilderError {
UnfulfillableRequirement(Requirement),
/// Unsupported address type.
#[error("unsupported address type {0}")]
// TODO replace with string when 2.0 has Address::kind_str
UnsupportedAddressType(u8),
UnsupportedAddressType(String),
/// Block error.
#[error("{0}")]
Block(#[from] BlockError),
Expand Down
12 changes: 12 additions & 0 deletions sdk/src/client/api/block_builder/transaction_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,21 @@ impl TransactionBuilder {
let (input_mana, output_mana) = self.mana_sums(false)?;

if input_mana < output_mana {
let total_generation_amount = self
.selected_inputs
.iter()
.map(|o| o.output.mana_generation_amount(&self.protocol_parameters))
.sum::<u64>();
let slots_remaining = self.protocol_parameters.slots_until_generated(
self.creation_slot,
total_generation_amount,
self.total_selected_mana(false)?,
output_mana - input_mana,
)?;
return Err(TransactionBuilderError::InsufficientMana {
found: input_mana,
required: output_mana,
slots_remaining,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ impl TransactionBuilder {

self.fulfill_sender_requirement(restricted_address.address())
}
_ => Err(TransactionBuilderError::UnsupportedAddressType(address.kind())),
_ => Err(TransactionBuilderError::UnsupportedAddressType(
address.kind_str().to_owned(),
)),
}
}
}
4 changes: 4 additions & 0 deletions sdk/src/types/block/mana/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ pub enum ManaError {
AllotmentsNotUniqueSorted,
#[display(fmt = "invalid epoch diff: created {created}, target {target}")]
EpochDiff { created: EpochIndex, target: EpochIndex },
#[display(fmt = "insufficient amount to generate positive mana")]
InsufficientGenerationAmount,
#[display(fmt = "mana value {value} above maximum {max}")]
AboveMax { value: u64, max: u64 },
}

#[cfg(feature = "std")]
Expand Down
171 changes: 171 additions & 0 deletions sdk/src/types/block/mana/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,59 @@ impl ProtocolParameters {
- (c >> mana_parameters.decay_factors_exponent())
})
}

pub fn slots_until_generated(
&self,
current_slot: impl Into<SlotIndex>,
generation_amount: u64,
stored_mana: u64,
required_mana: u64,
) -> Result<u32, ManaError> {
if required_mana == 0 {
return Ok(0);
}
if required_mana > self.mana_parameters().max_mana() {
return Err(ManaError::AboveMax {
value: required_mana,
max: self.mana_parameters().max_mana(),
});
}
let current_slot = current_slot.into();
let mut num_slots = 0;
let mana_generated_per_epoch = self
.mana_parameters()
.generate_mana(generation_amount, self.slots_per_epoch());
let mut required_mana_remaining = required_mana;
loop {
// Get the minimum number of slots required to achieve the needed mana (i.e. not including decay)
let additional_slots = u32::try_from(
(required_mana_remaining as u128 * self.slots_per_epoch() as u128)
/ mana_generated_per_epoch.max(1) as u128,
)
.map_err(|_| ManaError::InsufficientGenerationAmount)?;

num_slots += additional_slots.max(1);
// Get the actual values after that many slots
let decayed_mana =
stored_mana - self.mana_with_decay(stored_mana, current_slot, current_slot + num_slots)?;
let generated_mana =
self.generate_mana_with_decay(generation_amount, current_slot, current_slot + num_slots)?;
// If we generated less than how much we lost, this is not going to work out
if generated_mana <= decayed_mana {
return Err(ManaError::InsufficientGenerationAmount);
}
if generated_mana - decayed_mana >= required_mana {
return Ok(num_slots);
}
let new_required_mana = (required_mana + decayed_mana)
.checked_sub(generated_mana)
.ok_or(ManaError::InsufficientGenerationAmount)?;
if new_required_mana >= required_mana_remaining {
return Err(ManaError::InsufficientGenerationAmount);
}
required_mana_remaining = new_required_mana;
}
}
}

/// Perform a multiplication and shift.
Expand Down Expand Up @@ -544,4 +597,122 @@ mod test {
4.0,
)
}

#[derive(Debug)]
struct ManaTest {
current_slot: u32,
generation_amount: u64,
stored_mana: u64,
required_mana: u64,
}

impl ManaTest {
fn mana_after(&self, slots: u32) -> u64 {
params()
.generate_mana_with_decay(self.generation_amount, self.current_slot, self.current_slot + slots)
.unwrap()
+ params()
.mana_with_decay(self.stored_mana, self.current_slot, self.current_slot + slots)
.unwrap()
}

fn slots_until_generated(&self) -> Result<u32, ManaError> {
params().slots_until_generated(
self.current_slot,
self.generation_amount,
self.stored_mana,
self.required_mana,
)
}
}

#[test]
fn slots_until_generated() {
for test in [
ManaTest {
current_slot: 100,
generation_amount: 100000,
stored_mana: 1000000,
required_mana: 50000,
},
ManaTest {
current_slot: 1000000,
generation_amount: 500000,
stored_mana: 12345,
required_mana: 999999,
},
ManaTest {
current_slot: 1294732685,
generation_amount: 300000,
stored_mana: 50,
required_mana: 1,
},
ManaTest {
current_slot: 1294732685,
generation_amount: 500000,
stored_mana: 0,
required_mana: 600,
},
] {
let slots_left = test.slots_until_generated().expect(&format!("{test:?}"));
let mana_after_n_minus_1 = test.mana_after(slots_left - 1);
let mana_after_n = test.mana_after(slots_left);
let expected_mana = test.stored_mana + test.required_mana;
assert!(
mana_after_n_minus_1 < expected_mana,
"{test:?}: mana after {} slots should be lower than {expected_mana}, but found {mana_after_n_minus_1}",
slots_left - 1,
);
assert!(
mana_after_n >= expected_mana,
"{test:?}: mana after {slots_left} slots should be greater than or equal to {expected_mana}, but found {mana_after_n}",
);
}
}

#[test]
fn slots_until_generated_insufficient_amount() {
for test in [
ManaTest {
current_slot: 10000,
generation_amount: 1000,
stored_mana: 1000000,
required_mana: 50000,
},
ManaTest {
current_slot: 10000,
generation_amount: 2000000,
stored_mana: 0,
required_mana: 1000000000,
},
ManaTest {
current_slot: 10000,
generation_amount: 100000,
stored_mana: 1000000,
required_mana: 500000000000,
},
] {
let slots_left = test.slots_until_generated().unwrap_err();
assert_eq!(slots_left, ManaError::InsufficientGenerationAmount);
}
}

#[test]
fn slots_until_generated_required_above_max() {
let test = ManaTest {
current_slot: 10000,
generation_amount: 100000,
stored_mana: 1000000,
required_mana: 9999999999999999999,
};

let slots_left = test.slots_until_generated().unwrap_err();
assert_eq!(
slots_left,
ManaError::AboveMax {
value: test.required_mana,
max: params().mana_parameters().max_mana()
}
);
}
}
9 changes: 7 additions & 2 deletions sdk/src/types/block/output/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,7 @@ impl AccountOutput {
creation_index: SlotIndex,
target_index: SlotIndex,
) -> Result<DecayedMana, OutputError> {
let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters());
let generation_amount = self.amount().saturating_sub(min_deposit);
let generation_amount = self.mana_generation_amount(protocol_parameters);
let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?;
let potential_mana =
protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?;
Expand All @@ -440,6 +439,12 @@ impl AccountOutput {
potential: potential_mana,
})
}

/// Returns the mana generation amount of the output.
pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 {
let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters());
self.amount().saturating_sub(min_deposit)
}
}

impl StorageScore for AccountOutput {
Expand Down
9 changes: 7 additions & 2 deletions sdk/src/types/block/output/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,7 @@ impl AnchorOutput {
creation_index: SlotIndex,
target_index: SlotIndex,
) -> Result<DecayedMana, OutputError> {
let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters());
let generation_amount = self.amount().saturating_sub(min_deposit);
let generation_amount = self.mana_generation_amount(protocol_parameters);
let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?;
let potential_mana =
protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?;
Expand All @@ -497,6 +496,12 @@ impl AnchorOutput {
potential: potential_mana,
})
}

/// Returns the mana generation amount of the output.
pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 {
let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters());
self.amount().saturating_sub(min_deposit)
}
}

impl StorageScore for AnchorOutput {
Expand Down
Loading

0 comments on commit 9a9dfd3

Please sign in to comment.