use crate::{
avalanche::{wallets::AvalancheWallet, AVAX_PRIMARY_NETWORK_ID},
errors::*,
};
use avalanche_types::{
ids::{node::Id as NodeId, Id},
key::bls::ProofOfPossession,
wallet::p,
};
use chrono::{DateTime, Duration, Utc};
pub async fn create_subnet(
wallet: &AvalancheWallet,
check_acceptance: bool,
) -> Result<Id, AshError> {
let tx_id = p::create_subnet::Tx::new(&wallet.pchain_wallet.p())
.check_acceptance(check_acceptance)
.issue()
.await
.map_err(|e| AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "create_subnet".to_string(),
msg: format!("failed to create subnet: {e}"),
})?;
Ok(tx_id)
}
pub async fn create_blockchain(
wallet: &AvalancheWallet,
subnet_id: Id,
genesis_data: Vec<u8>,
vm_id: Id,
name: &str,
check_acceptance: bool,
) -> Result<Id, AshError> {
let tx_id = p::create_chain::Tx::new(&wallet.pchain_wallet.p())
.subnet_id(subnet_id)
.genesis_data(genesis_data)
.vm_id(vm_id)
.chain_name(name.to_string())
.check_acceptance(check_acceptance)
.issue()
.await
.map_err(|e| AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "create_blockchain".to_string(),
msg: format!("failed to create blockchain on Subnet {subnet_id}: {e}"),
})?;
Ok(tx_id)
}
pub async fn add_permissioned_subnet_validator(
wallet: &AvalancheWallet,
subnet_id: Id,
node_id: NodeId,
weight: u64,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
check_acceptance: bool,
) -> Result<Id, AshError> {
let (tx_id, success) = p::add_subnet_validator::Tx::new(&wallet.pchain_wallet.p())
.subnet_id(subnet_id)
.node_id(node_id)
.weight(weight)
.start_time(start_time)
.end_time(end_time)
.check_acceptance(check_acceptance)
.poll_initial_wait(Duration::seconds(1).to_std().unwrap())
.issue()
.await
.map_err(|e| AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "add_subnet_validator".to_string(),
msg: format!("failed to add '{node_id}' as validator to Subnet '{subnet_id}': {e}"),
})?;
match success {
true => Ok(tx_id),
false => {
if Id::is_empty(&tx_id) {
Err(AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "add_validator".to_string(),
msg: format!("'{node_id}' is already a validator to Subnet '{subnet_id}'"),
}
.into())
} else {
Err(AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "add_validator".to_string(),
msg: format!(
"failed to add '{node_id}' as validator to Subnet '{subnet_id}': Unknown error"
),
}
.into())
}
}
}
}
pub async fn add_permissionless_subnet_validator(
wallet: &AvalancheWallet,
node_id: NodeId,
subnet_id: Id,
stake_amount: u64,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
reward_fee_percent: u32,
signer: Option<ProofOfPossession>,
check_acceptance: bool,
) -> Result<Id, AshError> {
let (tx_id, success) = p::add_permissionless_validator::Tx::new(&wallet.pchain_wallet.p())
.node_id(node_id)
.subnet_id(match subnet_id.to_string().as_str() {
AVAX_PRIMARY_NETWORK_ID => Id::empty(),
_ => subnet_id,
})
.stake_amount(stake_amount)
.start_time(start_time)
.end_time(end_time)
.reward_fee_percent(reward_fee_percent)
.proof_of_possession(signer.unwrap_or_default())
.check_acceptance(check_acceptance)
.poll_initial_wait(Duration::seconds(1).to_std().unwrap())
.issue()
.await
.map_err(|e| AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "add_validator".to_string(),
msg: format!("failed to add '{node_id}' as Avalanche validator: {e}"),
})?;
match success {
true => Ok(tx_id),
false => {
if Id::is_empty(&tx_id) {
Err(AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "add_validator".to_string(),
msg: format!("'{node_id}' is already an Avalanche validator"),
}
.into())
} else {
Err(AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "add_validator".to_string(),
msg: format!("failed to add '{node_id}' as Avalanche validator: Unknown error"),
}
.into())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::avalanche::{
nodes::generate_node_bls_key,
vms::{encode_genesis_data, subnet_evm::AVAX_SUBNET_EVM_ID, AvalancheVmType},
AvalancheNetwork,
};
use chrono::Duration;
use std::{fs, str::FromStr};
const AVAX_EWOQ_PRIVATE_KEY: &str =
"PrivateKey-ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN";
const NETWORK_RUNNER_PCHAIN_ADDR: &str = "P-custom18jma8ppw3nhx5r4ap8clazz0dps7rv5u9xde7p";
const NETWORK_RUNNER_NODE_ID: &str = "NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg";
fn load_test_network() -> AvalancheNetwork {
AvalancheNetwork::load("local", Some("tests/conf/avalanche-network-runner.yml")).unwrap()
}
#[async_std::test]
#[serial_test::serial]
#[ignore]
async fn test_create_subnet() {
let mut local_network = load_test_network();
let local_wallet = local_network
.create_wallet_from_cb58(AVAX_EWOQ_PRIVATE_KEY)
.unwrap();
let tx_id = create_subnet(&local_wallet, true).await.unwrap();
local_network.update_subnets().unwrap();
let subnet = local_network.get_subnet(tx_id).unwrap();
assert_eq!(subnet.threshold, 1);
assert_eq!(subnet.control_keys.len(), 1);
assert_eq!(subnet.control_keys[0], NETWORK_RUNNER_PCHAIN_ADDR);
}
#[async_std::test]
#[serial_test::serial]
#[ignore]
async fn test_create_blockchain() {
let mut local_network = load_test_network();
let local_wallet = local_network
.create_wallet_from_cb58(AVAX_EWOQ_PRIVATE_KEY)
.unwrap();
let genesis_str = fs::read_to_string("tests/genesis/subnet-evm.json").unwrap();
let genesis_data = encode_genesis_data(AvalancheVmType::SubnetEVM, &genesis_str).unwrap();
let subnet_id = create_subnet(&local_wallet, true).await.unwrap();
let tx_id = create_blockchain(
&local_wallet,
subnet_id,
genesis_data,
Id::from_str(AVAX_SUBNET_EVM_ID).unwrap(),
"testCreateBlockchain",
true,
)
.await
.unwrap();
local_network.update_subnets().unwrap();
local_network.update_blockchains().unwrap();
let subnet = local_network.get_subnet(subnet_id).unwrap();
let blockchain = subnet.get_blockchain(tx_id).unwrap();
assert_eq!(blockchain.name, "testCreateBlockchain");
assert_eq!(blockchain.vm_id, Id::from_str(AVAX_SUBNET_EVM_ID).unwrap());
}
#[async_std::test]
#[serial_test::serial]
#[ignore]
async fn test_add_validators() {
let mut local_network = load_test_network();
let local_wallet = local_network
.create_wallet_from_cb58(AVAX_EWOQ_PRIVATE_KEY)
.unwrap();
let subnet_id = create_subnet(&local_wallet, true).await.unwrap();
let start_time = Utc::now() + Duration::seconds(20);
let end_time = Utc::now() + Duration::seconds(86420);
add_permissioned_subnet_validator(
&local_wallet,
subnet_id,
NodeId::from_str(NETWORK_RUNNER_NODE_ID).unwrap(),
100,
start_time,
end_time,
true,
)
.await
.unwrap();
local_network.update_subnets().unwrap();
local_network.update_subnet_validators(subnet_id).unwrap();
let subnet_validator = local_network
.get_subnet(subnet_id)
.unwrap()
.get_validator(NodeId::from_str(NETWORK_RUNNER_NODE_ID).unwrap());
assert!(subnet_validator.is_ok());
assert_eq!(subnet_validator.unwrap().weight, Some(100));
let (_, pop) = generate_node_bls_key().unwrap();
let avalanche_validator = add_permissionless_subnet_validator(
&local_wallet,
NodeId::from_str(NETWORK_RUNNER_NODE_ID).unwrap(),
Id::from_str(AVAX_PRIMARY_NETWORK_ID).unwrap(),
1 * 1_000_000_000,
start_time,
end_time,
2,
Some(pop),
true,
)
.await;
assert!(avalanche_validator.is_err());
assert_eq!(
avalanche_validator.err(),
Some(AshError::AvalancheWalletError(
AvalancheWalletError::IssueTx {
blockchain_name: "P-Chain".to_string(),
tx_type: "add_validator".to_string(),
msg: format!(
"'{}' is already an Avalanche validator",
NodeId::from_str(NETWORK_RUNNER_NODE_ID).unwrap()
),
}
))
)
}
}