Skip to content

Commit

Permalink
[feature] hyperledger-iroha#2099: Add WASM integration test based on …
Browse files Browse the repository at this point in the history
…Orillion use-case (hyperledger-iroha#2122)

Signed-off-by: Daniil Polyakov <arjentix@gmail.com>
  • Loading branch information
Arjentix authored and appetrosyan committed May 12, 2022
1 parent cab9d5f commit d32c492
Show file tree
Hide file tree
Showing 19 changed files with 392 additions and 15 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/iroha2-dev-pr-static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
- name: Format check
run: cargo +nightly-2022-04-20 fmt --all -- --check
- name: Static analysis without features
run: cargo lints clippy --workspace --benches --tests --examples --quiet --no-default-features
run: cargo +nightly-2022-04-20 lints clippy --workspace --benches --tests --examples --quiet --no-default-features
if: always()
- name: Static analysis with all features enabled
run: cargo lints clippy --workspace --benches --tests --examples --quiet --all-features
run: cargo +nightly-2022-04-20 lints clippy --workspace --benches --tests --examples --quiet --all-features
if: always()
- name: Verify iroha_data_model still supports no_std
run: cargo nono check --package iroha_data_model --no-default-features
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.ci
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ RUN git clone https://github.com/rui314/mold.git; \
RUN rustup component add llvm-tools-preview clippy; \
rustup target add wasm32-unknown-unknown; \
rustup install --profile default nightly-2022-04-20; \
rustup component add rust-src --toolchain nightly-2022-04-20-x86_64-unknown-linux-gnu; \
cargo install cargo-lints cargo-nono webassembly-test-runner grcov

RUN curl -fsSL https://get.docker.com -o get-docker.sh; \
Expand Down
9 changes: 9 additions & 0 deletions cli/src/samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use iroha_core::{
genesis::config::GenesisConfiguration,
kura::config::KuraConfiguration,
queue::Configuration as QueueConfiguration,
smartcontracts::wasm::config::Configuration as WasmConfiguration,
sumeragi::config::{SumeragiConfiguration, TrustedPeers},
wsv::config::Configuration as WsvConfiguration,
};
use iroha_crypto::{KeyPair, PublicKey};
use iroha_data_model::peer::Id as PeerId;
Expand Down Expand Up @@ -101,6 +103,13 @@ pub fn get_config(trusted_peers: HashSet<PeerId>, key_pair: Option<KeyPair>) ->
account_private_key: Some(private_key),
..GenesisConfiguration::default()
},
wsv: WsvConfiguration {
wasm_runtime_config: WasmConfiguration {
fuel_limit: 10_000_000,
..WasmConfiguration::default()
},
..WsvConfiguration::default()
},
..Configuration::default()
}
}
2 changes: 1 addition & 1 deletion cli/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub trait Stream<R: DecodeVersioned>:

