#![cfg(any(test, feature = "test_helpers"))]
#[cfg(test)]
use crate::helpers::tests::entropy::runtime_types::bounded_collections::bounded_vec::BoundedVec;
use crate::{
app,
chain_api::{
entropy::{
self,
runtime_types::entropy_runtime::RuntimeCall,
runtime_types::pallet_staking_extension::pallet::{JumpStartStatus, ServerInfo},
},
EntropyConfig,
},
helpers::{
launch::{
setup_kv_store, setup_latest_block_number, Configuration, ValidatorName,
DEFAULT_ENDPOINT,
},
logger::{Instrumentation, Logger},
substrate::submit_transaction,
},
AppState,
};
use axum::{routing::IntoMakeService, Router};
use entropy_client::substrate::query_chain;
use entropy_kvdb::{get_db_path, kv_manager::KvManager, BuildType};
use entropy_protocol::PartyId;
#[cfg(test)]
use entropy_shared::EncodedVerifyingKey;
use entropy_shared::NETWORK_PARENT_KEY;
use sp_keyring::AccountKeyring;
use std::{fmt, net::SocketAddr, path::PathBuf, str, time::Duration};
use subxt::{
backend::legacy::LegacyRpcMethods, ext::sp_core::sr25519, tx::PairSigner,
utils::AccountId32 as SubxtAccountId32, Config, OnlineClient,
};
use tokio::sync::OnceCell;
pub static LOGGER: OnceCell<()> = OnceCell::const_new();
pub async fn initialize_test_logger() {
let instrumentation = Instrumentation { logger: Logger::Pretty, ..Default::default() };
*LOGGER.get_or_init(|| instrumentation.setup()).await
}
pub async fn setup_client() -> AppState {
let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
let storage_path: PathBuf = get_db_path(BuildType::Test).into();
let (kv_store, sr25519_pair, x25519_secret, _should_backup) =
setup_kv_store(&Some(ValidatorName::Alice), Some(storage_path.clone())).await.unwrap();
let app_state = AppState::new(configuration, kv_store.clone(), sr25519_pair, x25519_secret);
app_state.cache.make_ready().unwrap();
setup_latest_block_number(app_state.clone(), 0).unwrap();
let app = app(app_state.clone()).into_make_service();
let listener = tokio::net::TcpListener::bind("0.0.0.0:3001")
.await
.expect("Unable to bind to given server address.");
tokio::spawn(async move {
axum::serve(listener, app).await.unwrap();
});
app_state
}
pub async fn create_clients(
key_number: String,
values: Vec<Vec<u8>>,
keys: Vec<String>,
validator_name: &Option<ValidatorName>,
) -> (IntoMakeService<Router>, KvManager, SubxtAccountId32) {
let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
let path = format!(".entropy/testing/test_db_{key_number}");
let _ = std::fs::remove_dir_all(path.clone());
let (kv_store, sr25519_pair, x25519_secret, _should_backup) =
setup_kv_store(validator_name, Some(path.into())).await.unwrap();
let app_state = AppState::new(configuration, kv_store.clone(), sr25519_pair, x25519_secret);
for (i, value) in values.into_iter().enumerate() {
let reservation = kv_store.clone().kv().reserve_key(keys[i].to_string()).await.unwrap();
let _ = kv_store.clone().kv().put(reservation, value).await;
}
app_state.cache.make_ready().unwrap();
setup_latest_block_number(app_state.clone(), 0).unwrap();
let account_id = app_state.subxt_account_id();
let app = app(app_state).into_make_service();
(app, kv_store, account_id)
}
#[derive(Copy, Clone, PartialEq)]
pub enum ChainSpecType {
Integration,
IntegrationJumpStarted,
}
impl fmt::Display for ChainSpecType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
ChainSpecType::Integration => "integration-tests",
ChainSpecType::IntegrationJumpStarted => "integration-tests-jumpstarted",
},
)
}
}
pub async fn spawn_testing_validators(
chain_spec_type: ChainSpecType,
) -> (Vec<String>, Vec<PartyId>) {
let ports = [3001i64, 3002, 3003, 3004];
let (alice_axum, alice_kv, alice_id) =
create_clients("validator1".to_string(), vec![], vec![], &Some(ValidatorName::Alice)).await;
let alice_id = PartyId::new(alice_id);
let (bob_axum, bob_kv, bob_id) =
create_clients("validator2".to_string(), vec![], vec![], &Some(ValidatorName::Bob)).await;
let bob_id = PartyId::new(bob_id);
let (charlie_axum, charlie_kv, charlie_id) =
create_clients("validator3".to_string(), vec![], vec![], &Some(ValidatorName::Charlie))
.await;
let charlie_id = PartyId::new(charlie_id);
let listener_alice = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", ports[0]))
.await
.expect("Unable to bind to given server address.");
tokio::spawn(async move {
axum::serve(listener_alice, alice_axum).await.unwrap();
});
let listener_bob = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", ports[1]))
.await
.expect("Unable to bind to given server address.");
tokio::spawn(async move {
axum::serve(listener_bob, bob_axum).await.unwrap();
});
let listener_charlie = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", ports[2]))
.await
.expect("Unable to bind to given server address.");
tokio::spawn(async move {
axum::serve(listener_charlie, charlie_axum).await.unwrap();
});
let (dave_axum, _dave_kv, dave_id) =
create_clients("validator4".to_string(), vec![], vec![], &Some(ValidatorName::Dave)).await;
let listener_dave = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", ports[3]))
.await
.expect("Unable to bind to given server address.");
tokio::spawn(async move {
axum::serve(listener_dave, dave_axum).await.unwrap();
});
let dave_id = PartyId::new(dave_id);
let ids = vec![alice_id, bob_id, charlie_id, dave_id];
if chain_spec_type == ChainSpecType::IntegrationJumpStarted {
put_keyshares_in_db(ValidatorName::Alice, alice_kv).await;
put_keyshares_in_db(ValidatorName::Bob, bob_kv).await;
put_keyshares_in_db(ValidatorName::Charlie, charlie_kv).await;
}
tokio::time::sleep(Duration::from_secs(1)).await;
let ips = ports.iter().map(|port| format!("127.0.0.1:{port}")).collect();
(ips, ids)
}
pub async fn put_keyshares_in_db(validator_name: ValidatorName, kvdb: KvManager) {
let keyshare_bytes = {
let project_root = project_root::get_project_root().expect("Error obtaining project root.");
let file_path = project_root.join(format!(
"crates/testing-utils/keyshares/production/keyshare-held-by-{}.keyshare",
validator_name
));
std::fs::read(file_path).unwrap()
};
let reservation = kvdb.kv().reserve_key(hex::encode(NETWORK_PARENT_KEY)).await.unwrap();
kvdb.kv().put(reservation, keyshare_bytes).await.unwrap();
}
pub async fn remove_program(
entropy_api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
deployer: &sr25519::Pair,
program_hash: <EntropyConfig as Config>::Hash,
) {
let remove_program_tx = entropy::tx().programs().remove_program(program_hash);
let deployer = PairSigner::<EntropyConfig, sr25519::Pair>::new(deployer.clone());
submit_transaction(entropy_api, rpc, &deployer, &remove_program_tx, None).await.unwrap();
}
pub async fn run_to_block(rpc: &LegacyRpcMethods<EntropyConfig>, block_run: u32) {
let mut current_block = 0;
while current_block < block_run {
current_block = rpc.chain_get_header(None).await.unwrap().unwrap().number;
}
}
#[cfg(test)]
pub async fn unsafe_get(client: &reqwest::Client, query_key: String, port: u32) -> Vec<u8> {
let get_query = crate::r#unsafe::api::UnsafeQuery::new(query_key, vec![]).to_json();
let get_result = client
.post(format!("http://127.0.0.1:{}/unsafe/get", port))
.header("Content-Type", "application/json")
.body(get_query)
.send()
.await
.unwrap();
get_result.bytes().await.unwrap().into()
}
#[cfg(test)]
pub async fn store_program_and_register(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
user: &sr25519::Pair,
deployer: &sr25519::Pair,
) -> (EncodedVerifyingKey, sp_core::H256) {
use entropy_client::{
self as test_client,
chain_api::entropy::runtime_types::pallet_registry::pallet::ProgramInstance,
};
use entropy_testing_utils::constants::TEST_PROGRAM_WASM_BYTECODE;
use sp_core::Pair;
let program_hash = test_client::store_program(
api,
rpc,
deployer,
TEST_PROGRAM_WASM_BYTECODE.to_owned(),
vec![],
vec![],
vec![],
0u8,
)
.await
.unwrap();
let (verifying_key, _registered_info) = test_client::register(
api,
rpc,
user.clone(),
SubxtAccountId32(deployer.public().0), BoundedVec(vec![ProgramInstance { program_pointer: program_hash, program_config: vec![] }]),
)
.await
.unwrap();
(verifying_key, program_hash)
}
pub async fn do_jump_start(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
pair: sr25519::Pair,
) {
run_to_block(rpc, 8).await;
let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1;
put_jumpstart_request_on_chain(api, rpc, pair).await;
run_to_block(rpc, block_number + 1).await;
let jump_start_status_query = entropy::storage().staking_extension().jump_start_progress();
let mut jump_start_status = query_chain(api, rpc, jump_start_status_query.clone(), None)
.await
.unwrap()
.unwrap()
.jump_start_status;
let mut i = 0;
while format!("{:?}", jump_start_status) != format!("{:?}", JumpStartStatus::Done) {
tokio::time::sleep(Duration::from_secs(1)).await;
jump_start_status = query_chain(api, rpc, jump_start_status_query.clone(), None)
.await
.unwrap()
.unwrap()
.jump_start_status;
i += 1;
if i > 75 {
panic!("Jump start failed");
}
}
assert_eq!(format!("{:?}", jump_start_status), format!("{:?}", JumpStartStatus::Done));
}
async fn put_jumpstart_request_on_chain(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
pair: sr25519::Pair,
) {
let account = PairSigner::<EntropyConfig, sp_core::sr25519::Pair>::new(pair);
let registering_tx = entropy::tx().registry().jump_start_network();
submit_transaction(api, rpc, &account, ®istering_tx, None).await.unwrap();
}
pub fn get_port(server_info: &ServerInfo<SubxtAccountId32>) -> u32 {
let socket_address: SocketAddr =
str::from_utf8(&server_info.endpoint).unwrap().parse().unwrap();
socket_address.port().into()
}
pub async fn call_set_storage(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
call: RuntimeCall,
) {
let set_storage = entropy::tx().sudo().sudo(call);
let alice = AccountKeyring::Alice;
let signature_request_pair_signer =
PairSigner::<EntropyConfig, sp_core::sr25519::Pair>::new(alice.into());
submit_transaction(api, rpc, &signature_request_pair_signer, &set_storage, None).await.unwrap();
}