Merge pull request #2910 from input-output-hk/iapyx-qr-code
[Tests] Move vit related structs to new workspace
[Tests] Move vit related structs to new workspace
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
dependencies = [
"lazy_static",
"regex 1.4.3",
]
[[package]]
name = "addr2line"
version = "0.14.1"
"memchr 0.1.11",
]
[[package]]
name = "aho-corasick"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
dependencies = [
"memchr 2.3.4",
]
[[package]]
name = "aho-corasick"
version = "0.7.15"
"winapi 0.3.9",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "anyhow"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
[[package]]
name = "askama"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dc2a4b6d7f812d2b13d251ae792caecebd635d6401761162d4b71d5ebe1a010"
dependencies = [
"askama_derive",
"askama_escape",
"askama_shared",
]
[[package]]
name = "askama_derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ee2fff0f22ad5d215cace1227cd036c28e81e26206763bb837b6d0e766c87d"
dependencies = [
"askama_shared",
"nom",
"proc-macro2 0.4.30",
"quote 0.6.13",
"syn 0.15.44",
]
[[package]]
name = "askama_escape"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0de942230b5beedaa9e1d64df5b76fa1c97002e4c7982897be899cccf40621d"
[[package]]
name = "askama_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6dfa6b6d254fd066a8bbed9a8f913123e3f701db89216ad4f0aff04ad87718c"
dependencies = [
"askama_escape",
"humansize",
"num-traits",
"serde",
"serde_derive",
"toml 0.4.10",
]
[[package]]
name = "assert_cmd"
version = "1.0.2"
"tempfile",
]
[[package]]
name = "async-channel"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-graphql"
version = "2.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a20530b1e9f3a4a33ba6a78384a18d1505a91515cf1d5aa2d94b463ffb851f"
dependencies = [
"async-graphql-derive",
"async-graphql-parser",
"async-graphql-value",
"async-stream 0.3.0",
"async-trait",
"blocking",
"bson",
"chrono",
"chrono-tz",
"fnv",
"futures-channel",
"futures-timer",
"futures-util",
"http",
"indexmap",
"log 0.4.11",
"lru",
"multer",
"num-traits",
"once_cell",
"pin-project-lite 0.2.4",
"regex 1.4.3",
"serde",
"serde_json",
"sha2",
"spin 0.7.0",
"static_assertions",
"tempfile",
"thiserror",
"tracing",
"url",
"uuid",
]
[[package]]
name = "async-graphql-derive"
version = "2.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75763cc369e523724e703a6077d9a913f4bd74741ec8d0f08ad1a3c6a11cd91"
dependencies = [
"Inflector",
"async-graphql-parser",
"darling",
"proc-macro-crate",
"proc-macro2 1.0.24",
"quote 1.0.8",
"syn 1.0.58",
"thiserror",
]
[[package]]
name = "async-graphql-parser"
version = "2.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a33dd3bc63cff9e57ab8c1fb3d6a396d277fb051937ec0298281d8ad0ea39dd"
dependencies = [
"async-graphql-value",
"pest",
"pest_derive",
"serde",
"serde_json",
]
[[package]]
name = "async-graphql-value"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d3aa3cd3696ffd8decb10f5053affc78cb33ecfc545e480072bbc600e6723d"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "async-graphql-warp"
version = "2.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea142666cbee77a4b972ecc1cedc0eee47f57aa8a7397bbf0641f0227c3ce6a3"
dependencies = [
"async-graphql",
"testing/jormungandr-testing-utils",
"testing/jormungandr-integration-tests",
"testing/jormungandr-scenario-tests",
"testing/iapyx",
"testing/mjolnir",
]
/Cargo.lock
/target
/vendor
/.vscode
[package]
name = "iapyx"
version = "0.1.0"
authors = ["dkijania <[email protected]>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
wallet-core = { git = "https://github.com/input-output-hk/chain-wallet-libs.git", branch = "master" }
wallet = { git = "https://github.com/input-output-hk/chain-wallet-libs.git", branch = "master" }
hdkeygen = { git = "https://github.com/input-output-hk/chain-wallet-libs.git", branch = "master" }
bip39 = { git = "https://github.com/input-output-hk/chain-wallet-libs.git", branch = "master" }
chain-crypto = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" }
chain-core = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" }
chain-addr = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" }
chain-ser = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" }
chain-impl-mockchain = { git = "https://github.com/input-output-hk/chain-libs.git", branch = "master" }
hex = "0.4"
rand = "0.7"
rand_core = "0.5"
cryptoxide = "0.2.0"
ed25519-bip32 = "^0.3.1"
jormungandr-testing-utils = { path = "../jormungandr-testing-utils"}
jormungandr-lib = { path = "../../jormungandr-lib" }
jortestkit = { git = "https://github.com/input-output-hk/jortestkit.git", branch = "master" }
hyper = "0.13.6"
thiserror = "1.0"
serde_json = "1.0.53"
serde = { version = "1.0", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
regex = "*"
dialoguer = "0.6.2"
structopt = "0.3"
console = "0.11"
warp = "0.2.5"
warp-reverse-proxy = "0.1.0"
tokio = { version = "0.2", features = ["macros"] }
url = "2.1.1"
[dependencies.reqwest]
version = "0.10.6"
default-features = false
features = ["blocking"]
\ No newline at end of file
# iapyx
Test wallet based on rust chain-wallet-libs.
It contains two apis: Controller and MultiController, which can emulate wallet behavior, and also to cli utilities:
## iapyx-cli:
command line tool to live operations on wallet (recover,voting etc)
## iapyx-load:
load tool for generating load over backend
example:
`cargo run --bin iapyx-load -- --address 127.0.0.1:8000 --pace 100 --progress-bar-mode monitor --threads 4 -- mnemonics .\mnemonics.txt`
Where mnemonics.txt should have format like:
```
town lift follow more chronic lunch weird uniform earth census proof cave gap fancy topic year leader phrase state circle cloth reward dish survey act punch bounce
neck bulb teach illegal try monitor claw rival amount boring provide village rival draft stone
```
mod node;
mod proxy;
mod vit_station;
use crate::Proposal;
use crate::SimpleVoteStatus;
use chain_core::mempack::Readable;
use chain_core::{mempack::ReadBuf, property::Fragment as _};
use chain_impl_mockchain::{
block::Block,
fragment::{Fragment, FragmentId},
};
use chain_ser::deser::Deserialize;
use jormungandr_lib::interfaces::AccountIdentifier;
use jormungandr_lib::interfaces::{AccountState, FragmentLog, VotePlanStatus};
use jormungandr_testing_utils::testing::node::Explorer;
use jormungandr_testing_utils::testing::node::RestSettings;
use node::{RestError as NodeRestError, WalletNodeRestClient};
pub use proxy::{ProxyClient, ProxyClientError, ProxyServerError, ProxyServerStub};
use std::collections::HashMap;
use std::str::FromStr;
use thiserror::Error;
use vit_station::{RestError as VitRestError, VitStationRestClient};
use wallet::{AccountId, Settings};
pub struct WalletBackend {
node_client: WalletNodeRestClient,
vit_client: VitStationRestClient,
proxy_client: ProxyClient,
explorer_client: Explorer,
}
impl WalletBackend {
pub fn new_from_addresses(
proxy_address: String,
node_address: String,
vit_address: String,
node_rest_settings: RestSettings,
) -> Self {
let mut backend = Self {
node_client: WalletNodeRestClient::new(
format!("http://{}/api", node_address),
node_rest_settings.clone(),
),
vit_client: VitStationRestClient::new(vit_address),
proxy_client: ProxyClient::new(proxy_address),
explorer_client: Explorer::new(node_address),
};
if node_rest_settings.enable_debug {
backend.enable_logs()
}
backend
}
pub fn new(address: String, node_rest_settings: RestSettings) -> Self {
Self::new_from_addresses(
address.clone(),
address.clone(),
address,
node_rest_settings,
)
}
pub fn send_fragment(&self, transaction: Vec<u8>) -> Result<FragmentId, WalletBackendError> {
self.node_client.send_fragment(transaction.clone())?;
let fragment = Fragment::deserialize(transaction.as_slice())?;
Ok(fragment.id())
}
pub fn fragment_logs(&self) -> Result<HashMap<FragmentId, FragmentLog>, WalletBackendError> {
self.node_client.fragment_logs().map_err(Into::into)
}
pub fn account_state(&self, account_id: AccountId) -> Result<AccountState, WalletBackendError> {
self.node_client
.account_state(account_id)
.map_err(Into::into)
}
pub fn proposals(&self) -> Result<Vec<Proposal>, WalletBackendError> {
Ok(self
.vit_client
.proposals()?
.iter()
.cloned()
.map(Into::into)
.collect())
}
pub fn block0(&self) -> Result<Vec<u8>, WalletBackendError> {
Ok(self.proxy_client.block0().map(Into::into)?)
}
pub fn vote_plan_statuses(&self) -> Result<Vec<VotePlanStatus>, WalletBackendError> {
self.node_client.vote_plan_statuses().map_err(Into::into)
}
pub fn disable_logs(&mut self) {
self.node_client.disable_logs();
self.vit_client.disable_logs();
self.proxy_client.disable_debug();
}
pub fn enable_logs(&mut self) {
self.node_client.enable_logs();
self.vit_client.enable_logs();
self.proxy_client.enable_debug();
}
pub fn are_fragments_in_blockchain(
&self,
fragment_ids: Vec<FragmentId>,
) -> Result<bool, WalletBackendError> {
Ok(fragment_ids.iter().all(|x| {
let hash = jormungandr_lib::crypto::hash::Hash::from_str(&x.to_string()).unwrap();
self.explorer_client.transaction(hash).is_ok()
}))
}
pub fn vote_statuses(
&self,
_identifier: AccountIdentifier,
) -> Result<Vec<SimpleVoteStatus>, WalletBackendError> {
/*
let vote_plan_statuses = self.vote_plan_statuses().unwrap();
let proposals = self.proposals().unwrap();
let mut active_votes = Vec::new();
for vote_plan_status in vote_plan_statuses {
for proposal in vote_plan_status.proposals {
for (account, payload) in proposal.votes.iter() {
if *account == identifier {
let vit_proposal = proposals
.iter()
.find(|x| {
x.chain_proposal_id_as_str()
== proposal.proposal_id.clone().to_string()
})
.unwrap();
active_votes.push(SimpleVoteStatus {
chain_proposal_id: vit_proposal.chain_proposal_id_as_str(),
proposal_title: vit_proposal.proposal_title.clone(),
choice: vit_proposal.get_option_text(payload.choice().unwrap().clone()),
});
}
}
}
}
Ok(active_votes)
*/
unimplemented!()
}
pub fn settings(&self) -> Result<Settings, WalletBackendError> {
let block0 = self.block0()?;
let mut block0_bytes = ReadBuf::from(&block0);
let block0 = Block::read(&mut block0_bytes).map_err(WalletBackendError::Block0ReadError)?;
Ok(Settings::new(&block0)
.map_err(|e| WalletBackendError::SettingsReadError(Box::new(e)))?)
}
pub fn account_exists(&self, id: AccountId) -> Result<bool, WalletBackendError> {
self.node_client.account_exists(id).map_err(Into::into)
}
}
#[derive(Debug, Error)]
pub enum WalletBackendError {
#[error("vit station error")]
VitStationConnectionError(#[from] VitRestError),
#[error("node rest error")]
NodeConnectionError(#[from] NodeRestError),
#[error("node rest error")]
ProxyConnectionError(#[from] ProxyClientError),
#[error("io error")]
IOError(#[from] std::io::Error),
#[error("block0 retrieve error")]
Block0ReadError(#[from] chain_core::mempack::ReadError),
#[error("block0 retrieve error")]
SettingsReadError(#[from] Box<chain_impl_mockchain::ledger::Error>),
}
mod rest;
pub use rest::{RestError, WalletNodeRestClient};
#![allow(dead_code)]
use chain_crypto::{bech32::Bech32, Ed25519, PublicKey};
use chain_impl_mockchain::fragment::FragmentId;
use jormungandr_lib::interfaces::{AccountState, FragmentLog, NodeStatsDto, VotePlanStatus};
pub use jormungandr_testing_utils::testing::node::RestError;
use jormungandr_testing_utils::testing::node::{JormungandrRest, RestSettings};
use regex::Regex;
use std::collections::HashMap;
use std::str::FromStr;
use wallet::AccountId;
pub struct WalletNodeRestClient {
rest_client: JormungandrRest,
}
impl WalletNodeRestClient {
pub fn new(address: String, settings: RestSettings) -> Self {
let re = Regex::new(r"/v0/?").unwrap();
let address = re.replace_all(&address, "");
Self {
rest_client: JormungandrRest::new_with_custom_settings(address.to_string(), settings),
}
}
pub fn send_fragment(&self, body: Vec<u8>) -> Result<(), RestError> {
let result = self.rest_client.send_raw_fragment(body);
println!("Request{:?}", result);
Ok(())
}
pub fn fragment_logs(&self) -> Result<HashMap<FragmentId, FragmentLog>, RestError> {
Ok(self
.rest_client
.fragment_logs()?
.iter()
.map(|(id, entry)| {
let str = id.to_string();
(FragmentId::from_str(&str).unwrap(), entry.clone())
})
.collect())
}
pub fn disable_logs(&mut self) {
self.rest_client.disable_logger();
}
pub fn enable_logs(&mut self) {
self.rest_client.enable_logger();
}
pub fn stats(&self) -> Result<NodeStatsDto, RestError> {
self.rest_client.stats()
}
pub fn account_state(&self, account_id: AccountId) -> Result<AccountState, RestError> {
let public_key: PublicKey<Ed25519> = account_id.into();
self.rest_client
.account_state_by_pk(&public_key.to_bech32_str())
}
pub fn account_exists(&self, account_id: AccountId) -> Result<bool, RestError> {
let public_key: PublicKey<Ed25519> = account_id.into();
let response_text = self
.rest_client
.account_state_by_pk_raw(&public_key.to_bech32_str())?;
Ok(!response_text.is_empty())
}
pub fn vote_plan_statuses(&self) -> Result<Vec<VotePlanStatus>, RestError> {
self.rest_client.vote_plan_statuses()
}
}
use hyper::StatusCode;
use thiserror::Error;
pub struct ProxyClient {
address: String,
debug: bool,
}
impl ProxyClient {
pub fn new(address: String) -> Self {
Self {
address,
debug: false,
}
}
pub fn enable_debug(&mut self) {
self.debug = true;
}
pub fn disable_debug(&mut self) {
self.debug = false;
}
pub fn print_response(&self, response: &reqwest::blocking::Response) {
if self.debug {
println!("Response: {:?}", response);
}
}
pub fn print_request_path(&self, path: &str) {
if self.debug {
println!("Request: {}", path);
}
}
pub fn block0(&self) -> Result<Vec<u8>, Error> {
let response = reqwest::blocking::get(&self.path("api/v0/block0"))?;
self.print_response(&response);
Ok(response.bytes()?.to_vec())
}
fn path(&self, path: &str) -> String {
let path = format!("{}/{}", self.address, path);
self.print_request_path(&path);
path
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("could not deserialize response {text}, due to: {source}")]
CannotDeserializeResponse {
source: serde_json::Error,
text: String,
},
#[error("could not send reqeuest")]
RequestError(#[from] reqwest::Error),
#[error("server is not up")]
ServerIsNotUp,
#[error("Error code recieved: {0}")]
ErrorStatusCode(StatusCode),
}
mod client;
mod server;
pub use client::{Error as ProxyClientError, ProxyClient};
pub use server::{Error as ProxyServerError, ProxyServerStub};
use std::net::SocketAddr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("Malformed proxy address: {0}")]
MalformedProxyAddress(String),
#[error("Malformed vit address: {0}")]
MalformedVitStationAddress(String),
#[error("Malformed node rest address: {0}")]
MalformedNodeRestAddress(String),
}
pub struct ProxyServerStub {
address: String,
vit_address: String,
node_rest_address: String,
block0: Vec<u8>,
}
impl ProxyServerStub {
pub fn new(
address: String,
vit_address: String,
node_rest_address: String,
block0: Vec<u8>,
) -> Result<Self, Error> {
Ok(Self {
address,
vit_address,
node_rest_address,
block0,
})
}
pub fn block0(&self) -> Vec<u8> {
self.block0.clone()
}
pub fn address(&self) -> String {
self.address.parse().unwrap()
}
pub fn vit_address(&self) -> String {
self.vit_address.parse().unwrap()
}
pub fn node_rest_address(&self) -> String {
self.node_rest_address.parse().unwrap()
}
pub fn base_address(&self) -> SocketAddr {
self.address.parse().unwrap()
}
pub fn http_vit_address(&self) -> String {
format!("http://{}/", self.vit_address)
}
pub fn http_node_address(&self) -> String {
format!("http://{}/", self.node_rest_address)
}
}
#![allow(dead_code)]
use crate::data::{Fund, Proposal};
use hyper::StatusCode;
use reqwest::blocking::Response;
use thiserror::Error;
pub const API_TOKEN_HEADER: &str = "API-Token";
#[derive(Debug, Clone)]
pub struct RestClientLogger {
enabled: bool,
}
impl RestClientLogger {
pub fn log_request(&self, request: &str) {
if !self.is_enabled() {
return;
}
println!("Request: {:#?}", request);
}
pub fn log_response(&self, response: &reqwest::blocking::Response) {
if !self.is_enabled() {
return;
}
println!("Response: {:#?}", response);
}
pub fn log_text(&self, content: &str) {
if !self.is_enabled() {
return;
}
println!("Text: {:#?}", content);
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled
}
}
#[derive(Debug, Clone)]
pub struct VitStationRestClient {
path_builder: RestPathBuilder,
api_token: Option<String>,
logger: RestClientLogger,
}
impl VitStationRestClient {
pub fn new(address: String) -> Self {
Self {
api_token: None,
path_builder: RestPathBuilder::new(address),
logger: RestClientLogger { enabled: false },
}
}
pub fn disable_logs(&mut self) {
self.logger.set_enabled(false);
}
pub fn enable_logs(&mut self) {
self.logger.set_enabled(true);
}
pub fn health(&self) -> Result<(), RestError> {
self.get_and_verify_status_code(&self.path_builder.health())
.map(|_| ())
.map_err(|_| RestError::ServerIsNotUp)
}
pub fn health_raw(&self) -> Result<Response, RestError> {
self.get(&self.path_builder.health())
.map_err(RestError::RequestError)
}
pub fn funds(&self) -> Result<Fund, RestError> {
let content = self
.get_and_verify_status_code(&self.path_builder.funds())?
.text()?;
self.logger.log_text(&content);
serde_json::from_str(&content).map_err(|e| RestError::CannotDeserializeResponse {
source: e,
text: content.clone(),
})
}
pub fn funds_raw(&self) -> Result<Response, RestError> {
self.get(&self.path_builder.funds())
.map_err(RestError::RequestError)
}
pub fn path_builder(&self) -> &RestPathBuilder {
&self.path_builder
}
pub fn proposals(&self) -> Result<Vec<Proposal>, RestError> {
let content = self
.get_and_verify_status_code(&self.path_builder.proposals())?
.text()?;
self.logger.log_text(&content);
if content.is_empty() {
return Ok(vec![]);
}
serde_json::from_str(&content).map_err(|e| RestError::CannotDeserializeResponse {
source: e,
text: content.clone(),
})
}
pub fn proposals_raw(&self) -> Result<Response, RestError> {
self.get(&self.path_builder.proposals())
.map_err(RestError::RequestError)
}
pub fn proposal(&self, id: &str) -> Result<Proposal, RestError> {
let response = self.proposal_raw(id)?;
self.verify_status_code(&response)?;
let content = response.text()?;
self.logger.log_text(&content);
serde_json::from_str(&content).map_err(RestError::CannotDeserialize)
}
pub fn proposal_raw(&self, id: &str) -> Result<Response, RestError> {
self.get(&self.path_builder().proposal(id))
.map_err(RestError::RequestError)
}
pub fn fund(&self, id: &str) -> Result<Fund, RestError> {
let response = self.fund_raw(id)?;
self.verify_status_code(&response)?;
let content = response.text()?;
self.logger.log_text(&content);
serde_json::from_str(&content).map_err(RestError::CannotDeserialize)
}
pub fn fund_raw(&self, id: &str) -> Result<Response, RestError> {
self.get(&self.path_builder().fund(id))
.map_err(RestError::RequestError)
}
pub fn genesis(&self) -> Result<Vec<u8>, RestError> {
Ok(self.genesis_raw()?.bytes()?.to_vec())
}
pub fn genesis_raw(&self) -> Result<Response, RestError> {
self.get(&self.path_builder.genesis())
.map_err(RestError::RequestError)
}
pub fn get(&self, path: &str) -> Result<reqwest::blocking::Response, reqwest::Error> {
self.logger.log_request(path);
let client = reqwest::blocking::Client::new();
let mut res = client.get(path);
if let Some(api_token) = &self.api_token {
res = res.header(API_TOKEN_HEADER, api_token.to_string());
}
let response = res.send()?;
self.logger.log_response(&response);
Ok(response)
}
fn get_and_verify_status_code(
&self,
path: &str,
) -> Result<reqwest::blocking::Response, RestError> {
let response = self.get(path)?;
self.verify_status_code(&response)?;
Ok(response)
}
fn verify_status_code(&self, response: &Response) -> Result<(), RestError> {
if !response.status().is_success() {
return Err(RestError::ErrorStatusCode(response.status()));
}
Ok(())
}
pub fn disable_log(&mut self) {
self.logger.set_enabled(false);
}
pub fn set_api_token(&mut self, token: String) {
self.api_token = Some(token);
}
pub fn post(&self, path: &str, data: String) -> Result<serde_json::Value, RestError> {
let client = reqwest::blocking::Client::new();
let mut res = client.post(path).body(String::into_bytes(data));
if let Some(api_token) = &self.api_token {
res = res.header(API_TOKEN_HEADER, api_token.to_string());
}
let response = res.send()?;
use iapyx::cli::args::interactive::IapyxInteractiveCommandExec;
use iapyx::cli::args::interactive::{UserInteractionContoller, WalletState};
use jortestkit::console::UserInteraction;
pub fn main() {
let user_interaction = UserInteraction::new(
"iapyx".to_string(),
"wallet interactive console".to_string(),
"type command:".to_string(),
"exit".to_string(),
">".to_string(),
vec![
"You can control each aspect of wallet:".to_string(),
"- recover from mnemonic,".to_string(),
"- vote,".to_string(),
"- retrieve blockchain data,".to_string(),
"- generate new wallet".to_string(),
],
);
user_interaction
.interact(&mut IapyxInteractiveCommandExec {
controller: UserInteractionContoller {
state: WalletState::New,
controller: None,
backend_address: "127.0.0.1:8000".to_string(),
settings: Default::default(),
},
})
.unwrap();
}
use iapyx::cli::args::load::IapyxLoadCommand;
use structopt::StructOpt;
pub fn main() {
IapyxLoadCommand::from_args().exec().unwrap();
}
use iapyx::cli::args::proxy::IapyxProxyCommand;
use structopt::StructOpt;
use warp::Filter;
use warp_reverse_proxy::reverse_proxy_filter;
#[tokio::main]
async fn main() {
let server_stub = IapyxProxyCommand::from_args().build().unwrap();
let root = warp::path!("api" / "v0" / ..);
let proposals = warp::path!("proposals").and(reverse_proxy_filter(
"".to_string(),
server_stub.http_vit_address(),
));
let fund = warp::path!("fund" / ..).and(reverse_proxy_filter(
"".to_string(),
server_stub.http_vit_address(),
));
let account = warp::path!("account" / ..).and(reverse_proxy_filter(
"".to_string(),
server_stub.http_node_address(),
));
let fragment = warp::path!("fragments" / ..).and(reverse_proxy_filter(
"".to_string(),
server_stub.http_node_address(),
));
let message = warp::path!("message" / ..).and(reverse_proxy_filter(
"".to_string(),
server_stub.http_node_address(),
));
let settings = warp::path!("settings" / ..).and(reverse_proxy_filter(
"".to_string(),
server_stub.http_node_address(),
));
let explorer = warp::path!("explorer" / "graphql").and(reverse_proxy_filter(
"".to_string(),
server_stub.http_node_address(),
));
let block0_content = server_stub.block0();
let block0 = warp::path!("block0").map(move || Ok(block0_content.clone()));
let app = root.and(
proposals
.or(fund)
.or(account)
.or(fragment)
.or(message)
.or(settings)
.or(explorer)
.or(block0),
);
warp::serve(app).run(server_stub.base_address()).await;
}
use super::WalletState;
use crate::cli::args::interactive::UserInteractionContoller;
use crate::Controller;
use bip39::Type;
use chain_addr::{AddressReadable, Discrimination};
use chain_impl_mockchain::fragment::FragmentId;
use jormungandr_testing_utils::testing::node::RestSettings;
use structopt::{clap::AppSettings, StructOpt};
use thiserror::Error;
use wallet_core::Choice;
#[derive(StructOpt, Debug)]
#[structopt(setting = AppSettings::NoBinaryName)]
pub enum IapyxCommand {
/// recover wallet funds from mnemonic
Recover(Recover),
/// generate new wallet
Generate(Generate),
/// recover wallet funds from mnemonic
Connect(Connect),
/// Prints nodes related data, like stats,fragments etc.
RetrieveFunds,
/// Spawn leader or passive node (also legacy)
Convert(Convert),
/// confirms transaction
ConfirmTx,
/// Exit interactive mode
Value,
/// Prints wallets, nodes which can be used. Draw topology
Status,
/// Prints wallets, nodes which can be used. Draw topology
Refresh,
/// get Address
Address(Address),
/// send fragments
// Vote(Vote),
/// send fragments
Logs,
Exit,
Proposals,
Vote(Vote),
Votes,
IsConverted,
PendingTransactions,
}
impl IapyxCommand {
pub fn exec(&self, model: &mut UserInteractionContoller) -> Result<(), IapyxCommandError> {
match self {
IapyxCommand::PendingTransactions => {
if let Some(controller) = model.controller.as_mut() {
let fragment_ids = controller
.pending_transactions()
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>();
println!("===================");
for (id, fragment_ids) in fragment_ids.iter().enumerate() {
println!("{}. {}", (id + 1), fragment_ids);
}
println!("===================");
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::IsConverted => {
if let Some(controller) = model.controller.as_mut() {
println!("Is Converted: {}", controller.is_converted()?);
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::Votes => {
if let Some(controller) = model.controller.as_mut() {
println!("===================");
for (id, vote) in controller.active_votes()?.iter().enumerate() {
println!("{}. {}", (id + 1), vote);
}
println!("===================");
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::Proposals => {
if let Some(controller) = model.controller.as_mut() {
println!("===================");
for (id, proposal) in controller.get_proposals()?.iter().enumerate() {
println!(
"{}. #{} [{}] {}",
(id + 1),
proposal.chain_proposal_id_as_str(),
proposal.proposal_title,
proposal.proposal_summary
);
println!("{:#?}", proposal.chain_vote_options.0);
}
println!("===================");
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::Vote(vote) => vote.exec(model),
IapyxCommand::ConfirmTx => {
if let Some(controller) = model.controller.as_mut() {
controller.confirm_all_transactions();
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::Recover(recover) => recover.exec(model),
IapyxCommand::Exit => Ok(()),
IapyxCommand::Generate(generate) => generate.exec(model),
IapyxCommand::Connect(connect) => connect.exec(model),
IapyxCommand::RetrieveFunds => {
if let Some(controller) = model.controller.as_mut() {
controller.retrieve_funds()?;
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::Convert(convert) => convert.exec(model),
IapyxCommand::Value => {
if let Some(controller) = model.controller.as_mut() {
println!("Total Value: {}", controller.total_value());
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::Status => {
if let Some(controller) = model.controller.as_ref() {
let account_state = controller.get_account_state()?;
println!("-------------------------");
println!("- Delegation: {:?}", account_state.delegation());
println!("- Value: {}", account_state.value());
println!("- Spending counter: {}", account_state.counter());
println!("- Rewards: {:?}", account_state.last_rewards());
println!("--------------------------");
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::Refresh => {
if let Some(controller) = model.controller.as_mut() {
controller.refresh_state()?;
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
IapyxCommand::Address(address) => address.exec(model),
IapyxCommand::Logs => {
if let Some(controller) = model.controller.as_mut() {
println!("{:#?}", controller.fragment_logs());
return Ok(());
}
Err(IapyxCommandError::GeneralError(
"wallet not recovered or generated".to_string(),
))
}
}
}
}
#[derive(StructOpt, Debug)]
pub struct Address {
/// blocks execution until fragment is in block
#[structopt(short = "t", long = "testing")]
pub testing: bool,
}
impl Address {
pub fn exec(&self, model: &mut UserInteractionContoller) -> Result<(), IapyxCommandError> {
if let Some(controller) = model.controller.as_mut() {
let (prefix, discrimination) = {
if self.testing {
("ca", Discrimination::Test)
} else {
("ta", Discrimination::Production)
}
};
let address =
AddressReadable::from_address(prefix, &controller.account(discrimination));
println!("Address: {}", address.to_string());
pub mod command;
use crate::Controller;
pub use command::{IapyxCommand, IapyxCommandError};
use jormungandr_testing_utils::testing::node::RestSettings;
use jortestkit::prelude::{ConsoleWriter, InteractiveCommandError, InteractiveCommandExec};
use std::ffi::OsStr;
use structopt::StructOpt;
#[derive(Debug, Copy, Clone)]
pub enum WalletState {
New,
Recovered,
Generated,
FundsRetrieved,
}
pub struct IapyxInteractiveCommandExec {
pub controller: UserInteractionContoller,
}
impl InteractiveCommandExec for IapyxInteractiveCommandExec {
fn parse_and_exec(
&mut self,
tokens: Vec<String>,
console: ConsoleWriter,
) -> std::result::Result<(), InteractiveCommandError> {
match IapyxCommand::from_iter_safe(&mut tokens.iter().map(|x| OsStr::new(x))) {
Ok(interactive) => {
if let Err(err) = interactive.exec(&mut self.controller) {
console.format_error(InteractiveCommandError::UserError(err.to_string()));
}
}
Err(err) => console.show_help(InteractiveCommandError::UserError(err.to_string())),
}
Ok(())
}
}
pub struct UserInteractionContoller {
pub state: WalletState,
pub controller: Option<Controller>,
pub backend_address: String,
pub settings: RestSettings,
}
use jortestkit::load::Configuration;
use std::path::PathBuf;
pub struct IapyxLoadConfig {
pub config: Configuration,
pub measure: bool,
pub address: String,
pub mnemonics_file: PathBuf,
}
impl IapyxLoadConfig {
pub fn new(
config: Configuration,
measure: bool,
address: String,
mnemonics_file: PathBuf,
) -> Self {
Self {
config,
measure,
address,
mnemonics_file,
}
}
}
mod config;
pub use config::IapyxLoadConfig;
pub use jortestkit::console::progress_bar::{parse_progress_bar_mode_from_str, ProgressBarMode};
use jortestkit::load::{self, Configuration, Monitor};
use crate::{MultiController, VoteStatusProvider, WalletRequestGen};
use jormungandr_testing_utils::testing::node::RestSettings;
use std::path::PathBuf;
use structopt::StructOpt;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum IapyxLoadCommandError {
#[error("duration or requests per thread stategy has to be defined")]
NoStrategyDefined,
#[error("cannot read mnemonics file")]
CannotReadMnemonicsFile,
}
#[derive(StructOpt, Debug)]
pub struct IapyxLoadCommand {
/// Prints nodes related data, like stats,fragments etc.
#[structopt(short = "t", long = "threads", default_value = "3")]
pub threads: usize,
/// address in format:
/// 127.0.0.1:8000
#[structopt(short = "a", long = "address", default_value = "127.0.0.1:8000")]
pub address: String,
/// amount of delay [miliseconds] between requests
#[structopt(short = "p", long = "pace", default_value = "10")]
pub pace: u64,
// duration of scenario
#[structopt(short = "r", long = "duration")]
pub duration: Option<u64>,
/// how many requests per thread should be sent
#[structopt(short = "n", long = "requests-per-thread")]
pub count: Option<u32>,
/// wallet mnemonics file
#[structopt(short = "s", long = "mnemonics")]
pub wallet_mnemonics_file: PathBuf,
/// use https for sending fragments
#[structopt(short = "h", long = "https")]
pub use_https_for_post: bool,
/// use https for sending fragments
#[structopt(short = "d", long = "debug")]
pub debug: bool,
// measure
#[structopt(short = "m", long = "measure")]
pub measure: bool,
// show progress
#[structopt(
long = "progress-bar-mode",
short = "b",
default_value = "Monitor",
parse(from_str = parse_progress_bar_mode_from_str)
)]
progress_bar_mode: ProgressBarMode,
}
impl IapyxLoadCommand {
pub fn exec(&self) -> Result<(), IapyxLoadCommandError> {
let config = self.build_config()?;
let mnemonics = jortestkit::file::read_file_as_vector(&config.mnemonics_file)
.map_err(|_e| IapyxLoadCommandError::CannotReadMnemonicsFile)?;
let backend = config.address;
let settings = RestSettings {
enable_debug: self.debug,
use_https_for_post: self.use_https_for_post,
..Default::default()
};
println!("{:?}", settings);
let multicontroller = MultiController::recover(&backend, mnemonics, &[], settings).unwrap();
let mut request_generator = WalletRequestGen::new(multicontroller);
request_generator.fill_generator().unwrap();
load::start_async(
request_generator,
VoteStatusProvider::new(backend),
config.config,
"Wallet backend load test",
);
Ok(())
}
fn build_monitor(&self) -> Monitor {
match self.progress_bar_mode {
ProgressBarMode::Monitor => Monitor::Progress(100),
ProgressBarMode::Standard => Monitor::Standard(100),
ProgressBarMode::None => Monitor::Disabled(10),
}
}
fn build_config(&self) -> Result<IapyxLoadConfig, IapyxLoadCommandError> {
let config = if let Some(duration) = self.duration {
Configuration::duration(
self.threads,
std::time::Duration::from_secs(duration),
self.pace,
self.build_monitor(),
0,
)
} else if let Some(count) = self.count {
Configuration::requests_per_thread(
self.threads,
count,
self.pace,
self.build_monitor(),
0,
)
} else {
return Err(IapyxLoadCommandError::NoStrategyDefined);
};
Ok(IapyxLoadConfig::new(
config,
self.measure,
self.address.clone(),
self.wallet_mnemonics_file.clone(),
))
}
}
removed Shelley references
responding to feedback
I think this is more correct: other package that depend on the system odbc package may not need freetds, the problem is that the Haskell odbc package doesn't declare its dependency on freetds.
Completed Figures 1, 2, 3, 4, 5, 6, 7, 8 and 12.
* Minimal support for hw wallets (witness pool transactions) * Supports witnessing with stake or payment keys
* supports hardware wallets and mnemonics * supports importing account keys * supports signing transaction with payment or payment and stake key