Skip to content
This repository has been archived by the owner on Nov 28, 2024. It is now read-only.

Commit

Permalink
feat!: reimplement multisignature transactions (hyperledger-iroha#4788)
Browse files Browse the repository at this point in the history
Signed-off-by: Shanin Roman <[email protected]>
  • Loading branch information
Erigara authored Jul 5, 2024
1 parent 32cd7c7 commit ebe738c
Show file tree
Hide file tree
Showing 21 changed files with 633 additions and 18 deletions.
1 change: 1 addition & 0 deletions client/tests/integration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod asset_propagation;
mod domain_owner_permissions;
mod events;
mod extra_functional;
mod multisig;
mod non_mintable;
mod pagination;
mod permissions;
Expand Down
150 changes: 150 additions & 0 deletions client/tests/integration/multisig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use std::{collections::BTreeMap, str::FromStr};

use executor_custom_data_model::multisig::{MultisigArgs, MultisigRegisterArgs};
use eyre::Result;
use iroha::{
client::{self, ClientQueryError},
crypto::KeyPair,
data_model::{
prelude::*,
transaction::{TransactionBuilder, WasmSmartContract},
},
};
use iroha_data_model::parameter::SmartContractParameter;
use nonzero_ext::nonzero;
use test_network::*;
use test_samples::{gen_account_in, ALICE_ID};

#[test]
fn mutlisig() -> Result<()> {
let (_rt, _peer, test_client) = <PeerBuilder>::new().with_port(11_400).start_with_runtime();
wait_for_genesis_committed(&vec![test_client.clone()], 0);

test_client.submit_all_blocking([
SetParameter::new(Parameter::SmartContract(SmartContractParameter::Fuel(
nonzero!(100_000_000_u64),
))),
SetParameter::new(Parameter::Executor(SmartContractParameter::Fuel(nonzero!(
100_000_000_u64
)))),
])?;

let account_id = ALICE_ID.clone();
let multisig_register_trigger_id = TriggerId::from_str("multisig_register")?;

let wasm =
iroha_wasm_builder::Builder::new("tests/integration/smartcontracts/multisig_register")
.show_output()
.build()?
.optimize()?
.into_bytes()?;
let wasm = WasmSmartContract::from_compiled(wasm);

let trigger = Trigger::new(
multisig_register_trigger_id.clone(),
Action::new(
wasm,
Repeats::Indefinitely,
account_id.clone(),
ExecuteTriggerEventFilter::new().for_trigger(multisig_register_trigger_id.clone()),
),
);

// Register trigger which would allow multisig account creation in wonderland domain
// Access to call this trigger shouldn't be restricted
test_client.submit_blocking(Register::trigger(trigger))?;

// Create multisig account id and destroy it's private key
let multisig_account_id = gen_account_in("wonderland").0;

let multisig_trigger_id: TriggerId = format!(
"{}_{}_multisig_trigger",
multisig_account_id.signatory(),
multisig_account_id.domain()
)
.parse()?;

let signatories = core::iter::repeat_with(|| gen_account_in("wonderland"))
.take(5)
.collect::<BTreeMap<AccountId, KeyPair>>();

let args = MultisigRegisterArgs {
account: Account::new(multisig_account_id.clone()),
signatories: signatories.keys().cloned().collect(),
};

test_client.submit_all_blocking(
signatories
.keys()
.cloned()
.map(Account::new)
.map(Register::account),
)?;

let call_trigger = ExecuteTrigger::new(multisig_register_trigger_id).with_args(&args);
test_client.submit_blocking(call_trigger)?;

// Check that multisig account exist
let account = test_client
.request(client::account::by_id(multisig_account_id.clone()))
.expect("multisig account should be created after the call to register multisig trigger");

assert_eq!(account.id(), &multisig_account_id);

// Check that multisig trigger exist
let trigger = test_client
.request(client::trigger::by_id(multisig_trigger_id.clone()))
.expect("multisig trigger should be created after the call to register multisig trigger");

assert_eq!(trigger.id(), &multisig_trigger_id);

let domain_id: DomainId = "domain_controlled_by_multisig".parse().unwrap();
let isi = vec![InstructionBox::from(Register::domain(Domain::new(
domain_id.clone(),
)))];
let isi_hash = HashOf::new(&isi);

let mut signatories_iter = signatories.into_iter();

if let Some((signatory, key_pair)) = signatories_iter.next() {
let args = MultisigArgs::Instructions(isi);
let call_trigger = ExecuteTrigger::new(multisig_trigger_id.clone()).with_args(&args);
test_client.submit_transaction_blocking(
&TransactionBuilder::new(test_client.chain.clone(), signatory)
.with_instructions([call_trigger])
.sign(key_pair.private_key()),
)?;
}

// Check that domain isn't created yet
let err = test_client
.request(client::domain::by_id(domain_id.clone()))
.expect_err("domain shouldn't be created before enough votes are collected");
assert!(matches!(
err,
ClientQueryError::Validation(iroha_data_model::ValidationFail::QueryFailed(
iroha_data_model::query::error::QueryExecutionFail::Find(
iroha_data_model::query::error::FindError::Domain(err_domain_id)
)
)) if domain_id == err_domain_id
));

for (signatory, key_pair) in signatories_iter {
let args = MultisigArgs::Vote(isi_hash);
let call_trigger = ExecuteTrigger::new(multisig_trigger_id.clone()).with_args(&args);
test_client.submit_transaction_blocking(
&TransactionBuilder::new(test_client.chain.clone(), signatory)
.with_instructions([call_trigger])
.sign(key_pair.private_key()),
)?;
}

// Check that new domain was created and multisig account is owner
let domain = test_client
.request(client::domain::by_id(domain_id.clone()))
.expect("domain should be created after enough votes are collected");

assert_eq!(domain.owned_by(), &multisig_account_id);

Ok(())
}
3 changes: 3 additions & 0 deletions client/tests/integration/smartcontracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ resolver = "2"
members = [
"create_nft_for_every_user_trigger",
"mint_rose_trigger",
"mint_rose_trigger_args",
"executor_with_admin",
"executor_with_custom_permission",
"executor_with_custom_parameter",
Expand All @@ -21,6 +22,8 @@ members = [
"executor_custom_data_model",
"query_assets_and_save_cursor",
"smart_contract_can_filter_queries",
"multisig_register",
"multisig",
]

[profile.dev]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
extern crate alloc;

pub mod complex_isi;
pub mod mint_rose_args;
pub mod multisig;
pub mod parameters;
pub mod permissions;
pub mod simple_isi;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Arguments to mint rose with args trigger
use serde::{Deserialize, Serialize};

/// Arguments to mint rose with args trigger
#[derive(Serialize, Deserialize)]
pub struct MintRoseArgs {
// Amount to mint
pub val: u32,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Arguments to register and manage multisig account
use alloc::{collections::btree_set::BTreeSet, vec::Vec};

use iroha_data_model::{account::NewAccount, prelude::*};
use serde::{Deserialize, Serialize};

/// Arguments to multisig account register trigger
#[derive(Serialize, Deserialize)]
pub struct MultisigRegisterArgs {
// Account id of multisig account should be manually checked to not have corresponding private key (or having master key is ok)
pub account: NewAccount,
// List of accounts responsible for handling multisig account
pub signatories: BTreeSet<AccountId>,
}

/// Arguments to multisig account manager trigger
#[derive(Serialize, Deserialize)]
pub enum MultisigArgs {
/// Accept instructions proposal and initialize votes with the proposer's one
Instructions(Vec<InstructionBox>),
/// Accept vote for certain instructions
Vote(HashOf<Vec<InstructionBox>>),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "mint_rose_args"

edition.workspace = true
version.workspace = true
authors.workspace = true

license.workspace = true

[lib]
crate-type = ['cdylib']

[dependencies]
iroha_trigger.workspace = true
executor_custom_data_model.workspace = true

panic-halt.workspace = true
lol_alloc.workspace = true
getrandom.workspace = true

serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, default-features = false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! trigger which mints rose for its owner based on input args.
#![no_std]

#[cfg(not(test))]
extern crate panic_halt;

use core::str::FromStr as _;

use executor_custom_data_model::mint_rose_args::MintRoseArgs;
use iroha_trigger::{debug::dbg_panic, prelude::*};
use lol_alloc::{FreeListAllocator, LockedAllocator};
use serde::{Deserialize, Serialize};

#[global_allocator]
static ALLOC: LockedAllocator<FreeListAllocator> = LockedAllocator::new(FreeListAllocator::new());

getrandom::register_custom_getrandom!(iroha_trigger::stub_getrandom);

/// Mint 1 rose for owner
#[iroha_trigger::main]
fn main(_id: TriggerId, owner: AccountId, event: EventBox) {
let rose_definition_id = AssetDefinitionId::from_str("rose#wonderland")
.dbg_expect("Failed to parse `rose#wonderland` asset definition id");
let rose_id = AssetId::new(rose_definition_id, owner);

let args: MintRoseArgs = match event {
EventBox::ExecuteTrigger(event) => event
.args()
.dbg_expect("Trigger expect parameters")
.try_into_any()
.dbg_expect("Failed to parse args"),
_ => dbg_panic("Only work as by call trigger"),
};

let val = args.val;

Mint::asset_numeric(val, rose_id)
.execute()
.dbg_expect("Failed to mint rose");
}
25 changes: 25 additions & 0 deletions client/tests/integration/smartcontracts/multisig/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "multisig"

edition.workspace = true
version.workspace = true
authors.workspace = true

license.workspace = true

[lib]
crate-type = ['cdylib']

[dependencies]
iroha_trigger.workspace = true
executor_custom_data_model.workspace = true

panic-halt.workspace = true
lol_alloc.workspace = true
getrandom.workspace = true

serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, default-features = false }

[build-dependencies]
iroha_wasm_builder = { version = "=2.0.0-pre-rc.21", path = "../../../../../wasm_builder" }
Loading

0 comments on commit ebe738c

Please sign in to comment.