Skip to content

Commit

Permalink
A0-596: Proper staking accounts setup for local runs (#388)
Browse files Browse the repository at this point in the history
* Changed chain spec generation to handle distinct controller and stash accounts for local runs.

* Improved modularity of chain spec generation; changed devnet deployment config so that the state of Balances is not copied over from the forked chain state.

* Fixed linter errors by decreasing complexity of return types.
  • Loading branch information
maciejzelaszczyk authored Apr 26, 2022
1 parent fff008e commit f4009d7
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy-to-devnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
# generate chainspec, it will reuse keys from the synced keystore
docker run -i -v $(pwd)/data:/data --env RUST_BACKTRACE=1 --entrypoint "/usr/local/bin/aleph-node" public.ecr.aws/p6e8q1z1/aleph-node:${COMMIT_ID} bootstrap-chain --raw --base-path /data --chain-id a0dnet1 --account-ids 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH,5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o,5Dfis6XL8J2P6JHUnUtArnFWndn62SydeP8ee8sG2ky9nfm9,5F4H97f7nQovyrbiq4ZetaaviNwThSVcFobcA5aGab6167dK,5DiDShBWa1fQx6gLzpf3SFBhMinCoyvHM1BWjPNsmXS8hkrW,5EFb84yH9tpcFuiKUcsmdoF7xeeY3ajG1ZLQimxQoFt9HMKR,5DZLHESsfGrJ5YzT3HuRPXsSNb589xQ4Unubh1mYLodzKdVY,5GHJzqvG6tXnngCpG7B12qjUvbo5e4e9z8Xjidk3CQZHxTPZ,5CUnSsgAyLND3bxxnfNhgWXSe9Wn676JzLpGLgyJv858qhoX,5CVKn7HAZW1Ky4r7Vkgsr7VEW88C2sHgUNDiwHY9Ct2hjU8q --sudo-account-id 5F4SvwaUEQubiqkPF8YnRfcN77cLsT2DfG4vFeQmSXNjR7hD > chainspec.skeleton.json
docker run -i -v $(pwd):/app public.ecr.aws/p6e8q1z1/fork-off:latest --http-rpc-endpoint=https://rpc.test.azero.dev --initial-spec-path=chainspec.skeleton.json --combined-spec-path=chainspec.json --pallets-keep-state=Aura,Aleph,Sudo,Staking,Session,Elections
docker run -i -v $(pwd):/app public.ecr.aws/p6e8q1z1/fork-off:latest --http-rpc-endpoint=https://rpc.test.azero.dev --initial-spec-path=chainspec.skeleton.json --combined-spec-path=chainspec.json --pallets-keep-state=Aura,Aleph,Sudo,Staking,Session,Elections,Balances
aws s3 cp chainspec.json s3://alephzero-devnet-eu-central-1-keys-bucket/chainspec.json
Expand Down
233 changes: 184 additions & 49 deletions bin/node/src/chain_spec.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use aleph_primitives::{
staking::{MIN_NOMINATOR_BOND, MIN_VALIDATOR_BOND},
AuthorityId as AlephId, ADDRESSES_ENCODING, TOKEN_DECIMALS,
AuthorityId as AlephId, ADDRESSES_ENCODING, TOKEN, TOKEN_DECIMALS,
};
use aleph_runtime::{
AccountId, AuraConfig, BalancesConfig, ElectionsConfig, GenesisConfig, Perbill, SessionConfig,
Expand All @@ -13,7 +13,7 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{Number, Value};
use sp_application_crypto::Ss58Codec;
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_core::{Pair, Public};
use sp_core::{sr25519, Pair, Public};
use sp_runtime::traits::{IdentifyAccount, Verify};
use std::{collections::HashSet, path::PathBuf, str::FromStr};
use structopt::StructOpt;
Expand Down Expand Up @@ -201,28 +201,38 @@ fn system_properties(token_symbol: String) -> serde_json::map::Map<String, Value
.collect()
}

pub fn devnet_config(
/// Generate chain spec.
pub fn config(
chain_params: ChainParams,
authorities: Vec<AuthorityKeys>,
) -> Result<ChainSpec, String> {
// TODO fix it properly so there's a default configuration for distinct stash and controller
// accounts, and linked controller accounts with validators
// for now it's better to leave it as empty not to imply (not advised) initial staking configuration
// in which stash == controller == validator
generate_chain_spec_config(chain_params, authorities, vec![])
generate_chain_spec_config(chain_params, authorities, None, None)
}

pub fn config(
/// Generate chain spec for local runs.
/// Stash and controller accounts are generated for the specified authorities.
pub fn local_config(
chain_params: ChainParams,
authorities: Vec<AuthorityKeys>,
) -> Result<ChainSpec, String> {
generate_chain_spec_config(chain_params, authorities, vec![])
let authority_accounts: Vec<AccountId> = to_account_ids(&authorities).collect();
let controller_accounts: Vec<AccountId> =
generate_staking_accounts(authority_accounts.clone(), StakingAccount::Controller);
let stash_accounts: Vec<AccountId> =
generate_staking_accounts(authority_accounts, StakingAccount::Stash);
generate_chain_spec_config(
chain_params,
authorities,
Some(controller_accounts),
Some(stash_accounts),
)
}

fn generate_chain_spec_config(
chain_params: ChainParams,
authorities: Vec<AuthorityKeys>,
stakers: Vec<AccountId>,
controller_accounts: Option<Vec<AccountId>>,
stash_accounts: Option<Vec<AccountId>>,
) -> Result<ChainSpec, String> {
let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?;
let token_symbol = String::from(chain_params.token_symbol());
Expand All @@ -244,7 +254,8 @@ fn generate_chain_spec_config(
authorities.clone(), // Initial PoA authorities, will receive funds
sudo_account.clone(), // Sudo account, will also be pre funded
faucet_account.clone(), // Pre-funded faucet account
stakers.clone(),
controller_accounts.clone(), // Controller accounts for staking.
stash_accounts.clone(), // Stash accounts for staking.
)
},
// Bootnodes
Expand All @@ -266,13 +277,147 @@ fn deduplicate(accounts: Vec<AccountId>) -> Vec<AccountId> {
set.into_iter().collect()
}

/// Configure initial storage state for FRAME modules
/// Type of staking account: stash or controller.
enum StakingAccount {
Controller,
Stash,
}

/// Given a set of accounts and an account type returns the corresponding staking accounts.
fn generate_staking_accounts(
accounts: Vec<AccountId>,
account_type: StakingAccount,
) -> Vec<AccountId> {
let account_suffix = match account_type {
StakingAccount::Controller => "Controller",
StakingAccount::Stash => "Stash",
};
accounts
.into_iter()
.enumerate()
.map(|(index, _account)| {
get_account_id_from_seed::<sr25519::Public>(
format!("//{}//{}", index, account_suffix).as_str(),
)
})
.collect()
}

// total issuance of 300M (for devnet/tests/local runs only)
const TOTAL_ISSUANCE: u128 = 300_000_000u128 * 10u128.pow(TOKEN_DECIMALS);

/// Calculate initial endowments such that total issuance is kept approximately constant.
fn calculate_initial_endowment(accounts: &[AccountId]) -> u128 {
TOTAL_ISSUANCE / (accounts.len() as u128)
}

/// Provides configuration for staking by defining balances, members, keys and stakers.
struct AccountsConfig {
balances: Vec<(AccountId, u128)>,
members: Vec<AccountId>,
keys: Vec<(AccountId, AccountId, SessionKeys)>,
stakers: Vec<(AccountId, AccountId, u128, StakerStatus<AccountId>)>,
}

/// Provides accounts for GenesisConfig setup based on distinct staking accounts.
/// Assumes validator, controller and stash are all separate accounts.
fn configure_distinct_staking_accounts(
unique_accounts_balances: Vec<(AccountId, u128)>,
authorities: Vec<AuthorityKeys>,
controllers: Vec<AccountId>,
stashes: Vec<AccountId>,
) -> AccountsConfig {
let balances = unique_accounts_balances
.into_iter()
.chain(
controllers
.clone()
.into_iter()
.map(|account| (account, TOKEN)),
)
.chain(
stashes
.clone()
.into_iter()
.map(|account| (account, MIN_VALIDATOR_BOND + TOKEN)),
)
.collect();

let keys = authorities
.iter()
.zip(stashes.clone())
.map(|(auth, stash)| {
(
stash.clone(),
stash,
SessionKeys {
aura: auth.aura_key.clone(),
aleph: auth.aleph_key.clone(),
},
)
})
.collect();

let stakers = stashes
.clone()
.into_iter()
.zip(controllers)
.map(|(stash, controller)| {
(
stash,
controller,
MIN_VALIDATOR_BOND,
StakerStatus::Validator,
)
})
.collect();

AccountsConfig {
balances,
members: stashes,
keys,
stakers,
}
}

/// Provides accounts for GenesisConfig setup based on non-distinct staking accounts.
/// Assumes validator, controller and stash are all the same account.
fn configure_non_distinct_staking_accounts(
unique_accounts_balances: Vec<(AccountId, u128)>,
authorities: Vec<AuthorityKeys>,
) -> AccountsConfig {
let members = to_account_ids(&authorities).collect();
let keys = authorities
.iter()
.map(|auth| {
(
auth.account_id.clone(),
auth.account_id.clone(),
SessionKeys {
aura: auth.aura_key.clone(),
aleph: auth.aleph_key.clone(),
},
)
})
.collect();

AccountsConfig {
balances: unique_accounts_balances,
members,
keys,
stakers: vec![],
}
}

/// Configure initial storage state for FRAME modules.
/// Possible to provide distinct controller and stash accounts.
fn generate_genesis_config(
wasm_binary: &[u8],
authorities: Vec<AuthorityKeys>,
sudo_account: AccountId,
faucet_account: Option<AccountId>,
stakers: Vec<AccountId>,
controller_accounts: Option<Vec<AccountId>>,
stash_accounts: Option<Vec<AccountId>>,
) -> GenesisConfig {
let special_accounts = match faucet_account {
Some(faucet_id) => vec![sudo_account.clone(), faucet_id],
Expand All @@ -282,15 +427,30 @@ fn generate_genesis_config(
// NOTE: some combinations of bootstrap chain arguments can potentially
// lead to duplicated rich accounts, e.g. if a sudo account is also an authority
// which is why we remove the duplicates if any here
let unique_accounts: Vec<AccountId> = deduplicate(
let unique_accounts = deduplicate(
to_account_ids(&authorities)
.chain(special_accounts)
.chain(stakers.iter().cloned())
.collect(),
);

// to have total issuance 300M (for devnet/tests/local runs only)
const ENDOWMENT: u128 = 60_000_000u128 * 10u128.pow(TOKEN_DECIMALS);
let endowment = calculate_initial_endowment(&unique_accounts);

let unique_accounts_balances = unique_accounts
.into_iter()
.map(|account| (account, endowment))
.collect::<Vec<_>>();

let validator_count = authorities.len() as u32;

let accounts_config = match (controller_accounts, stash_accounts) {
(Some(controllers), Some(stashes)) => configure_distinct_staking_accounts(
unique_accounts_balances,
authorities,
controllers,
stashes,
),
(_, _) => configure_non_distinct_staking_accounts(unique_accounts_balances, authorities),
};

GenesisConfig {
system: SystemConfig {
Expand All @@ -299,10 +459,7 @@ fn generate_genesis_config(
},
balances: BalancesConfig {
// Configure endowed accounts with an initial, significant balance
balances: unique_accounts
.into_iter()
.map(|account| (account, ENDOWMENT))
.collect(),
balances: accounts_config.balances,
},
aura: AuraConfig {
authorities: vec![],
Expand All @@ -312,41 +469,19 @@ fn generate_genesis_config(
key: sudo_account,
},
elections: ElectionsConfig {
members: to_account_ids(&authorities).collect(),
members: accounts_config.members.clone(),
},
session: SessionConfig {
keys: authorities
.iter()
.map(|auth| {
(
auth.account_id.clone(),
auth.account_id.clone(),
SessionKeys {
aura: auth.aura_key.clone(),
aleph: auth.aleph_key.clone(),
},
)
})
.collect(),
keys: accounts_config.keys,
},
staking: StakingConfig {
force_era: Forcing::NotForcing,
validator_count: authorities.len() as u32,
validator_count,
// to satisfy some e2e tests as this cannot be changed during runtime
minimum_validator_count: 4,
invulnerables: to_account_ids(&authorities).collect(),
invulnerables: accounts_config.members,
slash_reward_fraction: Perbill::from_percent(10),
stakers: stakers
.into_iter()
.map(|account_id| {
(
account_id.clone(),
account_id,
MIN_VALIDATOR_BOND,
StakerStatus::Validator,
)
})
.collect(),
stakers: accounts_config.stakers,
min_validator_bond: MIN_VALIDATOR_BOND,
min_nominator_bond: MIN_NOMINATOR_BOND,
..Default::default()
Expand Down
8 changes: 4 additions & 4 deletions bin/node/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn aleph_key(keystore: &impl SyncCryptoStore) -> AlephId {
.into()
}

/// Returns peer id, if not p2p key found under base_path/account-id/node-key-file a new provate key gets generated
/// Returns peer id, if not p2p key found under base_path/account-id/node-key-file a new private key gets generated
fn p2p_key(chain_params: &ChainParams, account_id: &AccountId) -> SerializablePeerId {
let authority = account_id.to_string();
let file = chain_params
Expand Down Expand Up @@ -132,8 +132,8 @@ impl BootstrapChainCmd {
})
.collect();

let chain_spec = match self.is_devnet() {
true => chain_spec::devnet_config(self.chain_params.clone(), genesis_authorities)?,
let chain_spec = match self.is_local_run() {
true => chain_spec::local_config(self.chain_params.clone(), genesis_authorities)?,
false => chain_spec::config(self.chain_params.clone(), genesis_authorities)?,
};

Expand All @@ -145,7 +145,7 @@ impl BootstrapChainCmd {
Ok(())
}

fn is_devnet(&self) -> bool {
fn is_local_run(&self) -> bool {
self.chain_params.chain_id() == DEFAULT_CHAIN_ID
}
}
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion fork-off/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub struct Config {
#[clap(long, default_value_t = 5)]
pub num_workers: u32,

/// which modules to set in forked spec
/// which modules to keep in forked spec
#[clap(
long,
multiple_occurrences = true,
Expand Down

0 comments on commit f4009d7

Please sign in to comment.