use std::{
io::{self, Error, ErrorKind},
time::Duration,
};
use crate::p as api_p;
use avalanche_types::{
avax, choices::status::Status, formatting, ids, key, platformvm, secp256k1fx,
};
use tokio::time::sleep;
#[derive(Clone, Debug)]
pub struct Tx<T>
where
T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
pub inner: crate::wallet::p::P<T>,
pub subnet_id: ids::Id,
pub genesis_data: Vec<u8>,
pub vm_id: ids::Id,
pub chain_name: String,
pub check_acceptance: bool,
}
impl<T> Tx<T>
where
T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
pub fn new(p: &crate::wallet::p::P<T>) -> Self {
Self {
inner: p.clone(),
subnet_id: ids::Id::empty(),
genesis_data: Vec::new(),
vm_id: ids::Id::empty(),
chain_name: String::new(),
check_acceptance: false,
}
}
#[must_use]
pub fn subnet_id(mut self, subnet_id: ids::Id) -> Self {
self.subnet_id = subnet_id;
self
}
#[must_use]
pub fn genesis_data(mut self, genesis_data: Vec<u8>) -> Self {
self.genesis_data = genesis_data;
self
}
#[must_use]
pub fn vm_id(mut self, vm_id: ids::Id) -> Self {
self.vm_id = vm_id;
self
}
#[must_use]
pub fn chain_name(mut self, chain_name: String) -> Self {
self.chain_name = chain_name;
self
}
#[must_use]
pub fn check_acceptance(mut self, check_acceptance: bool) -> Self {
self.check_acceptance = check_acceptance;
self
}
pub async fn issue(&self) -> io::Result<(ids::Id, bool)> {
let picked_http_rpc = self.inner.inner.pick_http_rpc();
log::info!(
"creating a new chain for subnet {}, vm id {}, chain name {}, via {}",
self.subnet_id,
self.vm_id,
self.chain_name,
picked_http_rpc.1
);
let cur_balance_p = self.inner.balance().await?;
if cur_balance_p < self.inner.inner.create_blockchain_tx_fee {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("key address {} (balance {} nano-AVAX, network {}) does not have enough to cover stake amount + fee {}", self.inner.inner.p_address, cur_balance_p, self.inner.inner.network_name, self.inner.inner.create_blockchain_tx_fee),
));
};
let (ins, unstaked_outs, _, signers) = self
.inner
.spend(0, self.inner.inner.create_blockchain_tx_fee)
.await?;
let mut tx = platformvm::create_chain::Tx {
unsigned_tx: avax::BaseTx {
network_id: self.inner.inner.network_id,
blockchain_id: self.inner.inner.p_chain_id,
transferable_outputs: Some(unstaked_outs),
transferable_inputs: Some(ins),
..Default::default()
},
subnet_id: self.subnet_id,
chain_name: self.chain_name.clone(),
vm_id: self.vm_id,
genesis_data: self.genesis_data.clone(),
subnet_auth: secp256k1fx::Input {
sig_indices: vec![0_u32],
},
..Default::default()
};
tx.sign(signers)?;
let signed_bytes = tx.unsigned_tx.metadata.unwrap().bytes;
let hex_tx = formatting::encode_hex_with_checksum(&signed_bytes);
let resp = api_p::issue_tx(&picked_http_rpc.1, &hex_tx).await?;
if let Some(e) = resp.error {
return Err(Error::new(
ErrorKind::Other,
format!("failed to issue create chain transaction {:?}", e),
));
}
let tx_id = resp.result.unwrap().tx_id;
log::info!("{} successfully issued", tx_id);
if !self.check_acceptance {
log::debug!("skipping checking acceptance...");
return Ok((tx_id, true));
}
sleep(Duration::from_millis(500)).await;
log::info!("polling to confirm create chain transaction");
loop {
let resp = api_p::get_tx_status(&picked_http_rpc.1, &tx_id.to_string()).await?;
let status = resp.result.unwrap().status;
if status == Status::Accepted {
log::info!("{} successfully accepted", tx_id);
break;
}
log::warn!(
"{} {} (not accepted yet in {})",
tx_id,
status,
picked_http_rpc.1
);
sleep(Duration::from_millis(700)).await;
}
Ok((tx_id, true))
}
}