Skip to content

Commit

Permalink
feat(iota-genesis-builder): Add configurable delegator to genesis CLI (
Browse files Browse the repository at this point in the history
…#4346)

* feat(iota-genesis-builder): add delegator as cli arg for genesis

* add bail when delegator missing

* fix genesis tests

* feat(genesis): add delegator map type to genesis builder

* refactor(iota-genesis-builder): add comments and restructure resolve_token_distribution_schedule

* refactor(iota-genesis-builder): genesis cli arg to IotaAddress and add load/save DelegatorMap

* feat(genesis-ceremony): add InitDelegatorMap command

* refactor(iota-genesis-builder): renaming structs and replace fold() usage

* feat(iota-genesis-builder): add GenesisDelegation enum

* chore(iota): correct bail messages to start with lower case

* refactor(iota-genesis-builder): rename schedule_without_migration to schedule for clarity

* feat(iota-genesis-builder): create a token allocation to pay validator with gas

* feat(iota-genesis-builder): optimize timelock and gas objects picks

* fix format

* fix: add hardcoded delegator arg to docker config

* fix(iota-genesis-builder): fix csv read and write functions

* chore: fix clippy warnings

* fix pick_objects_for_allocation logic

* minor improvements

* fix: add delegator to start cmd and partially revert minor improvement

* refactor: add comments and adjust example argument

* fix fmt

* fix clippy issue

* chore: fix typos in comments

* fix(iota-genesis-builder): comments and names to make it clearer

* refactor(iota-genesis-builder): use the destroy verb instead of burn

* fix(iota-genesis-builder): update genesis_config_snapshot_matches snapshot

* fix(iota-e2e-tests): update test_full_node_load_migration_data with delegator

---------

Co-authored-by: miker83z <[email protected]>
Co-authored-by: Mirko Zichichi <[email protected]>
Co-authored-by: Chloe Martin <[email protected]>
  • Loading branch information
4 people authored Jan 8, 2025
1 parent f5e892b commit f3049e8
Show file tree
Hide file tree
Showing 13 changed files with 671 additions and 228 deletions.
133 changes: 126 additions & 7 deletions crates/iota-config/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ impl TokenDistributionSchedule {
for allocation in &self.allocations {
total_nanos = total_nanos
.checked_add(allocation.amount_nanos)
.expect("TokenDistributionSchedule allocates more than the maximum supply which equals u64::MAX", );
.expect("TokenDistributionSchedule allocates more than the maximum supply which equals u64::MAX");
}
}

Expand Down Expand Up @@ -516,12 +516,7 @@ impl TokenDistributionSchedule {
/// Helper to read a TokenDistributionSchedule from a csv file.
///
/// The file is encoded such that the final entry in the CSV file is used to
/// denote the allocation to the stake subsidy fund. It must be in the
/// following format:
/// `0x0000000000000000000000000000000000000000000000000000000000000000,
/// <pre>minted supply</pre>,`
///
/// All entries in a token distribution schedule must add up to 10B Iota.
/// denote the allocation to the stake subsidy fund.
pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
let mut reader = csv::Reader::from_reader(reader);
let mut allocations: Vec<TokenAllocation> =
Expand Down Expand Up @@ -568,7 +563,17 @@ impl TokenDistributionSchedule {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct TokenAllocation {
/// Indicates the address that owns the tokens. It means that this
/// `TokenAllocation` can serve to stake some funds to the
/// `staked_with_validator` during genesis, but it's the `recipient_address`
/// which will receive the associated StakedIota (or TimelockedStakedIota)
/// object.
pub recipient_address: IotaAddress,
/// Indicates an amount of nanos that is:
/// - minted for the `recipient_address` and staked to a validator, only in
/// the case `staked_with_validator` is Some
/// - minted for the `recipient_address` and transferred that address,
/// otherwise.
pub amount_nanos: u64,

/// Indicates if this allocation should be staked at genesis and with which
Expand Down Expand Up @@ -628,3 +633,117 @@ impl TokenDistributionScheduleBuilder {
schedule
}
}

/// Represents the allocation of stake and gas payment to a validator.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ValidatorAllocation {
/// The validator address receiving the stake and/or gas payment
pub validator: IotaAddress,
/// The amount of nanos to stake to the validator
pub amount_nanos_to_stake: u64,
/// The amount of nanos to transfer as gas payment to the validator
pub amount_nanos_to_pay_gas: u64,
}

/// Represents a delegation of stake and gas payment to a validator,
/// coming from a delegator. This struct is used to serialize and deserialize
/// delegations to and from a csv file.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Delegation {
/// The address from which to take the nanos for staking/gas
pub delegator: IotaAddress,
/// The allocation to a validator receiving a stake and/or a gas payment
#[serde(flatten)]
pub validator_allocation: ValidatorAllocation,
}

/// Represents genesis delegations to validators.
///
/// This struct maps a delegator address to a list of validators and their
/// stake and gas allocations. Each ValidatorAllocation contains the address of
/// a validator that will receive an amount of nanos to stake and an amount as
/// gas payment.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Delegations {
pub allocations: HashMap<IotaAddress, Vec<ValidatorAllocation>>,
}

