Skip to content

Commit

Permalink
[feature] #3168: Provide genesis validator in separate file
Browse files Browse the repository at this point in the history
Signed-off-by: Shanin Roman <shanin1000@yandex.ru>
  • Loading branch information
Erigara committed May 2, 2023
1 parent fecea84 commit 6daae99
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 73 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/src/samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,5 @@ where
.optimize()?
.into_bytes();

Ok(Validator::new(WasmSmartContract::new(wasm_blob)))
Ok(Validator::new(WasmSmartContract::from_compiled(wasm_blob)))
}
5 changes: 4 additions & 1 deletion client/tests/integration/events/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ fn wasm_execution_should_produce_events() -> Result<()> {
isi_calls = isi_calls
);

transaction_execution_should_produce_events(WasmSmartContract::new(wat.into_bytes()), 10_615)
transaction_execution_should_produce_events(
WasmSmartContract::from_compiled(wat.into_bytes()),
10_615,
)
}

fn transaction_execution_should_produce_events(
Expand Down
2 changes: 1 addition & 1 deletion client/tests/integration/triggers/time_trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ fn mint_nft_for_every_user_every_1_sec() -> Result<()> {
let register_trigger = RegisterBox::new(Trigger::new(
"mint_nft_for_all".parse()?,
Action::new(
WasmSmartContract::new(wasm),
WasmSmartContract::from_compiled(wasm),
Repeats::Indefinitely,
alice_id.clone(),
FilterBox::Time(TimeEventFilter::new(ExecutionTime::Schedule(schedule))),
Expand Down
2 changes: 1 addition & 1 deletion client/tests/integration/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn validator_upgrade_should_work() -> Result<()> {

info!("WASM size is {} bytes", wasm.len());

let upgrade_validator = UpgradeBox::new(Validator::new(WasmSmartContract::new(wasm)));
let upgrade_validator = UpgradeBox::new(Validator::new(WasmSmartContract::from_compiled(wasm)));
client.submit_blocking(upgrade_validator)?;

// Check that admin can transfer alice's rose now
Expand Down
2 changes: 1 addition & 1 deletion client_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@ mod wasm {
};

submit(
WasmSmartContract::new(raw_data),
WasmSmartContract::from_compiled(raw_data),
cfg,
UnlimitedMetadata::new(),
)
Expand Down
4 changes: 3 additions & 1 deletion configs/peer/genesis.json

Large diffs are not rendered by default.

Binary file added configs/peer/validator.wasm
Binary file not shown.
1 change: 0 additions & 1 deletion core/test_network/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,3 @@ rand = "0.8.5"
tempfile = "3.3.0"
tokio = { version = "1.23.0", features = ["rt", "rt-multi-thread", "macros"] }
unique_port = "0.2.1"
json5 = "0.4.1"
7 changes: 3 additions & 4 deletions core/test_network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,9 @@ impl TestGenesis for GenesisNetwork {

// TODO: Fix this somehow. Probably we need to make `kagami` a library (#3253).
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let content = std::fs::read_to_string(manifest_dir.join("../../configs/peer/genesis.json"))
.expect("Failed to read data from configs/peer/genesis.json");
let mut genesis: RawGenesisBlock =
json5::from_str(&content).expect("Failed to deserialize genesis block from config");
let mut genesis =
RawGenesisBlock::from_path(manifest_dir.join("../../configs/peer/genesis.json"))
.expect("Failed to deserialize genesis block from file");

let rose_definition_id = <AssetDefinition as Identifiable>::Id::from_str("rose#wonderland")
.expect("valid names");
Expand Down
11 changes: 9 additions & 2 deletions data_model/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ pub mod model {
PartialOrd,
Ord,
Hash,
Constructor,
Decode,
Encode,
Deserialize,
Expand Down Expand Up @@ -395,6 +394,14 @@ impl AsRef<[u8]> for WasmSmartContract {
}
}

impl WasmSmartContract {
/// Create [`Self`] from raw wasm bytes
#[inline]
pub const fn from_compiled(blob: Vec<u8>) -> Self {
Self(blob)
}
}

impl TransactionBuilder {
/// Construct [`Self`].
#[inline]
Expand Down Expand Up @@ -1289,7 +1296,7 @@ mod tests {

#[test]
fn wasm_smart_contract_debug_repr_should_contain_just_len() {
let contract = WasmSmartContract::new(vec![0, 1, 2, 3, 4]);
let contract = WasmSmartContract::from_compiled(vec![0, 1, 2, 3, 4]);
assert_eq!(format!("{contract:?}"), "WASM binary(len = 5)");
}
}
22 changes: 21 additions & 1 deletion docs/source/references/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2979,7 +2979,7 @@
},
{
"name": "validator",
"type": "Validator"
"type": "ValidatorMode"
}
]
},
Expand Down Expand Up @@ -3672,6 +3672,26 @@
}
]
},
"ValidatorMode": {
"Enum": [
{
"name": "Path",
"type": "ValidatorPath"
},
{
"name": "Inline",
"type": "Validator"
}
]
},
"ValidatorPath": {
"Struct": [
{
"name": "validator_relative_path",
"type": "String"
}
]
},
"Value": {
"Enum": [
{
Expand Down
141 changes: 115 additions & 26 deletions genesis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@
clippy::arithmetic_side_effects
)]

use std::{fmt::Debug, fs::File, io::BufReader, ops::Deref, path::Path};
use std::{
fmt::Debug,
fs::{self, File},
io::BufReader,
ops::Deref,
path::{Path, PathBuf},
};

use derive_more::Deref;
use eyre::{bail, eyre, Result, WrapErr};
use derive_more::{Deref, From};
use eyre::{bail, eyre, ErrReport, Result, WrapErr};
use iroha_config::genesis::Configuration;
use iroha_crypto::{KeyPair, PublicKey};
#[cfg(not(test))]
use iroha_data_model::validator::Validator;
use iroha_data_model::{
asset::AssetDefinition,
prelude::{Metadata, *},
validator::Validator,
};
use iroha_primitives::small::{smallvec, SmallVec};
use iroha_schema::IntoSchema;
use serde::{Deserialize, Serialize};
#[cfg(test)]
use MockValidator as Validator;

/// Time to live for genesis transactions.
const GENESIS_TRANSACTIONS_TTL_MS: u64 = 100_000;
Expand Down Expand Up @@ -77,7 +80,10 @@ impl GenesisNetworkTrait for GenesisNetwork {
let transactions_iter = raw_block.transactions.into_iter();
#[cfg(not(test))]
let transactions_iter = transactions_iter.chain(std::iter::once(GenesisTransaction {
isi: SmallVec(smallvec![UpgradeBox::new(raw_block.validator).into()]),
isi: SmallVec(smallvec![UpgradeBox::new(Validator::try_from(
raw_block.validator
)?)
.into()]),
}));

let transactions = transactions_iter
Expand All @@ -100,21 +106,93 @@ impl GenesisNetworkTrait for GenesisNetwork {
}
}

/// Mock of [`Validator`](iroha_data_model::validator::Validator) for unit tests
///
/// Aliased to `Validator` when `cfg(test)`.
#[cfg(test)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, IntoSchema)]
#[repr(transparent)]
pub struct MockValidator;

/// [`RawGenesisBlock`] is an initial block of the network
#[derive(Debug, Clone, Deserialize, Serialize, IntoSchema)]
pub struct RawGenesisBlock {
/// Transactions
pub transactions: SmallVec<[GenesisTransaction; 2]>,
/// Runtime Validator
pub validator: Validator,
pub validator: ValidatorMode,
}

/// Ways to provide validator either directly as base64 encoded string or as path to wasm file
#[derive(Debug, Clone, Serialize, Deserialize, IntoSchema, From)]
#[serde(untagged)]
pub enum ValidatorMode {
/// Path to validator wasm file
// In the first place to initially try to parse path
Path(ValidatorPath),
/// Validator encoded as base64 string
Inline(Validator),
}

impl ValidatorMode {
fn set_genesis_path(&mut self, genesis_path: impl AsRef<Path>) {
if let Self::Path(path) = self {
path.set_genesis_path(genesis_path);
}
}
}

impl TryFrom<ValidatorMode> for Validator {
type Error = ErrReport;

fn try_from(value: ValidatorMode) -> Result<Self> {
match value {
ValidatorMode::Inline(validator) => Ok(validator),
ValidatorMode::Path(ValidatorPath {
validator_path: relative_validator_path,
}) => {
let wasm = fs::read(&relative_validator_path)
.wrap_err(format!("Failed to open {:?}", &relative_validator_path))?;
Ok(Validator::new(WasmSmartContract::from_compiled(wasm)))
}
}
}
}

/// Path to the validator relative to genesis location
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidatorPath {
/// Path to validator.
/// If path is absolute it will be used directly otherwise it will be treated as relative to genesis location.
pub validator_path: PathBuf,
}

// Manual implementation because we want `PathBuf` appear as `String` in schema
impl iroha_schema::TypeId for ValidatorPath {
fn id() -> String {
"ValidatorPath".to_string()
}
}
impl iroha_schema::IntoSchema for ValidatorPath {
fn type_name() -> String {
"ValidatorPath".to_string()
}
fn update_schema_map(map: &mut iroha_schema::MetaMap) {
if !map.contains_key::<Self>() {
map.insert::<Self>(iroha_schema::Metadata::Struct(
iroha_schema::NamedFieldsMeta {
declarations: vec![iroha_schema::Declaration {
name: String::from(stringify!(validator_relative_path)),
ty: core::any::TypeId::of::<String>(),
}],
},
));
<String as iroha_schema::IntoSchema>::update_schema_map(map);
}
}
}

impl ValidatorPath {
fn set_genesis_path(&mut self, genesis_path: impl AsRef<Path>) {
let path_to_validator = genesis_path
.as_ref()
.parent()
.expect("Genesis must be in some directory")
.join(&self.validator_path);
self.validator_path = path_to_validator;
}
}

impl RawGenesisBlock {
Expand All @@ -135,10 +213,12 @@ impl RawGenesisBlock {
iroha_logger::warn!(%size, threshold = %Self::WARN_ON_GENESIS_GTE, "Genesis is quite large, it will take some time to apply it");
}
let reader = BufReader::new(file);
serde_json::from_reader(reader).wrap_err(format!(
let mut raw_genesis_block: Self = serde_json::from_reader(reader).wrap_err(format!(
"Failed to deserialize raw genesis block from {:?}",
&path
))
))?;
raw_genesis_block.validator.set_genesis_path(path);
Ok(raw_genesis_block)
}
}

Expand Down Expand Up @@ -192,10 +272,10 @@ pub struct RawGenesisDomainBuilder<S> {
}

mod validator_state {
use super::Validator;
use super::ValidatorMode;

#[cfg_attr(test, derive(Clone, Copy))]
pub struct Set(pub Validator);
#[cfg_attr(test, derive(Clone))]
pub struct Set(pub ValidatorMode);

#[derive(Clone, Copy)]
pub struct Unset;
Expand All @@ -217,10 +297,13 @@ impl RawGenesisBlockBuilder<validator_state::Unset> {
}

/// Set the validator.
pub fn validator(self, validator: Validator) -> RawGenesisBlockBuilder<validator_state::Set> {
pub fn validator(
self,
validator: impl Into<ValidatorMode>,
) -> RawGenesisBlockBuilder<validator_state::Set> {
RawGenesisBlockBuilder {
transaction: self.transaction,
state: validator_state::Set(validator),
state: validator_state::Set(validator.into()),
}
}
}
Expand Down Expand Up @@ -323,6 +406,12 @@ mod tests {

use super::*;

fn dummy_validator() -> ValidatorMode {
ValidatorMode::Path(ValidatorPath {
validator_path: "./validator.wasm".into(),
})
}

#[test]
#[allow(clippy::expect_used)]
fn load_new_genesis_block() -> Result<()> {
Expand All @@ -338,7 +427,7 @@ mod tests {
.domain("wonderland".parse()?)
.account("alice".parse()?, alice_public_key)
.finish_domain()
.validator(MockValidator)
.validator(dummy_validator())
.build(),
Some(
&ConfigurationProxy {
Expand Down Expand Up @@ -373,7 +462,7 @@ mod tests {
.finish_domain();

// In real cases validator should be constructed from a wasm blob
let finished_genesis_block = genesis_builder.validator(MockValidator).build();
let finished_genesis_block = genesis_builder.validator(dummy_validator()).build();
{
let domain_id: DomainId = "wonderland".parse().unwrap();
assert_eq!(
Expand Down
2 changes: 1 addition & 1 deletion scripts/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ case $1 in
exit 1
};;
"genesis")
cargo run --release --bin kagami -- genesis | diff - configs/peer/genesis.json || {
cargo run --release --bin kagami -- genesis --compiled-validator-path ./validator.wasm | diff - configs/peer/genesis.json || {
echo 'Please re-generate the genesis with `cargo run --release --bin kagami -- genesis > configs/peer/genesis.json`'
exit 1
};;
Expand Down
2 changes: 1 addition & 1 deletion scripts/test_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function generate_trusted_peers {
function set_up_peers_common {
PEERS="$TEST/peers"
mkdir -p "$PEERS"
cp ./configs/peer/{config.json,genesis.json} "$PEERS"
cp ./configs/peer/{config.json,genesis.json,validator.wasm} "$PEERS"
cp ./target/debug/iroha "$PEERS" || {
# TODO this can fail for other reasons as well.
echo 'Please build the `iroha` binary, by running:'
Expand Down
Loading

0 comments on commit 6daae99

Please sign in to comment.