impl StreamMessage for warp::ws::Message {
fn binary(source: Vec<u8>) -> Self {
Self::binary(source)
warp::ws::Message::binary(source)
}

fn as_bytes(&self) -> &[u8] {
Expand Down
2 changes: 1 addition & 1 deletion client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ base64 = "0.13.0"


[dev-dependencies]
iroha_core = { version = "=2.0.0-pre-rc.3", path = "../core", features = ["dev-telemetry", "telemetry"] }
iroha_core = { version = "=2.0.0-pre-rc.3", path = "../core", features = ["dev-telemetry", "telemetry"]}
iroha_permissions_validators = { version = "=2.0.0-pre-rc.3", path = "../permissions_validators" }
iroha = { path = "../cli", features = ["dev-telemetry", "telemetry"] }

Expand Down
49 changes: 49 additions & 0 deletions client/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Build script which builds smartcontract for test
//!
//! Technically this script is used only for testing purposes, but current cargo implementation
//! doesn't allow to run build script only for tests or get info about current profile from it.
//! See [cargo issue #4001](https://github.com/rust-lang/cargo/issues/4001)

use std::{env, path::Path, process::Command};

#[allow(clippy::expect_used)]
fn main() {
let manifest_dir =
env::var("CARGO_MANIFEST_DIR").expect("Expected `CARGO_MANIFEST_DIR` environment variable");
let smartcontract_path =
Path::new(&manifest_dir).join("tests/integration/create_nft_for_every_user_smartcontract");
let out_dir = env::var_os("OUT_DIR").expect("Expected `OUT_DIR` environment variable");

println!("cargo:rerun-if-changed=..");

let fmt = Command::new("cargo")
// Removing environment variable to avoid
// `error: infinite recursion detected` when running `cargo lints`
.env_remove("RUST_RECURSION_COUNT")
.current_dir(smartcontract_path.clone())
.args(&["+nightly-2022-04-20", "fmt", "--all"])
.status()
.expect("Failed to run `cargo fmt` on smartcontract");
assert!(fmt.success(), "Can't format smartcontract");

let build = Command::new("cargo")
// Removing environment variable to avoid
// `error: infinite recursion detected` when running `cargo lints`
.env_remove("RUST_RECURSION_COUNT")
.env("CARGO_TARGET_DIR", out_dir)
.current_dir(smartcontract_path)
.args(&[
"+nightly-2022-04-20",
"build",
"--release",
"-Z",
"build-std",
"-Z",
"build-std-features=panic_immediate_abort",
"--target",
"wasm32-unknown-unknown",
])
.status()
.expect("Failed to run `cargo build` on smartcontract");
assert!(build.success(), "Can't build smartcontract")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "create_nft_for_every_user_smartcontract"
version = "2.0.0-pre-rc.3"
authors = ["Iroha 2 team <https://github.com/orgs/soramitsu/teams/iroha2>"]
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
# Smartcontract should be linked dynamically so that it may link to functions exported
# from the host environment. Also, host environment executes the smartcontract by
# calling the function which smartcontract exports(entry point of execution)
crate-type = ['cdylib']

# Empty workspace to fix "current package believes it's in a workspace when it's not"
[workspace]

[profile.release]
strip = "debuginfo" # Remove debugging info from the binary
panic = "abort" # Panics are transcribed to Traps when compiling for wasm anyways
lto = true # Link-time-optimization produces notable decrease in binary size
opt-level = "z" # Optimize for size vs speed with "s"/"z"(removes vectorization)
codegen-units = 1 # Further reduces binary size but increases compilation time

[dependencies]
iroha_wasm = { path = "../../../../wasm", features = ["debug"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! Smartcontract which creates new nft for every user
//!
//! This module isn't included in the build-tree,
//! but instead it is being built by a `client/build.rs`

#![no_std]
#![no_main]
#![allow(clippy::all)]

extern crate alloc;

use alloc::{format, string::ToString, vec::Vec};
use core::str::FromStr;

use iroha_wasm::{data_model::prelude::*, DebugUnwrapExt, Execute};

#[iroha_wasm::iroha_wasm]
fn smartcontract_entry_point(_account_id: AccountId) {
let query = QueryBox::FindAllAccounts(FindAllAccounts {});
let accounts: Vec<Account> = query.execute().try_into().dbg_unwrap();

let limits = MetadataLimits::new(256, 256);

for account in accounts {
let mut metadata = Metadata::new();
let name = format!(
"nft_for_{}_in_{}",
account.id().name,
account.id().domain_id
)
.parse()
.dbg_unwrap();
metadata
.insert_with_limits(name, true.into(), limits)
.dbg_unwrap();

let nft_id = generate_new_nft_id(account.id());
let nft_definition = AssetDefinition::store(nft_id.clone())
.mintable_once()
.with_metadata(metadata)
.build();
let account_nft_id = <Asset as Identifiable>::Id::new(nft_id, account.id().clone());

Instruction::Register(RegisterBox::new(nft_definition)).execute();
Instruction::SetKeyValue(SetKeyValueBox::new(
account_nft_id,
Name::from_str("has_this_nft").dbg_unwrap(),
Value::Bool(true),
))
.execute();
}
}

fn generate_new_nft_id(account_id: &<Account as Identifiable>::Id) -> AssetDefinitionId {
let query = QueryBox::FindAssetsByAccountId(FindAssetsByAccountId::new(account_id.clone()));
let assets: Vec<Asset> = query.execute().try_into().dbg_unwrap();

let new_number = assets
.into_iter()
.filter(|asset| asset.id().definition_id.to_string().starts_with("nft_"))
.count()
+ 1;

format!(
"nft_number_{}_for_{}#{}",
new_number, account_id.name, account_id.domain_id
)
.parse()
.dbg_unwrap()
}
101 changes: 96 additions & 5 deletions client/tests/integration/triggers/time_trigger.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#![allow(clippy::restriction)]

use std::{str::FromStr as _, time::Duration};
use std::{fs, str::FromStr as _, time::Duration};

use eyre::Result;
use eyre::{Context, Result};
use iroha_client::client::{self, Client};
use iroha_core::block::DEFAULT_CONSENSUS_ESTIMATION_MS;
use iroha_data_model::prelude::*;
use iroha_data_model::{prelude::*, transaction::WasmSmartContract};
use test_network::{Peer as TestPeer, *};

/// Macro to abort compilation, if `e` isn't `true`
Expand Down Expand Up @@ -52,7 +52,12 @@ fn time_trigger_execution_count_error_should_be_less_than_10_percent() -> Result
));
test_client.submit(register_trigger)?;

submit_sample_isi_on_every_block_commit(&mut test_client, &account_id, 3)?;
submit_sample_isi_on_every_block_commit(
&mut test_client,
&account_id,
Duration::from_secs(1),
3,
)?;
std::thread::sleep(Duration::from_millis(DEFAULT_CONSENSUS_ESTIMATION_MS));

let finish_time = current_time();
Expand Down Expand Up @@ -101,6 +106,7 @@ fn change_asset_metadata_after_1_sec() -> Result<()> {
submit_sample_isi_on_every_block_commit(
&mut test_client,
&account_id,
Duration::from_secs(1),
usize::try_from(PERIOD_MS / DEFAULT_CONSENSUS_ESTIMATION_MS + 1)?,
)?;

Expand Down Expand Up @@ -163,6 +169,90 @@ fn pre_commit_trigger_should_be_executed() -> Result<()> {
Ok(())
}

#[test]
fn mint_nft_for_every_user_every_1_sec() -> Result<()> {
const TRIGGER_PERIOD_MS: u64 = 1000;
const EXPECTED_COUNT: u64 = 4;

let (_rt, _peer, mut test_client) = <TestPeer>::start_test_with_runtime();
wait_for_genesis_committed(&vec![test_client.clone()], 0);

let alice_id = "alice@wonderland"
.parse::<<Account as Identifiable>::Id>()
.expect("Valid");

let accounts: Vec<AccountId> = vec![
alice_id.clone(),
"mad_hatter@wonderland".parse().expect("Valid"),
"cheshire_cat@wonderland".parse().expect("Valid"),
"caterpillar@wonderland".parse().expect("Valid"),
"white_rabbit@wonderland".parse().expect("Valid"),
];

// Registering accounts
let register_accounts = accounts
.iter()
.skip(1) // Alice has already been registered in genesis
.cloned()
.map(|account_id| RegisterBox::new(Account::new(account_id, [])).into())
.collect::<Vec<_>>();
test_client.submit_all_blocking(register_accounts)?;

// Reading wasm smartcontract
let wasm = fs::read(concat!(
env!("OUT_DIR"),
"/wasm32-unknown-unknown/release/create_nft_for_every_user_smartcontract.wasm"
))
.wrap_err("Can't read smartcontract")?;
println!("wasm size is {} bytes", wasm.len());

// Registering trigger
let start_time = current_time();
let schedule =
TimeSchedule::starting_at(start_time).with_period(Duration::from_millis(TRIGGER_PERIOD_MS));
let register_trigger = RegisterBox::new(Trigger::new(
"mint_nft_for_all".parse()?,
Action::new(
Executable::Wasm(WasmSmartContract { raw_data: wasm }),
Repeats::Indefinitely,
alice_id.clone(),
EventFilter::Time(TimeEventFilter(ExecutionTime::Schedule(schedule))),
),
));
test_client.submit(register_trigger)?;

// Time trigger will be executed on block commits, so we have to produce some transactions
submit_sample_isi_on_every_block_commit(
&mut test_client,
&alice_id,
Duration::from_millis(TRIGGER_PERIOD_MS),
usize::try_from(EXPECTED_COUNT)?,
)?;

// Checking results
for account_id in accounts {
let start_pattern = "nft_number_";
let end_pattern = format!("_for_{}#{}", account_id.name, account_id.domain_id);
let assets = test_client.request(client::asset::by_account_id(account_id.clone()))?;
let count: u64 = assets
.into_iter()
.filter(|asset| {
let s = asset.id().definition_id.to_string();
s.starts_with(&start_pattern) && s.ends_with(&end_pattern)
})
.count()
.try_into()
.expect("`usize` should always fit in `u64`");

assert!(
count >= EXPECTED_COUNT,
"{account_id} has {count} NFT, but at least {EXPECTED_COUNT} expected",
);
}

Ok(())
}

/// Get asset numeric value
fn get_asset_value(client: &mut Client, asset_id: AssetId) -> Result<u32> {
let asset = client.request(client::asset::by_id(asset_id))?;
Expand All @@ -173,6 +263,7 @@ fn get_asset_value(client: &mut Client, asset_id: AssetId) -> Result<u32> {
fn submit_sample_isi_on_every_block_commit(
test_client: &mut Client,
account_id: &AccountId,
timeout: Duration,
times: usize,
) -> Result<()> {
let block_filter =
Expand All @@ -189,7 +280,7 @@ fn submit_sample_isi_on_every_block_commit(
})
.take(times)
{
std::thread::sleep(Duration::from_secs(1));
std::thread::sleep(timeout);
// ISI just to create a new block
let sample_isi = SetKeyValueBox::new(
account_id.clone(),
Expand Down
Loading

0 comments on commit d32c492

Please sign in to comment.