impl Delegations {
pub fn new_for_validators_with_default_allocation(
validators: impl IntoIterator<Item = IotaAddress>,
delegator: IotaAddress,
) -> Self {
let validator_allocations = validators
.into_iter()
.map(|address| ValidatorAllocation {
validator: address,
amount_nanos_to_stake: iota_types::governance::MIN_VALIDATOR_JOINING_STAKE_NANOS,
amount_nanos_to_pay_gas: 0,
})
.collect();

let mut allocations = HashMap::new();
allocations.insert(delegator, validator_allocations);

Self { allocations }
}

/// Helper to read a Delegations struct from a csv file.
///
/// The file is encoded such that the final entry in the CSV file is used to
/// denote the allocation coming from a delegator. It must be in the
/// following format:
/// `delegator,validator,amount-nanos-to-stake,amount-nanos-to-pay-gas
/// <delegator1-address>,<validator-1-address>,2000000000000000,5000000000
/// <delegator1-address>,<validator-2-address>,3000000000000000,5000000000
/// <delegator2-address>,<validator-3-address>,4500000000000000,5000000000`
pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
let mut reader = csv::Reader::from_reader(reader);

let mut delegations = Self::default();
for delegation in reader.deserialize::<Delegation>() {
let delegation = delegation?;
delegations
.allocations
.entry(delegation.delegator)
.or_default()
.push(delegation.validator_allocation);
}

Ok(delegations)
}

/// Helper to write a Delegations struct into a csv file.
///
/// It writes in the following format:
/// `delegator,validator,amount-nanos-to-stake,amount-nanos-to-pay-gas
/// <delegator1-address>,<validator-1-address>,2000000000000000,5000000000
/// <delegator1-address>,<validator-2-address>,3000000000000000,5000000000
/// <delegator2-address>,<validator-3-address>,4500000000000000,5000000000`
pub fn to_csv<W: std::io::Write>(&self, writer: W) -> Result<()> {
let mut writer = csv::Writer::from_writer(writer);

writer.write_record([
"delegator",
"validator",
"amount-nanos-to-stake",
"amount-nanos-to-pay-gas",
])?;

for (&delegator, validator_allocations) in &self.allocations {
for validator_allocation in validator_allocations {
writer.write_record(&[
delegator.to_string(),
validator_allocation.validator.to_string(),
validator_allocation.amount_nanos_to_stake.to_string(),
validator_allocation.amount_nanos_to_pay_gas.to_string(),
])?;
}
}

Ok(())
}
}
2 changes: 2 additions & 0 deletions crates/iota-e2e-tests/tests/full_node_migration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const HORNET_SNAPSHOT_PATH: &str = "tests/migration/test_hornet_full_snapshot.bi
const ADDRESS_SWAP_MAP_PATH: &str = "tests/migration/address_swap.csv";
const TEST_TARGET_NETWORK: &str = "alphanet-test";
const MIGRATION_DATA_FILE_NAME: &str = "stardust_object_snapshot.bin";
const DELEGATOR: &str = "0x4f72f788cdf4bb478cf9809e878e6163d5b351c82c11f1ea28750430752e7892";

/// Got from iota-genesis-builder/src/stardust/test_outputs/alias_ownership.rs
const MAIN_ADDRESS_MNEMONIC: &str = "few hood high omit camp keep burger give happy iron evolve draft few dawn pulp jazz box dash load snake gown bag draft car";
Expand All @@ -71,6 +72,7 @@ async fn test_full_node_load_migration_data() -> Result<(), anyhow::Error> {
// A new test cluster can be spawn with the stardust object snapshot
let test_cluster = TestClusterBuilder::new()
.with_migration_data(vec![snapshot_source])
.with_delegator(IotaAddress::from_str(DELEGATOR).unwrap())
.build()
.await;

Expand Down
28 changes: 16 additions & 12 deletions crates/iota-genesis-builder/examples/snapshot_only_test_outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
use std::{fs::File, path::Path};

use clap::{Parser, Subcommand};
use iota_genesis_builder::{
IF_STARDUST_ADDRESS,
stardust::{
parse::HornetSnapshotParser,
test_outputs::{add_snapshot_test_outputs, to_nanos},
},
use iota_genesis_builder::stardust::{
parse::HornetSnapshotParser,
test_outputs::{add_snapshot_test_outputs, to_nanos},
};
use iota_sdk::types::block::address::Ed25519Address;
use iota_types::{
base_types::IotaAddress, gas_coin::STARDUST_TOTAL_SUPPLY_IOTA, stardust::coin_type::CoinType,
};
use iota_sdk::types::block::address::Address;
use iota_types::{gas_coin::STARDUST_TOTAL_SUPPLY_IOTA, stardust::coin_type::CoinType};

const WITH_SAMPLING: bool = false;

Expand All @@ -32,6 +31,8 @@ enum Snapshot {
Iota {
#[clap(long, help = "Path to the Iota Hornet full-snapshot file")]
snapshot_path: String,
#[clap(long, help = "Specify the delegator address")]
delegator: IotaAddress,
},
}

Expand Down Expand Up @@ -62,8 +63,11 @@ fn parse_snapshot<const VERIFY: bool>(
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let (current_path, coin_type) = match cli.snapshot {
Snapshot::Iota { snapshot_path } => (snapshot_path, CoinType::Iota),
let (current_path, delegator, coin_type) = match cli.snapshot {
Snapshot::Iota {
snapshot_path,
delegator,
} => (snapshot_path, delegator, CoinType::Iota),
};
let mut new_path = String::from("test-");
// prepend "test-" before the file name
Expand All @@ -80,7 +84,7 @@ async fn main() -> anyhow::Result<()> {
let (randomness_seed, delegator_address) = match coin_type {
CoinType::Iota => {
// IOTA coin type values
(0, IF_STARDUST_ADDRESS)
(0, delegator)
}
};

Expand All @@ -89,7 +93,7 @@ async fn main() -> anyhow::Result<()> {
&new_path,
coin_type,
randomness_seed,
*Address::try_from_bech32(delegator_address)?.as_ed25519(),
Ed25519Address::from(delegator_address.to_inner()),
WITH_SAMPLING,
)
.await?;
Expand Down
Loading

0 comments on commit f3049e8

Please sign in to comment.