diff --git a/Cargo.lock b/Cargo.lock index 14eb850c11..fe8ed2b388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -392,9 +392,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -725,6 +725,7 @@ dependencies = [ "tempfile", "wabt", "walrus", + "wasmprinter", "wat", ] @@ -801,7 +802,7 @@ dependencies = [ name = "casper-executor-wasm-common" version = "0.1.0" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "borsh", "casper-sdk-sys", "hex", @@ -973,7 +974,7 @@ dependencies = [ name = "casper-sdk" version = "0.1.0" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "borsh", "bytes", "casper-executor-wasm-common", @@ -1064,7 +1065,7 @@ dependencies = [ "k256", "libc", "num", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-integer", "num-rational", "num-traits", @@ -1135,9 +1136,9 @@ dependencies = [ [[package]] name = "casper-wasmi" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8357f19a7fd98073d8fe8df60f1bef1e677b7c623c1e6e2e07d2f8e59ceb87fc" +checksum = "0319f5a421cce8a5b87f441230607a53c57a065ddc45703ea1e355cd2831397e" dependencies = [ "casper-wasm", "casper-wasmi-core", @@ -1146,9 +1147,9 @@ dependencies = [ [[package]] name = "casper-wasmi-core" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60089625560924f184cf91d59b0731373d5114b81224f1201c6a39ccc1d8388c" +checksum = "077494ae45ba9d6df93da1bc3e6e686c6f4cf8ea658f2a1152980edb2c0c60bd" dependencies = [ "downcast-rs", "libm", @@ -1159,9 +1160,9 @@ dependencies = [ [[package]] name = "casper-wasmi-validation" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f669d385132ce321a57fdf453588d69c01654e75991bee3d22392a3aaaad80bb" +checksum = "218e1bb3693233149b6ac945cd26ea3e5fb5e50ac523cc87fb3ee6579a9eb789" dependencies = [ "casper-wasm", ] @@ -1832,16 +1833,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rustc_version", "subtle", "zeroize", @@ -2003,6 +2003,9 @@ name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] [[package]] name = "derivative" @@ -2254,14 +2257,15 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "serde", "sha2", + "subtle", "zeroize", ] @@ -2689,9 +2693,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filesize" @@ -2904,6 +2908,14 @@ dependencies = [ "casper-types", ] +[[package]] +name = "get-blockinfo" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "get-blocktime" version = "0.1.0" @@ -3047,6 +3059,14 @@ dependencies = [ "casper-types", ] +[[package]] +name = "gh-4898-regression" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "gimli" version = "0.26.2" @@ -3179,7 +3199,7 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b328997d74dd15dc71b2773b162cb4af9a25c424105e4876e6d0686ab41c383e" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "bstr", "gix-path", "libc", @@ -3262,7 +3282,7 @@ version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7df15afa265cc8abe92813cd354d522f1ac06b29ec6dfa163ad320575cb447" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "bstr", "gix-features", "gix-path", @@ -3295,7 +3315,7 @@ version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a9a44eb55bd84bb48f8a44980e951968ced21e171b22d115d1cdcef82a7d73f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "bstr", "filetime", "fnv", @@ -3493,7 +3513,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1547d26fa5693a7f34f05b4a3b59a90890972922172653bcb891ab3f09f436df" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "gix-path", "libc", "windows-sys 0.52.0", @@ -3526,7 +3546,7 @@ version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e499a18c511e71cf4a20413b743b9f5bcf64b3d9e81e9c3c6cd399eae55a8840" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "gix-commitgraph", "gix-date", "gix-hash", @@ -4559,6 +4579,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.3.3" @@ -4687,7 +4713,7 @@ version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "cfg-if 1.0.0", "foreign-types", "libc", @@ -4891,12 +4917,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "platforms" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" - [[package]] name = "plotters" version = "0.3.5" @@ -5013,6 +5033,12 @@ dependencies = [ "pnet_sys", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "pprof" version = "0.13.0" @@ -5784,7 +5810,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -6597,14 +6623,16 @@ dependencies = [ [[package]] name = "time" -version = "0.3.25" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "libc", + "num-conv", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -6612,16 +6640,17 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -7840,11 +7869,32 @@ version = "0.121.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "indexmap 2.2.6", "semver", ] +[[package]] +name = "wasmparser" +version = "0.219.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324b4e56d24439495b88cd81439dad5e97f3c7b1eedc3c7e10455ed1e045e9a2" +dependencies = [ + "bitflags 2.6.0", + "indexmap 2.2.6", +] + +[[package]] +name = "wasmprinter" +version = "0.219.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cdac6eee8166b6bc319166483ea8f68ffaf9d378da5fff1dd4ce0af76b597a" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.219.0", +] + [[package]] name = "wast" version = "64.0.0" diff --git a/Makefile b/Makefile index d89abfc539..61119fd49a 100644 --- a/Makefile +++ b/Makefile @@ -4,18 +4,11 @@ RUSTUP = $(or $(shell which rustup), $(HOME)/.cargo/bin/rustup) NPM = $(or $(shell which npm), /usr/bin/npm) PINNED_NIGHTLY := $(shell cat smart_contracts/rust-toolchain) -# TODO: When `PINNED_NIGHTLY` is updated to something reletively new, we can get rid -# of this specific 'NIGHTLY_FOR_DOC' variable and use `PINNED_NIGHTLY` in `make doc` instead. -# -# At the moment, we can't use STABLE for doc, due to 'doc_auto_cfg' feature being unstable. -# We also can't use the nightly version that is pinned for the contracts, because it is too old. -NIGHTLY_FOR_DOC = nightly-2024-01-01 PINNED_STABLE := $(shell sed -nr 's/channel *= *\"(.*)\"/\1/p' rust-toolchain.toml) WASM_STRIP_VERSION := $(shell wasm-strip --version) CARGO_OPTS := --locked CARGO_PINNED_NIGHTLY := $(CARGO) +$(PINNED_NIGHTLY) $(CARGO_OPTS) -CARGO_NIGHTLY_FOR_DOC := $(CARGO) +$(NIGHTLY_FOR_DOC) $(CARGO_OPTS) CARGO := $(CARGO) $(CARGO_OPTS) DISABLE_LOGGING = RUST_LOG=MatchesNothing @@ -119,7 +112,10 @@ check-std-features: cd smart_contracts/contract && $(CARGO) check --all-targets --no-default-features --features=std cd smart_contracts/contract && $(CARGO) check --all-targets --features=std -.PHONY: check-testing-features +check-std-fs-io-features: + cd types && $(CARGO) check --all-targets --features=std-fs-io + cd types && $(CARGO) check --lib --features=std-fs-io + check-testing-features: cd types && $(CARGO) check --all-targets --no-default-features --features=testing cd types && $(CARGO) check --all-targets --features=testing @@ -136,12 +132,16 @@ lint-contracts-rs: cd smart_contracts/contracts && $(CARGO) clippy $(patsubst %, -p %, $(ALL_CONTRACTS)) -- -D warnings -A renamed_and_removed_lints .PHONY: lint -lint: lint-contracts-rs lint-default-features lint-all-features lint-smart-contracts +lint: lint-contracts-rs lint-default-features lint-all-features lint-smart-contracts lint-no-default-features .PHONY: lint-default-features lint-default-features: $(CARGO) clippy --all-targets -- -D warnings +.PHONY: lint-no-default-features +lint-no-default-features: + $(CARGO) clippy --all-targets --no-default-features -- -D warnings + .PHONY: lint-all-features lint-all-features: $(CARGO) clippy --all-targets --all-features -- -D warnings @@ -164,7 +164,7 @@ audit: audit-rs .PHONY: doc doc: - RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--cfg docsrs" $(CARGO_NIGHTLY_FOR_DOC) doc --all-features $(CARGO_FLAGS) --no-deps + RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--cfg docsrs" $(CARGO_PINNED_NIGHTLY) doc --all-features $(CARGO_FLAGS) --no-deps cd smart_contracts/contract && RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--cfg docsrs" $(CARGO_PINNED_NIGHTLY) doc --all-features $(CARGO_FLAGS) --no-deps .PHONY: check-rs @@ -175,6 +175,7 @@ check-rs: \ audit \ check-no-default-features \ check-std-features \ + check-std-fs-io-features \ check-testing-features \ test-rs \ test-rs-no-default-features \ diff --git a/README.md b/README.md index 27d5b8cd03..cd3e6800c4 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ The Casper MainNet is live. - [Platform Specification](https://docs.casperlabs.io/design/) - [Highway Consensus Proofs](https://github.com/CasperLabs/highway/releases/latest) +- [Zug Consensus Whitepaper](http://arxiv.org/pdf/2205.06314) ### Get Started with Smart Contracts - [Writing Smart Contracts](https://docs.casperlabs.io/developers/) diff --git a/binary_port/Cargo.toml b/binary_port/Cargo.toml index 5cdc05882c..bc92c9bc1f 100644 --- a/binary_port/Cargo.toml +++ b/binary_port/Cargo.toml @@ -6,7 +6,7 @@ description = "Types for the casper node binary port" documentation = "https://docs.rs/casper-binary-port" readme = "README.md" homepage = "https://casperlabs.io" -repository = "https://github.com/CasperLabs/casper-node/tree/master/binary_port" +repository = "https://github.com/casper-network/casper-node/tree/master/binary_port" license = "Apache-2.0" exclude = ["proptest-regressions"] diff --git a/binary_port/README.md b/binary_port/README.md index 80099cc02b..af573314f2 100644 --- a/binary_port/README.md +++ b/binary_port/README.md @@ -5,7 +5,7 @@ [![Build Status](https://drone-auto-casper-network.casperlabs.io/api/badges/casper-network/casper-node/status.svg?branch=dev)](http://drone-auto-casper-network.casperlabs.io/casper-network/casper-node) [![Crates.io](https://img.shields.io/crates/v/casper-hashing)](https://crates.io/crates/casper-binary-port) [![Documentation](https://docs.rs/casper-hashing/badge.svg)](https://docs.rs/casper-binary-port) -[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/CasperLabs/casper-node/blob/master/LICENSE) +[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/casper-network/casper-node/blob/master/LICENSE) Types for the binary port on a casper network node. diff --git a/binary_port/src/dictionary_item_identifier.rs b/binary_port/src/dictionary_item_identifier.rs index 44277b8ddb..28e5c81150 100644 --- a/binary_port/src/dictionary_item_identifier.rs +++ b/binary_port/src/dictionary_item_identifier.rs @@ -59,7 +59,7 @@ pub enum DictionaryItemIdentifier { impl DictionaryItemIdentifier { #[cfg(test)] pub(crate) fn random(rng: &mut TestRng) -> Self { - match rng.gen_range(0..4) { + match rng.gen_range(0..5) { 0 => DictionaryItemIdentifier::AccountNamedKey { hash: rng.gen(), dictionary_name: rng.random_string(32..64), @@ -70,11 +70,16 @@ impl DictionaryItemIdentifier { dictionary_name: rng.random_string(32..64), dictionary_item_key: rng.random_string(32..64), }, - 2 => DictionaryItemIdentifier::URef { + 2 => DictionaryItemIdentifier::EntityNamedKey { + addr: rng.gen(), + dictionary_name: rng.random_string(32..64), + dictionary_item_key: rng.random_string(32..64), + }, + 3 => DictionaryItemIdentifier::URef { seed_uref: rng.gen(), dictionary_item_key: rng.random_string(32..64), }, - 3 => DictionaryItemIdentifier::DictionaryItem(rng.gen()), + 4 => DictionaryItemIdentifier::DictionaryItem(rng.gen()), _ => unreachable!(), } } diff --git a/binary_port/src/entity_qualifier.rs b/binary_port/src/entity_qualifier.rs new file mode 100644 index 0000000000..93f8bf82bb --- /dev/null +++ b/binary_port/src/entity_qualifier.rs @@ -0,0 +1,193 @@ +use super::dictionary_item_identifier::DictionaryItemIdentifier; +use crate::{KeyPrefix, PurseIdentifier}; +#[cfg(test)] +use casper_types::testing::TestRng; +use casper_types::{ + bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + Key, KeyTag, +}; +#[cfg(test)] +use rand::Rng; + +const ITEM_TAG: u8 = 0; +const ALL_ITEMS_TAG: u8 = 1; +const DICTIONARY_ITEM_TAG: u8 = 2; +const BALANCE_TAG: u8 = 3; +const ITEMS_BY_PREFIX_TAG: u8 = 4; + +/// A request to get data from the global state. +#[derive(Clone, Debug, PartialEq)] +pub enum GlobalStateEntityQualifier { + /// Gets an item from the global state. + Item { + /// Key under which data is stored. + base_key: Key, + /// Path under which the value is stored. + path: Vec, + }, + /// Get all items under the given key tag. + AllItems { + /// Key tag + key_tag: KeyTag, + }, + /// Get a dictionary item by its identifier. + DictionaryItem { + /// Dictionary item identifier. + identifier: DictionaryItemIdentifier, + }, + /// Get balance by state root and purse. + Balance { + /// Purse identifier. + purse_identifier: PurseIdentifier, + }, + ItemsByPrefix { + /// Key prefix to search for. + key_prefix: KeyPrefix, + }, +} + +impl GlobalStateEntityQualifier { + #[cfg(test)] + pub(crate) fn random(rng: &mut TestRng) -> Self { + let gen_range = TestRng::gen_range(rng, 0..5); + random_for_variant(gen_range, rng) + } +} + +#[cfg(test)] +fn random_for_variant(gen_range: u8, rng: &mut TestRng) -> GlobalStateEntityQualifier { + match gen_range { + ITEM_TAG => { + let path_count = rng.gen_range(10..20); + GlobalStateEntityQualifier::Item { + base_key: rng.gen(), + path: std::iter::repeat_with(|| rng.random_string(32..64)) + .take(path_count) + .collect(), + } + } + ALL_ITEMS_TAG => GlobalStateEntityQualifier::AllItems { + key_tag: KeyTag::random(rng), + }, + DICTIONARY_ITEM_TAG => GlobalStateEntityQualifier::DictionaryItem { + identifier: DictionaryItemIdentifier::random(rng), + }, + BALANCE_TAG => GlobalStateEntityQualifier::Balance { + purse_identifier: PurseIdentifier::random(rng), + }, + ITEMS_BY_PREFIX_TAG => GlobalStateEntityQualifier::ItemsByPrefix { + key_prefix: KeyPrefix::random(rng), + }, + _ => unreachable!(), + } +} + +impl ToBytes for GlobalStateEntityQualifier { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + self.write_bytes(&mut buffer)?; + Ok(buffer) + } + + fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { + match self { + GlobalStateEntityQualifier::Item { base_key, path } => { + ITEM_TAG.write_bytes(writer)?; + base_key.write_bytes(writer)?; + path.write_bytes(writer) + } + GlobalStateEntityQualifier::AllItems { key_tag } => { + ALL_ITEMS_TAG.write_bytes(writer)?; + key_tag.write_bytes(writer) + } + GlobalStateEntityQualifier::DictionaryItem { identifier } => { + DICTIONARY_ITEM_TAG.write_bytes(writer)?; + identifier.write_bytes(writer) + } + GlobalStateEntityQualifier::Balance { purse_identifier } => { + BALANCE_TAG.write_bytes(writer)?; + purse_identifier.write_bytes(writer) + } + GlobalStateEntityQualifier::ItemsByPrefix { key_prefix } => { + ITEMS_BY_PREFIX_TAG.write_bytes(writer)?; + key_prefix.write_bytes(writer) + } + } + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + GlobalStateEntityQualifier::Item { base_key, path } => { + base_key.serialized_length() + path.serialized_length() + } + GlobalStateEntityQualifier::AllItems { key_tag } => key_tag.serialized_length(), + GlobalStateEntityQualifier::DictionaryItem { identifier } => { + identifier.serialized_length() + } + GlobalStateEntityQualifier::Balance { purse_identifier } => { + purse_identifier.serialized_length() + } + GlobalStateEntityQualifier::ItemsByPrefix { key_prefix } => { + key_prefix.serialized_length() + } + } + } +} + +impl FromBytes for GlobalStateEntityQualifier { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder) = u8::from_bytes(bytes)?; + match tag { + ITEM_TAG => { + let (base_key, remainder) = FromBytes::from_bytes(remainder)?; + let (path, remainder) = FromBytes::from_bytes(remainder)?; + Ok(( + GlobalStateEntityQualifier::Item { base_key, path }, + remainder, + )) + } + ALL_ITEMS_TAG => { + let (key_tag, remainder) = FromBytes::from_bytes(remainder)?; + Ok((GlobalStateEntityQualifier::AllItems { key_tag }, remainder)) + } + DICTIONARY_ITEM_TAG => { + let (identifier, remainder) = FromBytes::from_bytes(remainder)?; + Ok(( + GlobalStateEntityQualifier::DictionaryItem { identifier }, + remainder, + )) + } + BALANCE_TAG => { + let (purse_identifier, remainder) = FromBytes::from_bytes(remainder)?; + Ok(( + GlobalStateEntityQualifier::Balance { purse_identifier }, + remainder, + )) + } + ITEMS_BY_PREFIX_TAG => { + let (key_prefix, remainder) = FromBytes::from_bytes(remainder)?; + Ok(( + GlobalStateEntityQualifier::ItemsByPrefix { key_prefix }, + remainder, + )) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use casper_types::testing::TestRng; + + #[test] + fn bytesrepr_roundtrip() { + let rng = &mut TestRng::new(); + for i in 0..5 { + let qualifier = random_for_variant(i, rng); + bytesrepr::test_serialization_roundtrip(&qualifier); + } + } +} diff --git a/binary_port/src/error_code.rs b/binary_port/src/error_code.rs index 6f0a5ab0e5..8f841dee55 100644 --- a/binary_port/src/error_code.rs +++ b/binary_port/src/error_code.rs @@ -280,12 +280,15 @@ pub enum ErrorCode { /// Purse was not found for given identifier. #[error("purse was not found for given identifier")] PurseNotFound = 87, + /// Too many requests per second. + #[error("request was throttled")] + RequestThrottled = 88, /// Expected named arguments. #[error("expected named arguments")] - ExpectedNamedArguments = 88, + ExpectedNamedArguments = 89, /// Invalid transaction runtime. #[error("invalid transaction runtime")] - InvalidTransactionRuntime = 89, + InvalidTransactionRuntime = 90, } impl TryFrom for ErrorCode { @@ -381,8 +384,9 @@ impl TryFrom for ErrorCode { 85 => Ok(ErrorCode::GasPriceToleranceTooLow), 86 => Ok(ErrorCode::ReceivedV1Transaction), 87 => Ok(ErrorCode::PurseNotFound), - 88 => Ok(ErrorCode::ExpectedNamedArguments), - 89 => Ok(ErrorCode::InvalidTransactionRuntime), + 88 => Ok(ErrorCode::RequestThrottled), + 89 => Ok(ErrorCode::ExpectedNamedArguments), + 90 => Ok(ErrorCode::InvalidTransactionRuntime), _ => Err(UnknownErrorCode), } } @@ -515,7 +519,7 @@ impl From for ErrorCode { InvalidTransactionV1::EntryPointCannotBeCall => { ErrorCode::InvalidTransactionEntryPointCannotBeCall } - InvalidTransactionV1::InvalidTransactionKind(_) => { + InvalidTransactionV1::InvalidTransactionLane(_) => { ErrorCode::InvalidTransactionInvalidTransactionKind } InvalidTransactionV1::GasPriceToleranceTooLow { .. } => { @@ -548,6 +552,7 @@ mod tests { "variant {} not covered by TryFrom implementation", as_int ); + assert_eq!(decoded.unwrap(), variant); } } } diff --git a/binary_port/src/get_request.rs b/binary_port/src/get_request.rs index d0cfcc811b..483a05f9ae 100644 --- a/binary_port/src/get_request.rs +++ b/binary_port/src/get_request.rs @@ -1,4 +1,7 @@ -use casper_types::bytesrepr::{self, Bytes, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}; +use casper_types::{ + bytesrepr::{self, Bytes, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + Digest, +}; use crate::state_request::GlobalStateRequest; @@ -10,6 +13,7 @@ use rand::Rng; const RECORD_TAG: u8 = 0; const INFORMATION_TAG: u8 = 1; const STATE_TAG: u8 = 2; +const TRIE_TAG: u8 = 3; /// A request to get data from the node. #[derive(Clone, Debug, PartialEq)] @@ -30,12 +34,17 @@ pub enum GetRequest { }, /// Retrieves data from the global state. State(Box), + /// Get a trie by its Digest. + Trie { + /// A trie key. + trie_key: Digest, + }, } impl GetRequest { #[cfg(test)] pub(crate) fn random(rng: &mut TestRng) -> Self { - match rng.gen_range(0..3) { + match rng.gen_range(0..4) { 0 => GetRequest::Record { record_type_tag: rng.gen(), key: rng.random_vec(16..32), @@ -45,6 +54,9 @@ impl GetRequest { key: rng.random_vec(16..32), }, 2 => GetRequest::State(Box::new(GlobalStateRequest::random(rng))), + 3 => GetRequest::Trie { + trie_key: Digest::random(rng), + }, _ => unreachable!(), } } @@ -76,6 +88,10 @@ impl ToBytes for GetRequest { STATE_TAG.write_bytes(writer)?; req.write_bytes(writer) } + GetRequest::Trie { trie_key } => { + TRIE_TAG.write_bytes(writer)?; + trie_key.write_bytes(writer) + } } } @@ -90,6 +106,7 @@ impl ToBytes for GetRequest { info_type_tag.serialized_length() + key.serialized_length() } GetRequest::State(req) => req.serialized_length(), + GetRequest::Trie { trie_key } => trie_key.serialized_length(), } } } @@ -124,6 +141,10 @@ impl FromBytes for GetRequest { let (req, remainder) = FromBytes::from_bytes(remainder)?; Ok((GetRequest::State(Box::new(req)), remainder)) } + TRIE_TAG => { + let (trie_key, remainder) = FromBytes::from_bytes(remainder)?; + Ok((GetRequest::Trie { trie_key }, remainder)) + } _ => Err(bytesrepr::Error::Formatting), } } diff --git a/binary_port/src/lib.rs b/binary_port/src/lib.rs index ec0a173ccc..292b8740e9 100644 --- a/binary_port/src/lib.rs +++ b/binary_port/src/lib.rs @@ -7,6 +7,7 @@ mod binary_response; mod binary_response_and_request; mod binary_response_header; mod dictionary_item_identifier; +mod entity_qualifier; mod era_identifier; mod error; mod error_code; @@ -31,6 +32,7 @@ pub use binary_response::BinaryResponse; pub use binary_response_and_request::BinaryResponseAndRequest; pub use binary_response_header::BinaryResponseHeader; pub use dictionary_item_identifier::DictionaryItemIdentifier; +pub use entity_qualifier::GlobalStateEntityQualifier; pub use era_identifier::EraIdentifier; pub use error::Error; pub use error_code::ErrorCode; diff --git a/binary_port/src/response_type.rs b/binary_port/src/response_type.rs index 7f58f3869a..397d29bc05 100644 --- a/binary_port/src/response_type.rs +++ b/binary_port/src/response_type.rs @@ -4,8 +4,6 @@ use core::{convert::TryFrom, fmt}; #[cfg(test)] use rand::Rng; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; #[cfg(test)] use casper_types::testing::TestRng; @@ -32,7 +30,6 @@ use crate::{ /// A type of the payload being returned in a binary response. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(u8)] -#[cfg_attr(feature = "json-schema", derive(JsonSchema))] pub enum ResponseType { /// Legacy version of the block header. BlockHeaderV1, @@ -148,7 +145,7 @@ impl ResponseType { #[cfg(test)] pub(crate) fn random(rng: &mut TestRng) -> Self { - Self::try_from(rng.gen_range(0..45)).unwrap() + Self::try_from(rng.gen_range(0..44)).unwrap() } } diff --git a/binary_port/src/state_request.rs b/binary_port/src/state_request.rs index ca00fc4ecf..267c678e62 100644 --- a/binary_port/src/state_request.rs +++ b/binary_port/src/state_request.rs @@ -1,114 +1,51 @@ +use std::fmt::{Display, Formatter, Result as DisplayResult}; + +use crate::entity_qualifier::GlobalStateEntityQualifier; #[cfg(test)] use casper_types::testing::TestRng; -#[cfg(test)] -use rand::Rng; - use casper_types::{ - bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - Digest, GlobalStateIdentifier, Key, KeyTag, + bytesrepr::{self, FromBytes, ToBytes}, + GlobalStateIdentifier, }; - -use crate::{KeyPrefix, PurseIdentifier}; - -use super::dictionary_item_identifier::DictionaryItemIdentifier; - -const ITEM_TAG: u8 = 0; -const ALL_ITEMS_TAG: u8 = 1; -const TRIE_TAG: u8 = 2; -const DICTIONARY_ITEM_TAG: u8 = 3; -const BALANCE_TAG: u8 = 4; -const ITEMS_BY_PREFIX_TAG: u8 = 5; +#[cfg(test)] +use rand::Rng; /// A request to get data from the global state. #[derive(Clone, Debug, PartialEq)] -pub enum GlobalStateRequest { - /// Gets an item from the global state. - Item { - /// Global state identifier, `None` means "latest block state". - state_identifier: Option, - /// Key under which data is stored. - base_key: Key, - /// Path under which the value is stored. - path: Vec, - }, - /// Get all items under the given key tag. - AllItems { - /// Global state identifier, `None` means "latest block state". - state_identifier: Option, - /// Key tag - key_tag: KeyTag, - }, - /// Get a trie by its Digest. - Trie { - /// A trie key. - trie_key: Digest, - }, - /// Get a dictionary item by its identifier. - DictionaryItem { - /// Global state identifier, `None` means "latest block state". - state_identifier: Option, - /// Dictionary item identifier. - identifier: DictionaryItemIdentifier, - }, - /// Get balance by state root and purse. - Balance { - /// Global state identifier, `None` means "latest block state". - state_identifier: Option, - /// Purse identifier. - purse_identifier: PurseIdentifier, - }, - ItemsByPrefix { - /// Global state identifier, `None` means "latest block state". - state_identifier: Option, - /// Key prefix to search for. - key_prefix: KeyPrefix, - }, +pub struct GlobalStateRequest { + /// Global state identifier, `None` means "latest block state". + state_identifier: Option, + /// qualifier that points to a specific item (or items) in the global state. + qualifier: GlobalStateEntityQualifier, } impl GlobalStateRequest { + pub fn new( + state_identifier: Option, + qualifier: GlobalStateEntityQualifier, + ) -> Self { + GlobalStateRequest { + state_identifier, + qualifier, + } + } + pub fn destructure(self) -> (Option, GlobalStateEntityQualifier) { + (self.state_identifier, self.qualifier) + } + + pub fn state_identifier(self) -> Option { + self.state_identifier + } + #[cfg(test)] pub(crate) fn random(rng: &mut TestRng) -> Self { - match TestRng::gen_range(rng, 0..6) { - ITEM_TAG => { - let path_count = rng.gen_range(10..20); - GlobalStateRequest::Item { - state_identifier: rng - .gen::() - .then(|| GlobalStateIdentifier::random(rng)), - base_key: rng.gen(), - path: std::iter::repeat_with(|| rng.random_string(32..64)) - .take(path_count) - .collect(), - } - } - ALL_ITEMS_TAG => GlobalStateRequest::AllItems { - state_identifier: rng - .gen::() - .then(|| GlobalStateIdentifier::random(rng)), - key_tag: KeyTag::random(rng), - }, - TRIE_TAG => GlobalStateRequest::Trie { - trie_key: Digest::random(rng), - }, - DICTIONARY_ITEM_TAG => GlobalStateRequest::DictionaryItem { - state_identifier: rng - .gen::() - .then(|| GlobalStateIdentifier::random(rng)), - identifier: DictionaryItemIdentifier::random(rng), - }, - BALANCE_TAG => GlobalStateRequest::Balance { - state_identifier: rng - .gen::() - .then(|| GlobalStateIdentifier::random(rng)), - purse_identifier: PurseIdentifier::random(rng), - }, - ITEMS_BY_PREFIX_TAG => GlobalStateRequest::ItemsByPrefix { - state_identifier: rng - .gen::() - .then(|| GlobalStateIdentifier::random(rng)), - key_prefix: KeyPrefix::random(rng), - }, - _ => unreachable!(), + let state_identifier = rng + .gen::() + .then(|| GlobalStateIdentifier::random(rng)); + let qualifier = GlobalStateEntityQualifier::random(rng); + Self { + state_identifier, + qualifier, } } } @@ -121,155 +58,48 @@ impl ToBytes for GlobalStateRequest { } fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - match self { - GlobalStateRequest::Item { - state_identifier, - base_key, - path, - } => { - ITEM_TAG.write_bytes(writer)?; - state_identifier.write_bytes(writer)?; - base_key.write_bytes(writer)?; - path.write_bytes(writer) - } - GlobalStateRequest::AllItems { - state_identifier, - key_tag, - } => { - ALL_ITEMS_TAG.write_bytes(writer)?; - state_identifier.write_bytes(writer)?; - key_tag.write_bytes(writer) - } - GlobalStateRequest::Trie { trie_key } => { - TRIE_TAG.write_bytes(writer)?; - trie_key.write_bytes(writer) - } - GlobalStateRequest::DictionaryItem { - state_identifier, - identifier, - } => { - DICTIONARY_ITEM_TAG.write_bytes(writer)?; - state_identifier.write_bytes(writer)?; - identifier.write_bytes(writer) - } - GlobalStateRequest::Balance { - state_identifier, - purse_identifier, - } => { - BALANCE_TAG.write_bytes(writer)?; - state_identifier.write_bytes(writer)?; - purse_identifier.write_bytes(writer) - } - GlobalStateRequest::ItemsByPrefix { - state_identifier, - key_prefix, - } => { - ITEMS_BY_PREFIX_TAG.write_bytes(writer)?; - state_identifier.write_bytes(writer)?; - key_prefix.write_bytes(writer) - } - } + self.state_identifier.write_bytes(writer)?; + self.qualifier.write_bytes(writer)?; + Ok(()) } fn serialized_length(&self) -> usize { - U8_SERIALIZED_LENGTH - + match self { - GlobalStateRequest::Item { - state_identifier, - base_key, - path, - } => { - state_identifier.serialized_length() - + base_key.serialized_length() - + path.serialized_length() - } - GlobalStateRequest::AllItems { - state_identifier, - key_tag, - } => state_identifier.serialized_length() + key_tag.serialized_length(), - GlobalStateRequest::Trie { trie_key } => trie_key.serialized_length(), - GlobalStateRequest::DictionaryItem { - state_identifier, - identifier, - } => state_identifier.serialized_length() + identifier.serialized_length(), - GlobalStateRequest::Balance { - state_identifier, - purse_identifier, - } => state_identifier.serialized_length() + purse_identifier.serialized_length(), - GlobalStateRequest::ItemsByPrefix { - state_identifier, - key_prefix, - } => state_identifier.serialized_length() + key_prefix.serialized_length(), - } + self.state_identifier.serialized_length() + self.qualifier.serialized_length() } } impl FromBytes for GlobalStateRequest { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (tag, remainder) = u8::from_bytes(bytes)?; - match tag { - ITEM_TAG => { - let (state_identifier, remainder) = FromBytes::from_bytes(remainder)?; - let (base_key, remainder) = FromBytes::from_bytes(remainder)?; - let (path, remainder) = FromBytes::from_bytes(remainder)?; - Ok(( - GlobalStateRequest::Item { - state_identifier, - base_key, - path, - }, - remainder, - )) - } - ALL_ITEMS_TAG => { - let (state_identifier, remainder) = FromBytes::from_bytes(remainder)?; - let (key_tag, remainder) = FromBytes::from_bytes(remainder)?; - Ok(( - GlobalStateRequest::AllItems { - state_identifier, - key_tag, - }, - remainder, - )) + let (state_identifier, remainder) = FromBytes::from_bytes(bytes)?; + let (qualifier, remainder) = FromBytes::from_bytes(remainder)?; + Ok(( + GlobalStateRequest { + state_identifier, + qualifier, + }, + remainder, + )) + } +} + +impl Display for GlobalStateRequest { + fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { + match self.qualifier { + GlobalStateEntityQualifier::Item { base_key, .. } => { + write!(f, "get item from global state ({})", base_key) } - TRIE_TAG => { - let (trie_key, remainder) = Digest::from_bytes(remainder)?; - Ok((GlobalStateRequest::Trie { trie_key }, remainder)) + GlobalStateEntityQualifier::AllItems { key_tag, .. } => { + write!(f, "get all items ({})", key_tag) } - DICTIONARY_ITEM_TAG => { - let (state_identifier, remainder) = FromBytes::from_bytes(remainder)?; - let (identifier, remainder) = FromBytes::from_bytes(remainder)?; - Ok(( - GlobalStateRequest::DictionaryItem { - state_identifier, - identifier, - }, - remainder, - )) + GlobalStateEntityQualifier::DictionaryItem { .. } => { + write!(f, "get dictionary item") } - BALANCE_TAG => { - let (state_identifier, remainder) = FromBytes::from_bytes(remainder)?; - let (purse_identifier, remainder) = FromBytes::from_bytes(remainder)?; - Ok(( - GlobalStateRequest::Balance { - state_identifier, - purse_identifier, - }, - remainder, - )) + GlobalStateEntityQualifier::Balance { .. } => { + write!(f, "get balance by state root",) } - ITEMS_BY_PREFIX_TAG => { - let (state_identifier, remainder) = FromBytes::from_bytes(remainder)?; - let (key_prefix, remainder) = FromBytes::from_bytes(remainder)?; - Ok(( - GlobalStateRequest::ItemsByPrefix { - state_identifier, - key_prefix, - }, - remainder, - )) + GlobalStateEntityQualifier::ItemsByPrefix { .. } => { + write!(f, "get items by prefix") } - _ => Err(bytesrepr::Error::Formatting), } } } @@ -282,7 +112,6 @@ mod tests { #[test] fn bytesrepr_roundtrip() { let rng = &mut TestRng::new(); - let val = GlobalStateRequest::random(rng); bytesrepr::test_serialization_roundtrip(&val); } diff --git a/binary_port/src/type_wrappers.rs b/binary_port/src/type_wrappers.rs index 11d4e40e37..89a14cbf78 100644 --- a/binary_port/src/type_wrappers.rs +++ b/binary_port/src/type_wrappers.rs @@ -1,9 +1,6 @@ use core::{convert::TryFrom, num::TryFromIntError, time::Duration}; use std::collections::BTreeMap; -#[cfg(feature = "datasize")] -use datasize::DataSize; - use casper_types::{ bytesrepr::{self, Bytes, FromBytes, ToBytes}, contracts::ContractHash, @@ -76,7 +73,6 @@ impl TryFrom for TimeDiff { /// Type representing changes in consensus validators. #[derive(Debug, PartialEq, Eq, Serialize)] -#[cfg_attr(feature = "datasize", derive(DataSize))] pub struct ConsensusValidatorChanges(BTreeMap>); impl ConsensusValidatorChanges { diff --git a/ci/build_update_package.sh b/ci/build_update_package.sh index 314b378743..f5fd82944a 100755 --- a/ci/build_update_package.sh +++ b/ci/build_update_package.sh @@ -23,7 +23,7 @@ readme="$BIN_DIR/README.md" { echo "Build for Ubuntu 18.04." echo "" - echo "To run on other platforms, build from https://github.com/CasperLabs/casper-node" + echo "To run on other platforms, build from https://github.com/casper-network/casper-node" echo " cd node" echo " cargo build --release" echo "" diff --git a/execution_engine/Cargo.toml b/execution_engine/Cargo.toml index 343bf42e85..43d37635c9 100644 --- a/execution_engine/Cargo.toml +++ b/execution_engine/Cargo.toml @@ -7,7 +7,7 @@ description = "Casper execution engine crates." readme = "README.md" documentation = "https://docs.rs/casper-execution-engine" homepage = "https://casperlabs.io" -repository = "https://github.com/CasperLabs/casper-node/tree/master/execution_engine" +repository = "https://github.com/casper-network/casper-node/tree/master/execution_engine" license = "Apache-2.0" [dependencies] @@ -16,9 +16,9 @@ base16 = "0.2.1" bincode = "1.3.1" casper-storage = { version = "2.0.0", path = "../storage" } casper-types = { version = "5.0.0", path = "../types", default-features = false, features = ["datasize", "gens", "json-schema", "std"] } -casper-wasm = { version = "0.46.0", default-features = false } -casper-wasm-utils = "3.0.0" -casper-wasmi = "0.13.2" +casper-wasm = { version = "0.46.0", default-features = false, features = ["sign_ext"] } +casper-wasm-utils = { version = "3.0.0", default-features = false, features = ["sign_ext"] } +casper-wasmi = { version = "0.14.0", features = ["sign_ext"] } datasize = "0.2.4" either = "1.8.1" hex-buffer-serde = "0.2.1" diff --git a/execution_engine/src/engine_state/engine_config.rs b/execution_engine/src/engine_state/engine_config.rs index 59eb1293c2..98da996116 100644 --- a/execution_engine/src/engine_state/engine_config.rs +++ b/execution_engine/src/engine_state/engine_config.rs @@ -7,8 +7,9 @@ use num_rational::Ratio; use num_traits::One; use casper_types::{ - account::AccountHash, FeeHandling, ProtocolVersion, PublicKey, RefundHandling, SystemConfig, - TimeDiff, WasmConfig, DEFAULT_FEE_HANDLING, DEFAULT_REFUND_HANDLING, + account::AccountHash, FeeHandling, ProtocolVersion, PublicKey, RefundHandling, StorageCosts, + SystemConfig, TimeDiff, WasmConfig, DEFAULT_FEE_HANDLING, DEFAULT_MINIMUM_BID_AMOUNT, + DEFAULT_REFUND_HANDLING, }; /// Default value for a maximum query depth configuration option. @@ -59,6 +60,7 @@ pub struct EngineConfig { max_runtime_call_stack_height: u32, minimum_delegation_amount: u64, maximum_delegation_amount: u64, + minimum_bid_amount: u64, /// This flag indicates if arguments passed to contracts are checked against the defined types. strict_argument_checking: bool, /// Vesting schedule period in milliseconds. @@ -84,6 +86,7 @@ pub struct EngineConfig { pub(crate) fee_handling: FeeHandling, /// Compute auction rewards. pub(crate) compute_rewards: bool, + storage_costs: StorageCosts, } impl Default for EngineConfig { @@ -93,6 +96,7 @@ impl Default for EngineConfig { max_runtime_call_stack_height: DEFAULT_MAX_RUNTIME_CALL_STACK_HEIGHT, minimum_delegation_amount: DEFAULT_MINIMUM_DELEGATION_AMOUNT, maximum_delegation_amount: DEFAULT_MAXIMUM_DELEGATION_AMOUNT, + minimum_bid_amount: DEFAULT_MINIMUM_BID_AMOUNT, strict_argument_checking: DEFAULT_STRICT_ARGUMENT_CHECKING, vesting_schedule_period_millis: DEFAULT_VESTING_SCHEDULE_LENGTH_MILLIS, max_delegators_per_validator: DEFAULT_MAX_DELEGATORS_PER_VALIDATOR, @@ -105,6 +109,7 @@ impl Default for EngineConfig { fee_handling: DEFAULT_FEE_HANDLING, compute_rewards: DEFAULT_COMPUTE_REWARDS, protocol_version: DEFAULT_PROTOCOL_VERSION, + storage_costs: Default::default(), } } } @@ -145,6 +150,11 @@ impl EngineConfig { self.maximum_delegation_amount } + /// Returns the minimum delegation amount in motes. + pub fn minimum_bid_amount(&self) -> u64 { + self.minimum_bid_amount + } + /// Get the engine config's strict argument checking flag. pub fn strict_argument_checking(&self) -> bool { self.strict_argument_checking @@ -190,6 +200,11 @@ impl EngineConfig { self.fee_handling } + /// Returns the engine config's storage_costs. + pub fn storage_costs(&self) -> &StorageCosts { + &self.storage_costs + } + /// Returns the engine config's compute rewards flag. pub fn compute_rewards(&self) -> bool { self.compute_rewards @@ -207,7 +222,7 @@ impl EngineConfig { /// Sets the `wasm_config.max_memory` to `new_value`. #[cfg(feature = "test-support")] pub fn set_max_memory(&mut self, new_value: u32) { - self.wasm_config.max_memory = new_value; + *self.wasm_config.v1_mut().max_memory_mut() = new_value; } } @@ -222,6 +237,7 @@ pub struct EngineConfigBuilder { max_runtime_call_stack_height: Option, minimum_delegation_amount: Option, maximum_delegation_amount: Option, + minimum_bid_amount: Option, strict_argument_checking: Option, vesting_schedule_period_millis: Option, max_delegators_per_validator: Option, @@ -235,6 +251,7 @@ pub struct EngineConfigBuilder { fee_handling: Option, compute_rewards: Option, balance_hold_interval: Option, + storage_costs: Option, } impl EngineConfigBuilder { @@ -303,7 +320,7 @@ impl EngineConfigBuilder { /// Sets the maximum wasm stack height config option. pub fn with_wasm_max_stack_height(mut self, wasm_stack_height: u32) -> Self { let wasm_config = self.wasm_config.get_or_insert_with(WasmConfig::default); - wasm_config.max_stack_height = wasm_stack_height; + *wasm_config.v1_mut().max_stack_height_mut() = wasm_stack_height; self } @@ -319,6 +336,12 @@ impl EngineConfigBuilder { self } + /// Sets the minimum bid amount config option. + pub fn with_minimum_bid_amount(mut self, minimum_bid_amount: u64) -> Self { + self.minimum_bid_amount = Some(minimum_bid_amount); + self + } + /// Sets the administrative accounts. pub fn with_administrative_accounts( mut self, @@ -376,6 +399,12 @@ impl EngineConfigBuilder { self } + /// Sets the storage_costs config option. + pub fn with_storage_costs(mut self, storage_costs: StorageCosts) -> Self { + self.storage_costs = Some(storage_costs); + self + } + /// Builds a new [`EngineConfig`] object. pub fn build(self) -> EngineConfig { let max_associated_keys = self @@ -390,6 +419,9 @@ impl EngineConfigBuilder { let maximum_delegation_amount = self .maximum_delegation_amount .unwrap_or(DEFAULT_MAXIMUM_DELEGATION_AMOUNT); + let minimum_bid_amount = self + .minimum_bid_amount + .unwrap_or(DEFAULT_MINIMUM_BID_AMOUNT); let wasm_config = self.wasm_config.unwrap_or_default(); let system_config = self.system_config.unwrap_or_default(); let protocol_version = self.protocol_version.unwrap_or(DEFAULT_PROTOCOL_VERSION); @@ -419,12 +451,14 @@ impl EngineConfigBuilder { .max_delegators_per_validator .unwrap_or(DEFAULT_MAX_DELEGATORS_PER_VALIDATOR); let compute_rewards = self.compute_rewards.unwrap_or(DEFAULT_COMPUTE_REWARDS); + let storage_costs = self.storage_costs.unwrap_or_default(); EngineConfig { max_associated_keys, max_runtime_call_stack_height, minimum_delegation_amount, maximum_delegation_amount, + minimum_bid_amount, wasm_config, system_config, protocol_version, @@ -437,6 +471,7 @@ impl EngineConfigBuilder { vesting_schedule_period_millis, max_delegators_per_validator, compute_rewards, + storage_costs, } } } diff --git a/execution_engine/src/engine_state/mod.rs b/execution_engine/src/engine_state/mod.rs index e168775cce..296cdca657 100644 --- a/execution_engine/src/engine_state/mod.rs +++ b/execution_engine/src/engine_state/mod.rs @@ -30,7 +30,10 @@ pub use engine_config::{ }; pub use error::Error; use execution_kind::ExecutionKind; -pub use wasm_v1::{ExecutableItem, InvalidRequest, WasmV1Request, WasmV1Result}; +pub use wasm_v1::{ + BlockInfo, ExecutableItem, InvalidRequest, SessionDataDeploy, SessionDataV1, SessionInputData, + WasmV1Request, WasmV1Result, +}; /// The maximum amount of motes that payment code execution can cost. pub const MAX_PAYMENT_AMOUNT: u64 = 2_500_000_000; @@ -69,8 +72,7 @@ impl ExecutionEngineV1 { wasm_v1_request: WasmV1Request, ) -> WasmV1Result { let WasmV1Request { - state_hash, - block_time, + block_info, transaction_hash, gas_limit, initiator_addr, @@ -88,6 +90,7 @@ impl ExecutionEngineV1 { let account_hash = initiator_addr.account_hash(); let protocol_version = self.config.protocol_version(); + let state_hash = block_info.state_hash; let tc = match state_provider.tracking_copy(state_hash) { Ok(Some(tracking_copy)) => Rc::new(RefCell::new(tracking_copy)), Ok(None) => return WasmV1Result::root_not_found(gas_limit, state_hash), @@ -141,7 +144,7 @@ impl ExecutionEngineV1 { access_rights, authorization_keys, account_hash, - block_time, + block_info, transaction_hash, gas_limit, protocol_version, @@ -158,7 +161,7 @@ impl ExecutionEngineV1 { pub fn execute_with_tracking_copy( &self, tracking_copy: TrackingCopy, - block_time: BlockTime, + block_info: BlockInfo, transaction_hash: TransactionHash, gas_limit: Gas, initiator_addr: InitiatorAddr, @@ -223,7 +226,7 @@ impl ExecutionEngineV1 { access_rights, authorization_keys, account_hash, - block_time, + block_info, transaction_hash, gas_limit, protocol_version, diff --git a/execution_engine/src/engine_state/wasm_v1.rs b/execution_engine/src/engine_state/wasm_v1.rs index f0b15a6e1b..5870e9e01e 100644 --- a/execution_engine/src/engine_state/wasm_v1.rs +++ b/execution_engine/src/engine_state/wasm_v1.rs @@ -1,7 +1,4 @@ -use std::{ - collections::BTreeSet, - convert::{TryFrom, TryInto}, -}; +use std::{collections::BTreeSet, convert::TryFrom}; use serde::Serialize; use thiserror::Error; @@ -9,15 +6,194 @@ use thiserror::Error; use casper_storage::{data_access_layer::TransferResult, tracking_copy::TrackingCopyCache}; use casper_types::{ account::AccountHash, bytesrepr::Bytes, contract_messages::Messages, execution::Effects, - BlockTime, CLValue, DeployHash, Digest, ExecutableDeployItem, Gas, InitiatorAddr, Phase, - PricingMode, RuntimeArgs, Transaction, TransactionEntryPoint, TransactionHash, - TransactionInvocationTarget, TransactionLane, TransactionTarget, TransactionV1, Transfer, + BlockHash, BlockTime, CLValue, DeployHash, Digest, ExecutableDeployItem, Gas, InitiatorAddr, + Phase, PricingMode, RuntimeArgs, TransactionEntryPoint, TransactionHash, + TransactionInvocationTarget, TransactionTarget, TransactionV1Hash, Transfer, }; use crate::engine_state::{DeployItem, Error as EngineError}; const DEFAULT_ENTRY_POINT: &str = "call"; +/// Structure that needs to be filled with data so the engine can assemble wasm for deploy. +pub struct SessionDataDeploy<'a> { + deploy_hash: &'a DeployHash, + session: &'a ExecutableDeployItem, + initiator_addr: InitiatorAddr, + signers: BTreeSet, + is_standard_payment: bool, +} + +impl<'a> SessionDataDeploy<'a> { + /// Constructor + pub fn new( + deploy_hash: &'a DeployHash, + session: &'a ExecutableDeployItem, + initiator_addr: InitiatorAddr, + signers: BTreeSet, + is_standard_payment: bool, + ) -> Self { + Self { + deploy_hash, + session, + initiator_addr, + signers, + is_standard_payment, + } + } + + /// Deploy hash of the deploy + pub fn deploy_hash(&self) -> &DeployHash { + self.deploy_hash + } + + /// executable item of the deploy + pub fn session(&self) -> &ExecutableDeployItem { + self.session + } + + /// initiator address of the deploy + pub fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + /// signers of the deploy + pub fn signers(&self) -> BTreeSet { + self.signers.clone() + } +} + +/// Structure that needs to be filled with data so the engine can assemble wasm for v1. +pub struct SessionDataV1<'a> { + args: &'a RuntimeArgs, + target: &'a TransactionTarget, + entry_point: &'a TransactionEntryPoint, + is_install_upgrade: bool, + hash: &'a TransactionV1Hash, + pricing_mode: &'a PricingMode, + initiator_addr: InitiatorAddr, + signers: BTreeSet, + is_standard_payment: bool, +} + +impl<'a> SessionDataV1<'a> { + #[allow(clippy::too_many_arguments)] + /// Constructor + pub fn new( + args: &'a RuntimeArgs, + target: &'a TransactionTarget, + entry_point: &'a TransactionEntryPoint, + is_install_upgrade: bool, + hash: &'a TransactionV1Hash, + pricing_mode: &'a PricingMode, + initiator_addr: InitiatorAddr, + signers: BTreeSet, + is_standard_payment: bool, + ) -> Self { + Self { + args, + target, + entry_point, + is_install_upgrade, + hash, + pricing_mode, + initiator_addr, + signers, + is_standard_payment, + } + } + + /// Runtime args passed with the transaction. + pub fn args(&self) -> &RuntimeArgs { + self.args + } + + /// Target of the transaction. + pub fn target(&self) -> &TransactionTarget { + self.target + } + + /// Entry point of the transaction + pub fn entry_point(&self) -> &TransactionEntryPoint { + self.entry_point + } + + /// Should session be allowed to perform install/upgrade operations + pub fn is_install_upgrade(&self) -> bool { + self.is_install_upgrade + } + + /// Hash of the transaction + pub fn hash(&self) -> &TransactionV1Hash { + self.hash + } + + /// initiator address of the transaction + pub fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + /// signers of the transaction + pub fn signers(&self) -> BTreeSet { + self.signers.clone() + } + + /// Pricing mode of the transaction + pub fn pricing_mode(&self) -> &PricingMode { + self.pricing_mode + } +} + +/// Wrapper enum abstracting data for assmbling WasmV1Requests +pub enum SessionInputData<'a> { + /// Variant for sessions created from deploy transactions + DeploySessionData { + /// Deploy session data + data: SessionDataDeploy<'a>, + }, + /// Variant for sessions created from v1 transactions + SessionDataV1 { + /// v1 session data + data: SessionDataV1<'a>, + }, +} + +impl<'a> SessionInputData<'a> { + /// Transaction hash for the session + pub fn transaction_hash(&self) -> TransactionHash { + match self { + SessionInputData::DeploySessionData { data } => { + TransactionHash::Deploy(*data.deploy_hash()) + } + SessionInputData::SessionDataV1 { data } => TransactionHash::V1(*data.hash()), + } + } + + /// Initiator address for the session + pub fn initiator_addr(&self) -> &InitiatorAddr { + match self { + SessionInputData::DeploySessionData { data } => data.initiator_addr(), + SessionInputData::SessionDataV1 { data } => data.initiator_addr(), + } + } + + /// Signers for the session + pub fn signers(&self) -> BTreeSet { + match self { + SessionInputData::DeploySessionData { data } => data.signers(), + SessionInputData::SessionDataV1 { data } => data.signers(), + } + } + + /// determines if the transaction from which this session data was created is a standard payment + pub fn is_standard_payment(&self) -> bool { + match self { + SessionInputData::DeploySessionData { data } => data.is_standard_payment, + SessionInputData::SessionDataV1 { data } => data.is_standard_payment, + } + } +} + /// Error returned if constructing a new [`WasmV1Request`] fails. #[derive(Clone, Eq, PartialEq, Error, Serialize, Debug)] pub enum InvalidRequest { @@ -39,9 +215,6 @@ pub enum InvalidRequest { /// Unsupported category. #[error("invalid category for {0} attempting {1}")] InvalidCategory(TransactionHash, String), - /// Unexpected transaction args variant. - #[error("unexpected transaction args for {0} attempting {1}")] - UnexpectedTransactionArgs(TransactionHash, String), } #[derive(Debug, Clone)] @@ -68,13 +241,66 @@ pub enum ExecutableItem { Invocation(TransactionInvocationTarget), } -/// A request to execute the given Wasm on the V1 runtime. -#[derive(Debug)] -pub struct WasmV1Request { +/// Block info. +#[derive(Copy, Clone, Debug)] +pub struct BlockInfo { /// State root hash of the global state in which the transaction will be executed. pub state_hash: Digest, /// Block time represented as a unix timestamp. pub block_time: BlockTime, + /// Parent block hash + pub parent_block_hash: BlockHash, + /// Block height + pub block_height: u64, +} + +impl BlockInfo { + /// A new instance of `[BlockInfo]`. + pub fn new( + state_hash: Digest, + block_time: BlockTime, + parent_block_hash: BlockHash, + block_height: u64, + ) -> Self { + BlockInfo { + state_hash, + block_time, + parent_block_hash, + block_height, + } + } + + /// Apply different state hash. + pub fn with_state_hash(&mut self, state_hash: Digest) { + self.state_hash = state_hash; + } + + /// State hash. + pub fn state_hash(&self) -> Digest { + self.state_hash + } + + /// Block time. + pub fn block_time(&self) -> BlockTime { + self.block_time + } + + /// Parent block hash. + pub fn parent_block_hash(&self) -> BlockHash { + self.parent_block_hash + } + + /// Block height. + pub fn block_height(&self) -> u64 { + self.block_height + } +} + +/// A request to execute the given Wasm on the V1 runtime. +#[derive(Debug)] +pub struct WasmV1Request { + /// Block info. + pub block_info: BlockInfo, /// The hash identifying the transaction. pub transaction_hash: TransactionHash, /// The number of Motes per unit of Gas to be paid for execution. @@ -95,8 +321,7 @@ pub struct WasmV1Request { impl WasmV1Request { pub(crate) fn new_from_executable_info( - state_hash: Digest, - block_time: BlockTime, + block_info: BlockInfo, gas_limit: Gas, transaction_hash: TransactionHash, initiator_addr: InitiatorAddr, @@ -105,8 +330,7 @@ impl WasmV1Request { ) -> Self { let executable_item = executable_info.item(); Self { - state_hash, - block_time, + block_info, transaction_hash, gas_limit, initiator_addr, @@ -120,64 +344,47 @@ impl WasmV1Request { /// Creates a new request from a transaction for use as the session code. pub fn new_session( - state_hash: Digest, - block_time: BlockTime, + block_info: BlockInfo, gas_limit: Gas, - transaction: &Transaction, + session_input_data: &SessionInputData, ) -> Result { - let info = match transaction { - Transaction::Deploy(deploy) => { - SessionInfo::try_from((deploy.session(), deploy.hash()))? - } - Transaction::V1(v1_txn) => SessionInfo::try_from(v1_txn)?, - }; - - let transaction_hash = transaction.hash(); - let initiator_addr = transaction.initiator_addr(); - let authorization_keys = transaction.signers(); + let session_info = SessionInfo::try_from(session_input_data)?; + let transaction_hash = session_input_data.transaction_hash(); + let initiator_addr = session_input_data.initiator_addr().clone(); + let authorization_keys = session_input_data.signers().clone(); Ok(WasmV1Request::new_from_executable_info( - state_hash, - block_time, + block_info, gas_limit, transaction_hash, initiator_addr, authorization_keys, - info, + session_info, )) } /// Creates a new request from a transaction for use as custom payment. pub fn new_custom_payment( - state_hash: Digest, - block_time: BlockTime, + block_info: BlockInfo, gas_limit: Gas, - transaction: &Transaction, + session_input_data: &SessionInputData, ) -> Result { - let info = match transaction { - Transaction::Deploy(deploy) => { - PaymentInfo::try_from((deploy.payment(), deploy.hash()))? - } - Transaction::V1(v1_txn) => PaymentInfo::try_from(v1_txn)?, - }; - - let transaction_hash = transaction.hash(); - let initiator_addr = transaction.initiator_addr(); - let authorization_keys = transaction.signers(); + let payment_info = PaymentInfo::try_from(session_input_data)?; + let transaction_hash = session_input_data.transaction_hash(); + let initiator_addr = session_input_data.initiator_addr().clone(); + let authorization_keys = session_input_data.signers().clone(); Ok(WasmV1Request::new_from_executable_info( - state_hash, - block_time, + block_info, gas_limit, transaction_hash, initiator_addr, authorization_keys, - info, + payment_info, )) } /// Creates a new request from a deploy item for use as the session code. pub fn new_session_from_deploy_item( - state_hash: Digest, - block_time: BlockTime, + block_info: BlockInfo, gas_limit: Gas, DeployItem { ref address, @@ -187,25 +394,24 @@ impl WasmV1Request { .. }: &DeployItem, ) -> Result { - let info = SessionInfo::try_from((session, deploy_hash))?; + let transaction_hash = TransactionHash::Deploy(*deploy_hash); + let session_info = build_session_info_for_executable_item(session, transaction_hash)?; let transaction_hash = TransactionHash::Deploy(*deploy_hash); let initiator_addr = InitiatorAddr::AccountHash(*address); let authorization_keys = authorization_keys.clone(); Ok(WasmV1Request::new_from_executable_info( - state_hash, - block_time, + block_info, gas_limit, transaction_hash, initiator_addr, authorization_keys, - info, + session_info, )) } /// Creates a new request from a deploy item for use as custom payment. pub fn new_custom_payment_from_deploy_item( - state_hash: Digest, - block_time: BlockTime, + block_info: BlockInfo, gas_limit: Gas, DeployItem { ref address, @@ -215,18 +421,18 @@ impl WasmV1Request { .. }: &DeployItem, ) -> Result { - let info = PaymentInfo::try_from((payment, deploy_hash))?; + let transaction_hash = TransactionHash::Deploy(*deploy_hash); + let payment_info = build_payment_info_for_executable_item(payment, transaction_hash)?; let transaction_hash = TransactionHash::Deploy(*deploy_hash); let initiator_addr = InitiatorAddr::AccountHash(*address); let authorization_keys = authorization_keys.clone(); Ok(WasmV1Request::new_from_executable_info( - state_hash, - block_time, + block_info, gas_limit, transaction_hash, initiator_addr, authorization_keys, - info, + payment_info, )) } } @@ -438,88 +644,117 @@ impl Executable for SessionInfo { } } -impl TryFrom<(&ExecutableDeployItem, &DeployHash)> for SessionInfo { +impl TryFrom<&SessionInputData<'_>> for PaymentInfo { type Error = InvalidRequest; - fn try_from( - (session_item, deploy_hash): (&ExecutableDeployItem, &DeployHash), - ) -> Result { - let transaction_hash = TransactionHash::Deploy(*deploy_hash); - let session: ExecutableItem; - let session_entry_point: String; - let session_args: RuntimeArgs; - match session_item { - ExecutableDeployItem::ModuleBytes { module_bytes, args } => { - session = ExecutableItem::LegacyDeploy(module_bytes.clone()); - session_entry_point = DEFAULT_ENTRY_POINT.to_string(); - session_args = args.clone(); - } - ExecutableDeployItem::StoredContractByHash { - hash, - entry_point, - args, - } => { - session = ExecutableItem::Invocation( - TransactionInvocationTarget::new_invocable_entity(*hash), - ); - session_entry_point = entry_point.clone(); - session_args = args.clone(); - } - ExecutableDeployItem::StoredContractByName { - name, - entry_point, - args, - } => { - session = ExecutableItem::Invocation( - TransactionInvocationTarget::new_invocable_entity_alias(name.clone()), - ); - session_entry_point = entry_point.clone(); - session_args = args.clone(); - } - ExecutableDeployItem::StoredVersionedContractByHash { - hash, - version, - entry_point, - args, - } => { - session = ExecutableItem::Invocation(TransactionInvocationTarget::new_package( - *hash, *version, - )); - session_entry_point = entry_point.clone(); - session_args = args.clone(); - } - ExecutableDeployItem::StoredVersionedContractByName { - name, - version, - entry_point, - args, - } => { - session = ExecutableItem::Invocation( - TransactionInvocationTarget::new_package_alias(name.clone(), *version), - ); - session_entry_point = entry_point.clone(); - session_args = args.clone(); - } - ExecutableDeployItem::Transfer { .. } => { - return Err(InvalidRequest::UnsupportedMode( - transaction_hash, - session_item.to_string(), - )); - } + fn try_from(input_data: &SessionInputData) -> Result { + match input_data { + SessionInputData::DeploySessionData { data } => PaymentInfo::try_from(data), + SessionInputData::SessionDataV1 { data } => PaymentInfo::try_from(data), } + } +} - Ok(SessionInfo(ExecutableInfo { - item: session, - entry_point: session_entry_point, - args: session_args, - })) +impl TryFrom<&SessionInputData<'_>> for SessionInfo { + type Error = InvalidRequest; + + fn try_from(input_data: &SessionInputData) -> Result { + match input_data { + SessionInputData::DeploySessionData { data } => SessionInfo::try_from(data), + SessionInputData::SessionDataV1 { data } => SessionInfo::try_from(data), + } } } -impl TryFrom<&TransactionV1> for SessionInfo { +impl TryFrom<&SessionDataDeploy<'_>> for SessionInfo { type Error = InvalidRequest; - fn try_from(v1_txn: &TransactionV1) -> Result { + fn try_from(deploy_data: &SessionDataDeploy) -> Result { + let transaction_hash = TransactionHash::Deploy(*deploy_data.deploy_hash()); + let session_item = deploy_data.session(); + build_session_info_for_executable_item(session_item, transaction_hash) + } +} + +fn build_session_info_for_executable_item( + session_item: &ExecutableDeployItem, + transaction_hash: TransactionHash, +) -> Result { + let session: ExecutableItem; + let session_entry_point: String; + let session_args: RuntimeArgs; + match session_item { + ExecutableDeployItem::ModuleBytes { module_bytes, args } => { + session = ExecutableItem::LegacyDeploy(module_bytes.clone()); + session_entry_point = DEFAULT_ENTRY_POINT.to_string(); + session_args = args.clone(); + } + ExecutableDeployItem::StoredContractByHash { + hash, + entry_point, + args, + } => { + session = ExecutableItem::Invocation( + TransactionInvocationTarget::new_invocable_entity(*hash), + ); + session_entry_point = entry_point.clone(); + session_args = args.clone(); + } + ExecutableDeployItem::StoredContractByName { + name, + entry_point, + args, + } => { + session = ExecutableItem::Invocation( + TransactionInvocationTarget::new_invocable_entity_alias(name.clone()), + ); + session_entry_point = entry_point.clone(); + session_args = args.clone(); + } + ExecutableDeployItem::StoredVersionedContractByHash { + hash, + version, + entry_point, + args, + } => { + session = ExecutableItem::Invocation(TransactionInvocationTarget::new_package( + *hash, *version, + )); + session_entry_point = entry_point.clone(); + session_args = args.clone(); + } + ExecutableDeployItem::StoredVersionedContractByName { + name, + version, + entry_point, + args, + } => { + session = ExecutableItem::Invocation(TransactionInvocationTarget::new_package_alias( + name.clone(), + *version, + )); + session_entry_point = entry_point.clone(); + session_args = args.clone(); + } + ExecutableDeployItem::Transfer { .. } => { + return Err(InvalidRequest::UnsupportedMode( + transaction_hash, + session_item.to_string(), + )); + } + } + + Ok(SessionInfo(ExecutableInfo { + item: session, + entry_point: session_entry_point, + args: session_args, + })) +} + +impl TryFrom<&SessionDataV1<'_>> for SessionInfo { + type Error = InvalidRequest; + + fn try_from(v1_txn: &SessionDataV1) -> Result { let transaction_hash = TransactionHash::V1(*v1_txn.hash()); let args = v1_txn.args().clone(); let session = match v1_txn.target() { @@ -537,12 +772,6 @@ impl TryFrom<&TransactionV1> for SessionInfo { )); }; let item = ExecutableItem::Invocation(id.clone()); - let args = args.into_named().ok_or_else(|| { - InvalidRequest::UnexpectedTransactionArgs( - transaction_hash, - "named args".to_string(), - ) - })?; ExecutableInfo { item, entry_point: entry_point.clone(), @@ -556,34 +785,15 @@ impl TryFrom<&TransactionV1> for SessionInfo { v1_txn.entry_point().to_string(), )); }; - let category = v1_txn.transaction_lane(); - let lane: TransactionLane = category.try_into().map_err(|_| { - InvalidRequest::InvalidCategory(transaction_hash, category.to_string()) - })?; - let item = match lane { - TransactionLane::InstallUpgrade => ExecutableItem::SessionBytes { - kind: SessionKind::InstallUpgradeBytecode, - module_bytes: module_bytes.clone(), - }, - TransactionLane::Large | TransactionLane::Medium | TransactionLane::Small => { - ExecutableItem::SessionBytes { - kind: SessionKind::GenericBytecode, - module_bytes: module_bytes.clone(), - } - } - _ => { - return Err(InvalidRequest::InvalidCategory( - transaction_hash, - lane.to_string(), - )) - } + let kind = if v1_txn.is_install_upgrade() { + SessionKind::InstallUpgradeBytecode + } else { + SessionKind::GenericBytecode + }; + let item = ExecutableItem::SessionBytes { + kind, + module_bytes: module_bytes.clone(), }; - let args = args.into_named().ok_or_else(|| { - InvalidRequest::UnexpectedTransactionArgs( - transaction_hash, - "named args".to_string(), - ) - })?; ExecutableInfo { item, entry_point: DEFAULT_ENTRY_POINT.to_owned(), @@ -616,85 +826,91 @@ impl Executable for PaymentInfo { } } -impl TryFrom<(&ExecutableDeployItem, &DeployHash)> for PaymentInfo { +impl TryFrom<&SessionDataDeploy<'_>> for PaymentInfo { type Error = InvalidRequest; - fn try_from( - (payment_item, deploy_hash): (&ExecutableDeployItem, &DeployHash), - ) -> Result { - let transaction_hash = TransactionHash::Deploy(*deploy_hash); - match payment_item { - ExecutableDeployItem::ModuleBytes { module_bytes, args } => { - let payment = if module_bytes.is_empty() { - return Err(InvalidRequest::UnsupportedMode( - transaction_hash, - "standard payment is no longer handled by the execution engine".to_string(), - )); - } else { - ExecutableItem::PaymentBytes(module_bytes.clone()) - }; - Ok(PaymentInfo(ExecutableInfo { - item: payment, - entry_point: DEFAULT_ENTRY_POINT.to_string(), - args: args.clone(), - })) - } - ExecutableDeployItem::StoredContractByHash { - hash, - args, - entry_point, - } => Ok(PaymentInfo(ExecutableInfo { - item: ExecutableItem::Invocation(TransactionInvocationTarget::ByHash(hash.value())), - entry_point: entry_point.clone(), - args: args.clone(), - })), - ExecutableDeployItem::StoredContractByName { - name, - args, - entry_point, - } => Ok(PaymentInfo(ExecutableInfo { - item: ExecutableItem::Invocation(TransactionInvocationTarget::ByName(name.clone())), - entry_point: entry_point.clone(), - args: args.clone(), - })), - ExecutableDeployItem::StoredVersionedContractByHash { - args, - hash, - version, - entry_point, - } => Ok(PaymentInfo(ExecutableInfo { - item: ExecutableItem::Invocation(TransactionInvocationTarget::ByPackageHash { - addr: hash.value(), - version: *version, - }), - entry_point: entry_point.clone(), - args: args.clone(), - })), - ExecutableDeployItem::StoredVersionedContractByName { - name, - version, - args, - entry_point, - } => Ok(PaymentInfo(ExecutableInfo { - item: ExecutableItem::Invocation(TransactionInvocationTarget::ByPackageName { - name: name.clone(), - version: *version, - }), - entry_point: entry_point.clone(), + fn try_from(deploy_data: &SessionDataDeploy) -> Result { + let payment_item = deploy_data.session(); + let transaction_hash = TransactionHash::Deploy(*deploy_data.deploy_hash()); + build_payment_info_for_executable_item(payment_item, transaction_hash) + } +} + +fn build_payment_info_for_executable_item( + payment_item: &ExecutableDeployItem, + transaction_hash: TransactionHash, +) -> Result { + match payment_item { + ExecutableDeployItem::ModuleBytes { module_bytes, args } => { + let payment = if module_bytes.is_empty() { + return Err(InvalidRequest::UnsupportedMode( + transaction_hash, + "standard payment is no longer handled by the execution engine".to_string(), + )); + } else { + ExecutableItem::PaymentBytes(module_bytes.clone()) + }; + Ok(PaymentInfo(ExecutableInfo { + item: payment, + entry_point: DEFAULT_ENTRY_POINT.to_string(), args: args.clone(), - })), - ExecutableDeployItem::Transfer { .. } => Err(InvalidRequest::UnexpectedVariant( - transaction_hash, - "payment item".to_string(), - )), + })) } + ExecutableDeployItem::StoredContractByHash { + hash, + args, + entry_point, + } => Ok(PaymentInfo(ExecutableInfo { + item: ExecutableItem::Invocation(TransactionInvocationTarget::ByHash(hash.value())), + entry_point: entry_point.clone(), + args: args.clone(), + })), + ExecutableDeployItem::StoredContractByName { + name, + args, + entry_point, + } => Ok(PaymentInfo(ExecutableInfo { + item: ExecutableItem::Invocation(TransactionInvocationTarget::ByName(name.clone())), + entry_point: entry_point.clone(), + args: args.clone(), + })), + ExecutableDeployItem::StoredVersionedContractByHash { + args, + hash, + version, + entry_point, + } => Ok(PaymentInfo(ExecutableInfo { + item: ExecutableItem::Invocation(TransactionInvocationTarget::ByPackageHash { + addr: hash.value(), + version: *version, + }), + entry_point: entry_point.clone(), + args: args.clone(), + })), + ExecutableDeployItem::StoredVersionedContractByName { + name, + version, + args, + entry_point, + } => Ok(PaymentInfo(ExecutableInfo { + item: ExecutableItem::Invocation(TransactionInvocationTarget::ByPackageName { + name: name.clone(), + version: *version, + }), + entry_point: entry_point.clone(), + args: args.clone(), + })), + ExecutableDeployItem::Transfer { .. } => Err(InvalidRequest::UnexpectedVariant( + transaction_hash, + "payment item".to_string(), + )), } } -impl TryFrom<&TransactionV1> for PaymentInfo { +impl TryFrom<&SessionDataV1<'_>> for PaymentInfo { type Error = InvalidRequest; - fn try_from(v1_txn: &TransactionV1) -> Result { + fn try_from(v1_txn: &SessionDataV1) -> Result { let transaction_hash = TransactionHash::V1(*v1_txn.hash()); match v1_txn.pricing_mode() { mode @ PricingMode::PaymentLimited { @@ -723,17 +939,11 @@ impl TryFrom<&TransactionV1> for PaymentInfo { v1_txn.entry_point().to_string(), )); }; - let args = v1_txn.args().clone().into_named().ok_or_else(|| { - InvalidRequest::UnexpectedTransactionArgs( - transaction_hash, - "named args".to_string(), - ) - })?; let item = ExecutableItem::PaymentBytes(module_bytes.clone()); ExecutableInfo { item, entry_point: DEFAULT_ENTRY_POINT.to_owned(), - args, + args: v1_txn.args().clone(), } } TransactionTarget::Native | TransactionTarget::Stored { .. } => { diff --git a/execution_engine/src/execution/error.rs b/execution_engine/src/execution/error.rs index 624a399469..007f8d8d3a 100644 --- a/execution_engine/src/execution/error.rs +++ b/execution_engine/src/execution/error.rs @@ -183,9 +183,9 @@ pub enum Error { /// The EntryPoints contains an invalid entry. #[error("The EntryPoints contains an invalid entry")] InvalidEntryPointType, - /// Invalid message topic operation. - #[error("The requested operation is invalid for a message topic")] - InvalidMessageTopicOperation, + /// Invalid operation. + #[error("The imputed operation is invalid")] + InvalidImputedOperation, /// Invalid string encoding. #[error("Invalid UTF-8 string encoding: {0}")] InvalidUtf8Encoding(Utf8Error), diff --git a/execution_engine/src/execution/executor.rs b/execution_engine/src/execution/executor.rs index 568e939fed..dbf9ea005e 100644 --- a/execution_engine/src/execution/executor.rs +++ b/execution_engine/src/execution/executor.rs @@ -7,13 +7,13 @@ use casper_storage::{ }; use casper_types::{ account::AccountHash, addressable_entity::NamedKeys, contract_messages::Messages, - execution::Effects, AddressableEntity, AddressableEntityHash, BlockTime, ContextAccessRights, + execution::Effects, AddressableEntity, AddressableEntityHash, ContextAccessRights, EntryPointType, Gas, Key, Phase, ProtocolVersion, RuntimeArgs, StoredValue, Tagged, TransactionHash, U512, }; use crate::{ - engine_state::{execution_kind::ExecutionKind, EngineConfig, WasmV1Result}, + engine_state::{execution_kind::ExecutionKind, BlockInfo, EngineConfig, WasmV1Result}, execution::ExecError, runtime::{Runtime, RuntimeStack}, runtime_context::{CallingAddContractVersion, RuntimeContext}, @@ -53,7 +53,7 @@ impl Executor { access_rights: ContextAccessRights, authorization_keys: BTreeSet, account_hash: AccountHash, - blocktime: BlockTime, + block_info: BlockInfo, txn_hash: TransactionHash, gas_limit: Gas, protocol_version: ProtocolVersion, @@ -103,7 +103,7 @@ impl Executor { account_hash, address_generator, tracking_copy, - blocktime, + block_info, protocol_version, txn_hash, phase, @@ -169,7 +169,7 @@ impl Executor { account_hash: AccountHash, address_generator: Rc>, tracking_copy: Rc>>, - blocktime: BlockTime, + block_info: BlockInfo, protocol_version: ProtocolVersion, txn_hash: TransactionHash, phase: Phase, @@ -195,7 +195,7 @@ impl Executor { address_generator, tracking_copy, self.config.clone(), - blocktime, + block_info, protocol_version, txn_hash, phase, diff --git a/execution_engine/src/lib.rs b/execution_engine/src/lib.rs index 793b062e47..edfa94f13b 100644 --- a/execution_engine/src/lib.rs +++ b/execution_engine/src/lib.rs @@ -12,6 +12,8 @@ trivial_numeric_casts, unused_qualifications )] +#![warn(missing_docs)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod engine_state; pub mod execution; diff --git a/execution_engine/src/resolvers/mod.rs b/execution_engine/src/resolvers/mod.rs index 9dc81c0c29..9a14b51638 100644 --- a/execution_engine/src/resolvers/mod.rs +++ b/execution_engine/src/resolvers/mod.rs @@ -22,7 +22,7 @@ pub(crate) fn create_module_resolver( // TODO: revisit how protocol_version check here is meant to combine with upgrade if protocol_version >= ProtocolVersion::V1_0_0 { return Ok(v1_resolver::RuntimeModuleImportResolver::new( - engine_config.wasm_config().max_memory, + engine_config.wasm_config().v1().max_memory(), )); } Err(ResolverError::UnknownProtocolVersion(protocol_version)) diff --git a/execution_engine/src/resolvers/v1_function_index.rs b/execution_engine/src/resolvers/v1_function_index.rs index 3ec135cd00..fb6d1049b8 100644 --- a/execution_engine/src/resolvers/v1_function_index.rs +++ b/execution_engine/src/resolvers/v1_function_index.rs @@ -60,6 +60,7 @@ pub(crate) enum FunctionIndex { ManageMessageTopic, EmitMessage, LoadCallerInformation, + GetBlockInfoIndex, } impl From for usize { diff --git a/execution_engine/src/resolvers/v1_resolver.rs b/execution_engine/src/resolvers/v1_resolver.rs index fa35f8f3d7..5aa241d0df 100644 --- a/execution_engine/src/resolvers/v1_resolver.rs +++ b/execution_engine/src/resolvers/v1_resolver.rs @@ -253,6 +253,10 @@ impl ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), FunctionIndex::EmitMessage.into(), ), + "casper_get_block_info" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], None), + FunctionIndex::GetBlockInfoIndex.into(), + ), _ => { return Err(InterpreterError::Function(format!( "host module doesn't export function with name {}", diff --git a/execution_engine/src/runtime/externals.rs b/execution_engine/src/runtime/externals.rs index d8f76bc123..e5b80f6793 100644 --- a/execution_engine/src/runtime/externals.rs +++ b/execution_engine/src/runtime/externals.rs @@ -32,11 +32,8 @@ where ) -> Result, Trap> { let func = FunctionIndex::try_from(index).expect("unknown function index"); - let host_function_costs = self - .context - .engine_config() - .wasm_config() - .take_host_function_costs(); + let host_function_costs = + (*self.context.engine_config().wasm_config().v1()).take_host_function_costs(); match func { FunctionIndex::ReadFuncIndex => { @@ -606,6 +603,7 @@ where )?; Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) } + FunctionIndex::AddContractVersion => { // args(0) = pointer to package key in wasm memory // args(1) = size of package key in wasm memory @@ -660,6 +658,7 @@ where )?; Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) } + FunctionIndex::AddPackageVersion => { // args(0) = pointer to package hash in wasm memory // args(1) = size of package hash in wasm memory @@ -1152,6 +1151,7 @@ where Ok(Some(RuntimeValue::I32(0))) } + FunctionIndex::EnableContractVersion => { // args(0) = pointer to package hash in wasm memory // args(1) = size of package hash in wasm memory @@ -1175,6 +1175,7 @@ where Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } + FunctionIndex::ManageMessageTopic => { // args(0) = pointer to the serialized topic name string in wasm memory // args(1) = size of the serialized topic name string in wasm memory @@ -1206,11 +1207,11 @@ where .map_err(|e| Trap::from(ExecError::InvalidUtf8Encoding(e)))?; if operation_size as usize > MessageTopicOperation::max_serialized_len() { - return Err(Trap::from(ExecError::InvalidMessageTopicOperation)); + return Err(Trap::from(ExecError::InvalidImputedOperation)); } let topic_operation = self .t_from_mem(operation_ptr, operation_size) - .map_err(|_e| Trap::from(ExecError::InvalidMessageTopicOperation))?; + .map_err(|_e| Trap::from(ExecError::InvalidImputedOperation))?; // only allow managing messages from stored contracts if !self.context.get_entity_key().is_smart_contract_key() { @@ -1225,6 +1226,7 @@ where Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } + FunctionIndex::EmitMessage => { // args(0) = pointer to the serialized topic name string in wasm memory // args(1) = size of the serialized name string in wasm memory @@ -1276,6 +1278,16 @@ where } Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) } + + FunctionIndex::GetBlockInfoIndex => { + // args(0) = field selector + // args(1) = pointer to output pointer where host will write argument bytes + let (field_idx, dest_ptr): (u8, u32) = Args::parse(args)?; + + self.charge_host_function_call(&host_function_costs.get_block_info, [0u32, 0u32])?; + self.get_block_info(field_idx, dest_ptr)?; + Ok(None) + } } } } diff --git a/execution_engine/src/runtime/mint_internal.rs b/execution_engine/src/runtime/mint_internal.rs index 5bf26e0aaf..d137b65109 100644 --- a/execution_engine/src/runtime/mint_internal.rs +++ b/execution_engine/src/runtime/mint_internal.rs @@ -64,7 +64,7 @@ where .read_addressable_entity_by_account_hash(account_hash) .map_err(|err| { error!(%err, "error reading addressable entity by account hash"); - ProviderError::AddressableEntityByAccountHash(account_hash) + ProviderError::AccountHash(account_hash) }) } diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 8debe05174..4287183daa 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -434,11 +434,48 @@ where .map_err(|e| ExecError::Interpreter(e.into()).into()) } + /// Writes requested field from runtime context's block info to dest_ptr in the Wasm memory. + fn get_block_info(&self, field_idx: u8, dest_ptr: u32) -> Result<(), Trap> { + if field_idx == 0 { + // original functionality + return self.get_blocktime(dest_ptr); + } + let block_info = self.context.get_block_info(); + + let mut data: Vec = vec![]; + if field_idx == 1 { + data = block_info + .block_height() + .into_bytes() + .map_err(ExecError::BytesRepr)?; + } + if field_idx == 2 { + data = block_info + .parent_block_hash() + .into_bytes() + .map_err(ExecError::BytesRepr)?; + } + if field_idx == 3 { + data = block_info + .state_hash() + .into_bytes() + .map_err(ExecError::BytesRepr)?; + } + if data.is_empty() { + Err(ExecError::InvalidImputedOperation.into()) + } else { + Ok(self + .try_get_memory()? + .set(dest_ptr, &data) + .map_err(|e| ExecError::Interpreter(e.into()))?) + } + } + /// Writes current blocktime to dest_ptr in Wasm memory. fn get_blocktime(&self, dest_ptr: u32) -> Result<(), Trap> { - let blocktime = self - .context - .get_blocktime() + let block_info = self.context.get_block_info(); + let blocktime = block_info + .block_time() .into_bytes() .map_err(ExecError::BytesRepr)?; self.try_get_memory()? @@ -973,6 +1010,7 @@ where Self::try_get_named_argument(runtime_args, auction::ARG_RESERVED_SLOTS)? .unwrap_or(0); + let minimum_bid_amount = self.context().engine_config().minimum_bid_amount(); let result = runtime .add_bid( account_hash, @@ -980,6 +1018,7 @@ where amount, minimum_delegation_amount, maximum_delegation_amount, + minimum_bid_amount, reserved_slots, ) .map_err(Self::reverter)?; @@ -992,9 +1031,10 @@ where let public_key = Self::get_named_argument(runtime_args, auction::ARG_PUBLIC_KEY)?; let amount = Self::get_named_argument(runtime_args, auction::ARG_AMOUNT)?; + let min_bid_amount = self.context.engine_config().minimum_bid_amount(); let result = runtime - .withdraw_bid(public_key, amount) + .withdraw_bid(public_key, amount, min_bid_amount) .map_err(Self::reverter)?; CLValue::from_t(result).map_err(Self::reverter) })(), @@ -1171,7 +1211,7 @@ where let engine_config = self.context.engine_config(); let wasm_config = engine_config.wasm_config(); #[cfg(feature = "test-support")] - let max_stack_height = wasm_config.max_stack_height; + let max_stack_height = wasm_config.v1().max_stack_height(); let module = wasm_prep::preprocess(*wasm_config, module_bytes)?; let (instance, memory) = utils::instance_and_memory(module.clone(), protocol_version, engine_config)?; @@ -1643,7 +1683,11 @@ where #[cfg(feature = "test-support")] dump_runtime_stack_info( instance, - self.context.engine_config().wasm_config().max_stack_height, + self.context + .engine_config() + .wasm_config() + .v1() + .max_stack_height(), ); if let Some(host_error) = error.as_host_error() { // If the "error" was in fact a trap caused by calling `ret` then this is normal @@ -2017,10 +2061,8 @@ where for (_, topic_hash) in previous_message_topics.iter() { let topic_key = Key::message_topic(entity_addr, *topic_hash); - let summary = StoredValue::MessageTopic(MessageTopicSummary::new( - 0, - self.context.get_blocktime(), - )); + let block_time = self.context.get_block_info().block_time(); + let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0, block_time)); self.context.metered_write_gs_unsafe(topic_key, summary)?; } @@ -3286,7 +3328,9 @@ where where T: AsRef<[HostFunctionCost]> + Copy, { - let cost = host_function.calculate_gas_cost(weights); + let cost = host_function + .calculate_gas_cost(weights) + .ok_or(ExecError::GasLimit)?; // Overflowing gas calculation means gas limit was exceeded self.gas(cost)?; Ok(()) } @@ -3557,7 +3601,7 @@ where return Ok(Err(ApiError::MessageTopicNotRegistered)); }; - let current_blocktime = self.context.get_blocktime(); + let current_blocktime = self.context.get_block_info().block_time(); let topic_message_index = if prev_topic_summary.blocktime() != current_blocktime { for index in 1..prev_topic_summary.message_count() { self.context diff --git a/execution_engine/src/runtime/wasm_prep.rs b/execution_engine/src/runtime/wasm_prep.rs index e2191018ac..220d5643e8 100644 --- a/execution_engine/src/runtime/wasm_prep.rs +++ b/execution_engine/src/runtime/wasm_prep.rs @@ -17,8 +17,6 @@ use crate::execution::ExecError; const ATOMIC_OPCODE_PREFIX: u8 = 0xfe; const BULK_OPCODE_PREFIX: u8 = 0xfc; -const SIGN_EXT_OPCODE_START: u8 = 0xc0; -const SIGN_EXT_OPCODE_END: u8 = 0xc4; const SIMD_OPCODE_PREFIX: u8 = 0xfd; const DEFAULT_GAS_MODULE_NAME: &str = "env"; @@ -404,11 +402,11 @@ pub(crate) fn preprocess( ensure_parameter_limit(&module, DEFAULT_MAX_PARAMETER_COUNT)?; ensure_valid_imports(&module)?; - let costs = RuledOpcodeCosts(wasm_config.opcode_costs()); - let module = casper_wasm_utils::externalize_mem(module, None, wasm_config.max_memory); + let costs = RuledOpcodeCosts(wasm_config.v1().opcode_costs()); + let module = casper_wasm_utils::externalize_mem(module, None, wasm_config.v1().max_memory()); let module = casper_wasm_utils::inject_gas_counter(module, &costs, DEFAULT_GAS_MODULE_NAME) .map_err(|_| PreprocessingError::OperationForbiddenByGasRules)?; - let module = stack_height::inject_limiter(module, wasm_config.max_stack_height) + let module = stack_height::inject_limiter(module, wasm_config.v1().max_stack_height()) .map_err(|_| PreprocessingError::StackLimiter)?; Ok(module) } @@ -428,11 +426,9 @@ pub fn deserialize(module_bytes: &[u8]) -> Result { casper_wasm::SerializationError::UnknownOpcode(ATOMIC_OPCODE_PREFIX) => { PreprocessingError::Deserialize("Atomic operations are not supported".to_string()) } - casper_wasm::SerializationError::UnknownOpcode( - SIGN_EXT_OPCODE_START..=SIGN_EXT_OPCODE_END, - ) => PreprocessingError::Deserialize( - "Sign extension operations are not supported".to_string(), - ), + casper_wasm::SerializationError::UnknownOpcode(_) => { + PreprocessingError::Deserialize("Encountered an unsupported operation".to_string()) + } casper_wasm::SerializationError::Other( "Enable the multi_value feature to deserialize more than one function result", ) => { @@ -681,11 +677,13 @@ impl Rules for RuledOpcodeCosts { | Instruction::F64ConvertUI64 | Instruction::F64PromoteF32 => None, // Unsupported conversion operators for floats. + // Unsupported reinterpretation operators for floats. Instruction::I32ReinterpretF32 | Instruction::I64ReinterpretF64 | Instruction::F32ReinterpretI32 - | Instruction::F64ReinterpretI64 => None, /* Unsupported reinterpretation operators - * for floats. */ + | Instruction::F64ReinterpretI64 => None, + + Instruction::SignExt(_) => Some(costs.sign), } } @@ -701,10 +699,7 @@ mod tests { builder, elements::{CodeSection, Instructions}, }; - use walrus::{ - ir::{Instr, UnaryOp, Unop}, - FunctionBuilder, ModuleConfig, ValType, - }; + use walrus::{FunctionBuilder, ModuleConfig, ValType}; use super::*; @@ -1010,214 +1005,4 @@ mod tests { error, ); } - - #[test] - fn should_not_accept_sign_ext_i32_e8s_proposal_wasm() { - let module_bytes = { - let mut module = walrus::Module::with_config(ModuleConfig::new()); - - let _memory_id = module.memories.add_local(false, 11, None); - - let mut func_with_sign_ext = FunctionBuilder::new(&mut module.types, &[], &[]); - - func_with_sign_ext.func_body().i32_const(0); - - { - let mut body = func_with_sign_ext.func_body(); - let instructions = body.instrs_mut(); - let (instr, _) = instructions.get_mut(0).unwrap(); - *instr = Instr::Unop(Unop { - op: UnaryOp::I32Extend8S, - }); - } - - let func_with_sign_ext = func_with_sign_ext.finish(vec![], &mut module.funcs); - - let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]); - - call_func.func_body().call(func_with_sign_ext); - - let call = call_func.finish(Vec::new(), &mut module.funcs); - - module.exports.add(DEFAULT_ENTRY_POINT_NAME, call); - - module.emit_wasm() - }; - let error = preprocess(WasmConfig::default(), &module_bytes) - .expect_err("should fail with an error"); - assert!( - matches!(&error, PreprocessingError::Deserialize(msg) - if msg == "Sign extension operations are not supported"), - "{:?}", - error, - ); - } - - #[test] - fn should_not_accept_sign_ext_i32_e16s_proposal_wasm() { - let module_bytes = { - let mut module = walrus::Module::with_config(ModuleConfig::new()); - - let _memory_id = module.memories.add_local(false, 11, None); - - let mut func_with_sign_ext = FunctionBuilder::new(&mut module.types, &[], &[]); - - func_with_sign_ext.func_body().i32_const(0); - - { - let mut body = func_with_sign_ext.func_body(); - let instructions = body.instrs_mut(); - let (instr, _) = instructions.get_mut(0).unwrap(); - *instr = Instr::Unop(Unop { - op: UnaryOp::I32Extend16S, - }); - } - - let func_with_sign_ext = func_with_sign_ext.finish(vec![], &mut module.funcs); - - let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]); - - call_func.func_body().call(func_with_sign_ext); - - let call = call_func.finish(Vec::new(), &mut module.funcs); - - module.exports.add(DEFAULT_ENTRY_POINT_NAME, call); - - module.emit_wasm() - }; - let error = preprocess(WasmConfig::default(), &module_bytes) - .expect_err("should fail with an error"); - assert!( - matches!(&error, PreprocessingError::Deserialize(msg) - if msg == "Sign extension operations are not supported"), - "{:?}", - error, - ); - } - - #[test] - fn should_not_accept_sign_ext_i64_e8s_proposal_wasm() { - let module_bytes = { - let mut module = walrus::Module::with_config(ModuleConfig::new()); - - let _memory_id = module.memories.add_local(false, 11, None); - - let mut func_with_sign_ext = FunctionBuilder::new(&mut module.types, &[], &[]); - - func_with_sign_ext.func_body().i32_const(0); - - { - let mut body = func_with_sign_ext.func_body(); - let instructions = body.instrs_mut(); - let (instr, _) = instructions.get_mut(0).unwrap(); - *instr = Instr::Unop(Unop { - op: UnaryOp::I64Extend8S, - }); - } - - let func_with_sign_ext = func_with_sign_ext.finish(vec![], &mut module.funcs); - - let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]); - - call_func.func_body().call(func_with_sign_ext); - - let call = call_func.finish(Vec::new(), &mut module.funcs); - - module.exports.add(DEFAULT_ENTRY_POINT_NAME, call); - - module.emit_wasm() - }; - let error = preprocess(WasmConfig::default(), &module_bytes) - .expect_err("should fail with an error"); - assert!( - matches!(&error, PreprocessingError::Deserialize(msg) - if msg == "Sign extension operations are not supported"), - "{:?}", - error, - ); - } - - #[test] - fn should_not_accept_sign_ext_i64_e16s_proposal_wasm() { - let module_bytes = { - let mut module = walrus::Module::with_config(ModuleConfig::new()); - - let _memory_id = module.memories.add_local(false, 11, None); - - let mut func_with_sign_ext = FunctionBuilder::new(&mut module.types, &[], &[]); - - func_with_sign_ext.func_body().i32_const(0); - - { - let mut body = func_with_sign_ext.func_body(); - let instructions = body.instrs_mut(); - let (instr, _) = instructions.get_mut(0).unwrap(); - *instr = Instr::Unop(Unop { - op: UnaryOp::I64Extend16S, - }); - } - - let func_with_sign_ext = func_with_sign_ext.finish(vec![], &mut module.funcs); - - let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]); - - call_func.func_body().call(func_with_sign_ext); - - let call = call_func.finish(Vec::new(), &mut module.funcs); - - module.exports.add(DEFAULT_ENTRY_POINT_NAME, call); - - module.emit_wasm() - }; - let error = preprocess(WasmConfig::default(), &module_bytes) - .expect_err("should fail with an error"); - assert!( - matches!(&error, PreprocessingError::Deserialize(msg) - if msg == "Sign extension operations are not supported"), - "{:?}", - error, - ); - } - - #[test] - fn should_not_accept_sign_ext_i64_e32s_proposal_wasm() { - let module_bytes = { - let mut module = walrus::Module::with_config(ModuleConfig::new()); - - let _memory_id = module.memories.add_local(false, 11, None); - - let mut func_with_sign_ext = FunctionBuilder::new(&mut module.types, &[], &[]); - - func_with_sign_ext.func_body().i32_const(0); - - { - let mut body = func_with_sign_ext.func_body(); - let instructions = body.instrs_mut(); - let (instr, _) = instructions.get_mut(0).unwrap(); - *instr = Instr::Unop(Unop { - op: UnaryOp::I64Extend32S, - }); - } - - let func_with_sign_ext = func_with_sign_ext.finish(vec![], &mut module.funcs); - - let mut call_func = FunctionBuilder::new(&mut module.types, &[], &[]); - - call_func.func_body().call(func_with_sign_ext); - - let call = call_func.finish(Vec::new(), &mut module.funcs); - - module.exports.add(DEFAULT_ENTRY_POINT_NAME, call); - - module.emit_wasm() - }; - let error = preprocess(WasmConfig::default(), &module_bytes) - .expect_err("should fail with an error"); - assert!( - matches!(&error, PreprocessingError::Deserialize(msg) - if msg == "Sign extension operations are not supported"), - "{:?}", - error, - ); - } } diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index a9f38f768c..b0fd0c317d 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -42,7 +42,10 @@ use casper_types::{ DICTIONARY_ITEM_KEY_MAX_LENGTH, KEY_HASH_LENGTH, U512, }; -use crate::{engine_state::EngineConfig, execution::ExecError}; +use crate::{ + engine_state::{BlockInfo, EngineConfig}, + execution::ExecError, +}; /// Number of bytes returned from the `random_bytes` function. pub const RANDOM_BYTES_COUNT: usize = 32; @@ -65,7 +68,7 @@ pub struct RuntimeContext<'a, R> { access_rights: ContextAccessRights, args: RuntimeArgs, authorization_keys: BTreeSet, - blocktime: BlockTime, + block_info: BlockInfo, transaction_hash: TransactionHash, gas_limit: Gas, gas_counter: Gas, @@ -105,7 +108,7 @@ where address_generator: Rc>, tracking_copy: Rc>>, engine_config: EngineConfig, - blocktime: BlockTime, + block_info: BlockInfo, protocol_version: ProtocolVersion, transaction_hash: TransactionHash, phase: Phase, @@ -117,8 +120,7 @@ where entry_point_type: EntryPointType, calling_add_contract_version: CallingAddContractVersion, ) -> Self { - let emit_message_cost = engine_config - .wasm_config() + let emit_message_cost = (*engine_config.wasm_config().v1()) .take_host_function_costs() .emit_message .cost() @@ -133,7 +135,7 @@ where entity_key, authorization_keys, account_hash, - blocktime, + block_info, transaction_hash, gas_limit, gas_counter, @@ -166,7 +168,7 @@ where let tracking_copy = self.state(); let engine_config = self.engine_config.clone(); - let blocktime = self.blocktime; + let block_info = self.block_info; let protocol_version = self.protocol_version; let transaction_hash = self.transaction_hash; let phase = self.phase; @@ -187,7 +189,7 @@ where entity_key, authorization_keys, account_hash, - blocktime, + block_info, transaction_hash, gas_limit, gas_counter, @@ -266,9 +268,9 @@ where self.remove_key_from_entity(name) } - /// Returns the block time. - pub fn get_blocktime(&self) -> BlockTime { - self.blocktime + /// Returns block info. + pub fn get_block_info(&self) -> BlockInfo { + self.block_info } /// Returns the transaction hash. @@ -846,7 +848,7 @@ where } } - let storage_costs = self.engine_config.wasm_config().storage_costs(); + let storage_costs = self.engine_config.storage_costs(); let gas_cost = storage_costs.calculate_gas_cost(bytes_count); @@ -1237,7 +1239,7 @@ where ) -> Result { self.tracking_copy .borrow_mut() - .get_legacy_contract(legacy_contract) + .get_contract(legacy_contract) .map_err(Into::into) } @@ -1432,7 +1434,8 @@ where }; let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_name_hash)); - let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0, self.get_blocktime())); + let block_time = self.block_info.block_time(); + let summary = StoredValue::MessageTopic(MessageTopicSummary::new(0, block_time)); let entity_value = self.addressable_entity_to_validated_value(entity)?; diff --git a/execution_engine/src/runtime_context/tests.rs b/execution_engine/src/runtime_context/tests.rs index 3bc019bc10..1fa385d4e2 100644 --- a/execution_engine/src/runtime_context/tests.rs +++ b/execution_engine/src/runtime_context/tests.rs @@ -17,16 +17,16 @@ use casper_types::{ bytesrepr::ToBytes, execution::TransformKindV2, system::{AUCTION, HANDLE_PAYMENT, MINT, STANDARD_PAYMENT}, - AccessRights, AddressableEntity, AddressableEntityHash, BlockGlobalAddr, BlockTime, - ByteCodeHash, CLValue, ContextAccessRights, EntityAddr, EntityKind, EntryPointType, Gas, Key, - PackageHash, Phase, ProtocolVersion, PublicKey, RuntimeArgs, SecretKey, StoredValue, + AccessRights, AddressableEntity, AddressableEntityHash, BlockGlobalAddr, BlockHash, BlockTime, + ByteCodeHash, CLValue, ContextAccessRights, Digest, EntityAddr, EntityKind, EntryPointType, + Gas, Key, PackageHash, Phase, ProtocolVersion, PublicKey, RuntimeArgs, SecretKey, StoredValue, SystemEntityRegistry, Tagged, Timestamp, TransactionHash, TransactionV1Hash, URef, KEY_HASH_LENGTH, U256, U512, }; use tempfile::TempDir; use super::{CallingAddContractVersion, ExecError, RuntimeContext}; -use crate::engine_state::EngineConfig; +use crate::engine_state::{BlockInfo, EngineConfig}; const TXN_HASH_RAW: [u8; 32] = [1u8; 32]; const PHASE: Phase = Phase::Session; @@ -159,7 +159,12 @@ fn new_runtime_context<'a>( Rc::new(RefCell::new(address_generator)), Rc::new(RefCell::new(tracking_copy)), TEST_ENGINE_CONFIG.clone(), - BlockTime::new(0), + BlockInfo::new( + Digest::default(), + BlockTime::new(0), + BlockHash::default(), + 0, + ), ProtocolVersion::V1_0_0, TransactionHash::V1(TransactionV1Hash::from_raw([1u8; 32])), Phase::Session, @@ -426,7 +431,12 @@ fn contract_key_addable_valid() { Rc::new(RefCell::new(address_generator)), Rc::clone(&tracking_copy), EngineConfig::default(), - BlockTime::new(0), + BlockInfo::new( + Digest::default(), + BlockTime::new(0), + BlockHash::default(), + 0, + ), ProtocolVersion::V1_0_0, TransactionHash::V1(TransactionV1Hash::from_raw(TXN_HASH_RAW)), PHASE, @@ -484,7 +494,12 @@ fn contract_key_addable_invalid() { Rc::new(RefCell::new(address_generator)), Rc::clone(&tracking_copy), EngineConfig::default(), - BlockTime::new(0), + BlockInfo::new( + Digest::default(), + BlockTime::new(0), + BlockHash::default(), + 0, + ), ProtocolVersion::V1_0_0, TransactionHash::V1(TransactionV1Hash::from_raw(TXN_HASH_RAW)), PHASE, @@ -972,7 +987,6 @@ fn should_meter_for_gas_storage_write() { let value = StoredValue::CLValue(CLValue::from_t(43_i32).unwrap()); let expected_write_cost = test_engine_config() - .wasm_config() .storage_costs() .calculate_gas_cost(value.serialized_length()); @@ -993,7 +1007,10 @@ fn should_meter_for_gas_storage_write() { gas_usage_before ); - assert_eq!(gas_usage_after, gas_usage_before + expected_write_cost); + assert_eq!( + Some(gas_usage_after), + gas_usage_before.checked_add(expected_write_cost) + ); } #[test] @@ -1007,7 +1024,6 @@ fn should_meter_for_gas_storage_add() { let value = StoredValue::CLValue(CLValue::from_t(43_i32).unwrap()); let expected_add_cost = test_engine_config() - .wasm_config() .storage_costs() .calculate_gas_cost(value.serialized_length()); @@ -1029,7 +1045,10 @@ fn should_meter_for_gas_storage_add() { gas_usage_before ); - assert_eq!(gas_usage_after, gas_usage_before + expected_add_cost); + assert_eq!( + Some(gas_usage_after), + gas_usage_before.checked_add(expected_add_cost) + ); } #[test] diff --git a/execution_engine_testing/test_support/Cargo.toml b/execution_engine_testing/test_support/Cargo.toml index 2f320b8c30..b50ffee95c 100644 --- a/execution_engine_testing/test_support/Cargo.toml +++ b/execution_engine_testing/test_support/Cargo.toml @@ -7,7 +7,7 @@ description = "Library to support testing of Wasm smart contracts for use on the documentation = "https://docs.rs/casper-engine-test-support" readme = "README.md" homepage = "https://casperlabs.io" -repository = "https://github.com/CasperLabs/casper-node/tree/master/execution_engine_testing/test_support" +repository = "https://github.com/casper-network/casper-node/tree/master/execution_engine_testing/test_support" license = "Apache-2.0" [dependencies] diff --git a/execution_engine_testing/test_support/src/chainspec_config.rs b/execution_engine_testing/test_support/src/chainspec_config.rs index 66f65b80c3..0417c41dc2 100644 --- a/execution_engine_testing/test_support/src/chainspec_config.rs +++ b/execution_engine_testing/test_support/src/chainspec_config.rs @@ -13,7 +13,7 @@ use casper_storage::data_access_layer::GenesisRequest; use casper_types::{ system::auction::VESTING_SCHEDULE_LENGTH_MILLIS, CoreConfig, FeeHandling, GenesisAccount, GenesisConfig, GenesisConfigBuilder, MintCosts, PricingHandling, ProtocolVersion, - RefundHandling, SystemConfig, TimeDiff, WasmConfig, + RefundHandling, StorageCosts, SystemConfig, TimeDiff, WasmConfig, }; use crate::{ @@ -58,6 +58,8 @@ pub struct ChainspecConfig { /// SystemConfig #[serde(rename = "system_costs")] pub system_costs_config: SystemConfig, + /// Storage costs. + pub storage_costs: StorageCosts, } impl ChainspecConfig { @@ -121,6 +123,7 @@ impl ChainspecConfig { core_config, wasm_config, system_costs_config, + storage_costs, } = self; let CoreConfig { validator_slots, @@ -141,6 +144,7 @@ impl ChainspecConfig { .with_round_seigniorage_rate(*round_seigniorage_rate) .with_unbonding_delay(*unbonding_delay) .with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS) + .with_storage_costs(*storage_costs) .build(); Ok(GenesisRequest::new( @@ -207,7 +211,7 @@ impl ChainspecConfig { /// Sets wasm max stack height. pub fn with_wasm_max_stack_height(mut self, max_stack_height: u32) -> Self { - self.wasm_config.max_stack_height = max_stack_height; + *self.wasm_config.v1_mut().max_stack_height_mut() = max_stack_height; self } @@ -251,6 +255,7 @@ impl ChainspecConfig { .with_allow_unrestricted_transfers(self.core_config.allow_unrestricted_transfers) .with_refund_handling(self.core_config.refund_handling) .with_fee_handling(self.core_config.fee_handling) + .with_storage_costs(self.storage_costs) .build() } } @@ -296,6 +301,7 @@ impl TryFrom for GenesisConfig { .with_round_seigniorage_rate(chainspec_config.core_config.round_seigniorage_rate) .with_unbonding_delay(chainspec_config.core_config.unbonding_delay) .with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS) + .with_storage_costs(chainspec_config.storage_costs) .build()) } } diff --git a/execution_engine_testing/test_support/src/execute_request_builder.rs b/execution_engine_testing/test_support/src/execute_request_builder.rs index 3000a00852..45c98fdac5 100644 --- a/execution_engine_testing/test_support/src/execute_request_builder.rs +++ b/execution_engine_testing/test_support/src/execute_request_builder.rs @@ -1,12 +1,12 @@ use std::collections::BTreeSet; use casper_execution_engine::engine_state::{ - deploy_item::DeployItem, ExecutableItem, WasmV1Request, + deploy_item::DeployItem, BlockInfo, ExecutableItem, SessionInputData, WasmV1Request, }; use casper_types::{ account::AccountHash, addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, - AddressableEntityHash, BlockTime, Digest, EntityVersion, Gas, InitiatorAddr, PackageHash, - Phase, RuntimeArgs, Transaction, TransactionHash, TransactionV1Hash, + AddressableEntityHash, BlockHash, BlockTime, Digest, EntityVersion, Gas, InitiatorAddr, + PackageHash, Phase, RuntimeArgs, TransactionHash, TransactionV1Hash, }; use crate::{DeployItemBuilder, ARG_AMOUNT, DEFAULT_BLOCK_TIME, DEFAULT_PAYMENT}; @@ -26,6 +26,8 @@ pub struct ExecuteRequest { pub struct ExecuteRequestBuilder { state_hash: Digest, block_time: BlockTime, + block_height: u64, + parent_block_hash: BlockHash, transaction_hash: TransactionHash, initiator_addr: InitiatorAddr, payment: Option, @@ -48,14 +50,19 @@ impl ExecuteRequestBuilder { /// The default value used for `WasmV1Request::entry_point`. pub const DEFAULT_ENTRY_POINT: &'static str = "call"; - /// Converts a `Transaction` into an `ExecuteRequestBuilder`. - pub fn from_transaction(txn: &Transaction) -> Self { - let authorization_keys = txn.authorization_keys(); - let session = WasmV1Request::new_session( + /// Converts a `SessionInputData` into an `ExecuteRequestBuilder`. + pub fn from_session_input_data(session_input_data: &SessionInputData) -> Self { + let block_info = BlockInfo::new( Self::DEFAULT_STATE_HASH, BlockTime::new(DEFAULT_BLOCK_TIME), + BlockHash::default(), + 0, + ); + let authorization_keys = session_input_data.signers(); + let session = WasmV1Request::new_session( + block_info, Gas::new(5_000_000_000_000_u64), // TODO - set proper value - txn, + session_input_data, ) .unwrap(); @@ -63,17 +70,22 @@ impl ExecuteRequestBuilder { let payment_gas_limit: Gas; let payment_entry_point: String; let payment_args: RuntimeArgs; - if txn.is_standard_payment() { + if session_input_data.is_standard_payment() { payment = None; payment_gas_limit = Gas::zero(); payment_entry_point = DEFAULT_ENTRY_POINT_NAME.to_string(); payment_args = RuntimeArgs::new(); } else { - let request = WasmV1Request::new_custom_payment( + let block_info = BlockInfo::new( Self::DEFAULT_STATE_HASH, BlockTime::new(DEFAULT_BLOCK_TIME), + BlockHash::default(), + 0, + ); + let request = WasmV1Request::new_custom_payment( + block_info, Gas::new(5_000_000_000_000_u64), // TODO - set proper value - txn, + session_input_data, ) .unwrap(); payment = Some(request.executable_item); @@ -83,8 +95,10 @@ impl ExecuteRequestBuilder { } ExecuteRequestBuilder { - state_hash: session.state_hash, - block_time: session.block_time, + state_hash: session.block_info.state_hash, + block_time: session.block_info.block_time, + block_height: session.block_info.block_height, + parent_block_hash: session.block_info.parent_block_hash, transaction_hash: session.transaction_hash, initiator_addr: session.initiator_addr, payment, @@ -102,9 +116,14 @@ impl ExecuteRequestBuilder { /// Converts a `DeployItem` into an `ExecuteRequestBuilder`. pub fn from_deploy_item(deploy_item: &DeployItem) -> Self { let authorization_keys = deploy_item.authorization_keys.clone(); - let session = WasmV1Request::new_session_from_deploy_item( + let block_info = BlockInfo::new( Self::DEFAULT_STATE_HASH, BlockTime::new(DEFAULT_BLOCK_TIME), + BlockHash::default(), + 0, + ); + let session = WasmV1Request::new_session_from_deploy_item( + block_info, Gas::new(5_000_000_000_000_u64), // TODO - set proper value deploy_item, ) @@ -120,9 +139,14 @@ impl ExecuteRequestBuilder { payment_entry_point = DEFAULT_ENTRY_POINT_NAME.to_string(); payment_args = RuntimeArgs::new(); } else { - let request = WasmV1Request::new_custom_payment_from_deploy_item( + let block_info = BlockInfo::new( Self::DEFAULT_STATE_HASH, BlockTime::new(DEFAULT_BLOCK_TIME), + BlockHash::default(), + 0, + ); + let request = WasmV1Request::new_custom_payment_from_deploy_item( + block_info, Gas::new(5_000_000_000_000_u64), // TODO - set proper value deploy_item, ) @@ -134,8 +158,10 @@ impl ExecuteRequestBuilder { } ExecuteRequestBuilder { - state_hash: session.state_hash, - block_time: session.block_time, + state_hash: session.block_info.state_hash, + block_time: session.block_info.block_time, + block_height: session.block_info.block_height, + parent_block_hash: session.block_info.parent_block_hash, transaction_hash: session.transaction_hash, initiator_addr: session.initiator_addr, payment, @@ -265,6 +291,24 @@ impl ExecuteRequestBuilder { self } + /// Sets the block height of the [`WasmV1Request`]s. + pub fn with_block_height(mut self, block_height: u64) -> Self { + self.block_height = block_height; + self + } + + /// Sets the parent block hash of the [`WasmV1Request`]s. + pub fn with_parent_block_hash(mut self, parent_block_hash: BlockHash) -> Self { + self.parent_block_hash = parent_block_hash; + self + } + + /// Sets the parent block hash of the [`WasmV1Request`]s. + pub fn with_state_hash(mut self, state_hash: Digest) -> Self { + self.state_hash = state_hash; + self + } + /// Sets the authorization keys used by the [`WasmV1Request`]s. pub fn with_authorization_keys(mut self, authorization_keys: BTreeSet) -> Self { self.authorization_keys = authorization_keys; @@ -276,6 +320,8 @@ impl ExecuteRequestBuilder { let ExecuteRequestBuilder { state_hash, block_time, + block_height, + parent_block_hash, transaction_hash, initiator_addr, payment, @@ -289,9 +335,9 @@ impl ExecuteRequestBuilder { authorization_keys, } = self; + let block_info = BlockInfo::new(state_hash, block_time, parent_block_hash, block_height); let maybe_custom_payment = payment.map(|executable_item| WasmV1Request { - state_hash, - block_time, + block_info, transaction_hash, gas_limit: payment_gas_limit, initiator_addr: initiator_addr.clone(), @@ -303,8 +349,7 @@ impl ExecuteRequestBuilder { }); let session = WasmV1Request { - state_hash, - block_time, + block_info, transaction_hash, gas_limit: session_gas_limit, initiator_addr, diff --git a/execution_engine_testing/test_support/src/lib.rs b/execution_engine_testing/test_support/src/lib.rs index 90247c256c..57db63cca4 100644 --- a/execution_engine_testing/test_support/src/lib.rs +++ b/execution_engine_testing/test_support/src/lib.rs @@ -24,7 +24,7 @@ use casper_storage::data_access_layer::GenesisRequest; use casper_types::{ account::AccountHash, testing::TestRng, ChainspecRegistry, Digest, GenesisAccount, GenesisConfig, GenesisConfigBuilder, Motes, ProtocolVersion, PublicKey, SecretKey, - SystemConfig, WasmConfig, U512, + StorageCosts, SystemConfig, WasmConfig, WasmV1Config, U512, }; pub use chainspec_config::{ChainspecConfig, CHAINSPEC_SYMLINK}; @@ -138,11 +138,15 @@ pub static DEFAULT_ACCOUNTS: Lazy> = Lazy::new(|| { /// Default [`ProtocolVersion`]. pub const DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::V2_0_0; /// Default payment. -pub static DEFAULT_PAYMENT: Lazy = Lazy::new(|| U512::from(2_500_000_000_000u64)); +pub static DEFAULT_PAYMENT: Lazy = Lazy::new(|| U512::from(10_000_000_000_000u64)); /// Default [`WasmConfig`]. pub static DEFAULT_WASM_CONFIG: Lazy = Lazy::new(WasmConfig::default); +/// Default [`WasmV1Config`]. +pub static DEFAULT_WASM_V1_CONFIG: Lazy = Lazy::new(WasmV1Config::default); /// Default [`SystemConfig`]. pub static DEFAULT_SYSTEM_CONFIG: Lazy = Lazy::new(SystemConfig::default); +/// Default [`StorageConfig`]. +pub static DEFAULT_STORAGE_COSTS: Lazy = Lazy::new(StorageCosts::default); /// Default [`GenesisConfig`]. pub static DEFAULT_EXEC_CONFIG: Lazy = Lazy::new(|| { @@ -156,6 +160,7 @@ pub static DEFAULT_EXEC_CONFIG: Lazy = Lazy::new(|| { .with_round_seigniorage_rate(DEFAULT_ROUND_SEIGNIORAGE_RATE) .with_unbonding_delay(DEFAULT_UNBONDING_DELAY) .with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS) + .with_storage_costs(*DEFAULT_STORAGE_COSTS) .build() }); diff --git a/execution_engine_testing/test_support/src/utils.rs b/execution_engine_testing/test_support/src/utils.rs index 87b6031e43..e369cc8d0f 100644 --- a/execution_engine_testing/test_support/src/utils.rs +++ b/execution_engine_testing/test_support/src/utils.rs @@ -15,7 +15,7 @@ use super::{DEFAULT_ROUND_SEIGNIORAGE_RATE, DEFAULT_SYSTEM_CONFIG, DEFAULT_UNBON use crate::{ DEFAULT_AUCTION_DELAY, DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_GENESIS_TIMESTAMP_MILLIS, DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS, DEFAULT_PROTOCOL_VERSION, - DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG, + DEFAULT_STORAGE_COSTS, DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG, }; static RUST_WORKSPACE_PATH: Lazy = Lazy::new(|| { @@ -133,6 +133,7 @@ pub fn create_genesis_config(accounts: Vec) -> GenesisConfig { let round_seigniorage_rate = DEFAULT_ROUND_SEIGNIORAGE_RATE; let unbonding_delay = DEFAULT_UNBONDING_DELAY; let genesis_timestamp_millis = DEFAULT_GENESIS_TIMESTAMP_MILLIS; + let storage_costs = *DEFAULT_STORAGE_COSTS; GenesisConfigBuilder::default() .with_accounts(accounts) @@ -144,6 +145,7 @@ pub fn create_genesis_config(accounts: Vec) -> GenesisConfig { .with_round_seigniorage_rate(round_seigniorage_rate) .with_unbonding_delay(unbonding_delay) .with_genesis_timestamp_millis(genesis_timestamp_millis) + .with_storage_costs(storage_costs) .build() } diff --git a/execution_engine_testing/test_support/src/wasm_test_builder.rs b/execution_engine_testing/test_support/src/wasm_test_builder.rs index 048481f36b..239f691bf8 100644 --- a/execution_engine_testing/test_support/src/wasm_test_builder.rs +++ b/execution_engine_testing/test_support/src/wasm_test_builder.rs @@ -185,7 +185,8 @@ impl WasmTestBuilder { .as_ref() .expect("scratch state should exist"); - exec_request.state_hash = self.post_state_hash.expect("expected post_state_hash"); + let state_hash = self.post_state_hash.expect("expected post_state_hash"); + exec_request.block_info.with_state_hash(state_hash); // First execute the request against our scratch global state. let execution_result = self.execution_engine.execute(cached_state, exec_request); @@ -828,7 +829,8 @@ where /// If the custom payment is `Some` and its execution fails, the session request is not /// attempted. pub fn exec_wasm_v1(&mut self, mut request: WasmV1Request) -> &mut Self { - request.state_hash = self.post_state_hash.expect("expected post_state_hash"); + let state_hash = self.post_state_hash.expect("expected post_state_hash"); + request.block_info.with_state_hash(state_hash); let result = self .execution_engine .execute(self.data_access_layer.as_ref(), request); @@ -842,7 +844,8 @@ where pub fn exec(&mut self, mut exec_request: ExecuteRequest) -> &mut Self { let mut effects = Effects::new(); if let Some(mut payment) = exec_request.custom_payment { - payment.state_hash = self.post_state_hash.expect("expected post_state_hash"); + let state_hash = self.post_state_hash.expect("expected post_state_hash"); + payment.block_info.with_state_hash(state_hash); let payment_result = self .execution_engine .execute(self.data_access_layer.as_ref(), payment); @@ -856,7 +859,8 @@ where return self; } } - exec_request.session.state_hash = self.post_state_hash.expect("expected post_state_hash"); + let state_hash = self.post_state_hash.expect("expected post_state_hash"); + exec_request.session.block_info.with_state_hash(state_hash); let session_result = self .execution_engine @@ -1922,7 +1926,7 @@ where let req = TrieRequest::new(state_hash, None); self.data_access_layer() .trie(req) - .into_legacy() + .into_raw() .unwrap() .map(|bytes| bytesrepr::deserialize(bytes.into_inner().into()).unwrap()) } diff --git a/execution_engine_testing/tests/Cargo.toml b/execution_engine_testing/tests/Cargo.toml index 5598ee5a4e..78c937b2d7 100644 --- a/execution_engine_testing/tests/Cargo.toml +++ b/execution_engine_testing/tests/Cargo.toml @@ -19,6 +19,7 @@ serde = "1" serde_json = "1" tempfile = "3.4.0" wabt = "0.10.0" +wasmprinter = "0.219.0" walrus = "0.20.2" [dev-dependencies] diff --git a/execution_engine_testing/tests/src/test/check_transfer_success.rs b/execution_engine_testing/tests/src/test/check_transfer_success.rs index e865691ab9..d208e45297 100644 --- a/execution_engine_testing/tests/src/test/check_transfer_success.rs +++ b/execution_engine_testing/tests/src/test/check_transfer_success.rs @@ -72,8 +72,10 @@ fn test_check_transfer_success_with_source_only() { let transaction_fee = builder.get_proposer_purse_balance() - proposer_starting_balance; let expected_source_ending_balance = Motes::new(DEFAULT_ACCOUNT_INITIAL_BALANCE) - - Motes::new(transfer_amount) - - Motes::new(transaction_fee); + .checked_sub(Motes::new(transfer_amount)) + .unwrap() + .checked_sub(Motes::new(transaction_fee)) + .unwrap(); let actual_source_ending_balance = Motes::new(builder.get_purse_balance(source_purse)); assert_eq!(expected_source_ending_balance, actual_source_ending_balance); @@ -133,8 +135,10 @@ fn test_check_transfer_success_with_source_only_errors() { let transaction_fee = builder.get_proposer_purse_balance() - proposer_starting_balance; let expected_source_ending_balance = Motes::new(DEFAULT_ACCOUNT_INITIAL_BALANCE) - - Motes::new(transfer_amount) - - Motes::new(transaction_fee); + .checked_sub(Motes::new(transfer_amount)) + .unwrap() + .checked_sub(Motes::new(transaction_fee)) + .unwrap(); let actual_source_ending_balance = Motes::new(builder.get_purse_balance(source_purse)); assert!(expected_source_ending_balance != actual_source_ending_balance); @@ -191,8 +195,10 @@ fn test_check_transfer_success_with_source_and_target() { let transaction_fee = builder.get_proposer_purse_balance() - proposer_starting_balance; let expected_source_ending_balance = Motes::new(DEFAULT_ACCOUNT_INITIAL_BALANCE) - - Motes::new(transfer_amount) - - Motes::new(transaction_fee); + .checked_sub(Motes::new(transfer_amount)) + .unwrap() + .checked_sub(Motes::new(transaction_fee)) + .unwrap(); let actual_source_ending_balance = Motes::new(builder.get_purse_balance(source_purse)); assert_eq!(expected_source_ending_balance, actual_source_ending_balance); diff --git a/execution_engine_testing/tests/src/test/contract_api/add_contract_version.rs b/execution_engine_testing/tests/src/test/contract_api/add_contract_version.rs index 3ac68aa59d..3da72f847f 100644 --- a/execution_engine_testing/tests/src/test/contract_api/add_contract_version.rs +++ b/execution_engine_testing/tests/src/test/contract_api/add_contract_version.rs @@ -2,16 +2,23 @@ use casper_engine_test_support::{ utils, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_SECRET_KEY, LOCAL_GENESIS_REQUEST, }; -use casper_execution_engine::{engine_state::Error as StateError, execution::ExecError}; +use casper_execution_engine::{ + engine_state::{Error as StateError, SessionDataDeploy, SessionDataV1, SessionInputData}, + execution::ExecError, +}; use casper_types::{ - ApiError, BlockTime, RuntimeArgs, Transaction, TransactionLane, TransactionRuntime, - TransactionV1Builder, + ApiError, BlockTime, InitiatorAddr, Phase, PricingMode, RuntimeArgs, Transaction, + TransactionEntryPoint, TransactionTarget, TransactionV1Builder, }; const CONTRACT: &str = "do_nothing_stored.wasm"; const CHAIN_NAME: &str = "a"; const BLOCK_TIME: BlockTime = BlockTime::new(10); +pub(crate) const ARGS_MAP_KEY: u16 = 0; +pub(crate) const TARGET_MAP_KEY: u16 = 1; +pub(crate) const ENTRY_POINT_MAP_KEY: u16 = 2; + #[ignore] #[test] fn should_allow_add_contract_version_via_deploy() { @@ -25,24 +32,65 @@ fn should_allow_add_contract_version_via_deploy() { builder.exec(deploy_request).expect_success().commit(); } -fn try_add_contract_version(kind: TransactionLane, should_succeed: bool) { +fn try_add_contract_version(is_install_upgrade: bool, should_succeed: bool) { let mut builder = LmdbWasmTestBuilder::default(); builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()).commit(); let module_bytes = utils::read_wasm_file(CONTRACT); let txn = Transaction::from( - TransactionV1Builder::new_session(kind, module_bytes, TransactionRuntime::VmCasperV1) - .with_secret_key(&DEFAULT_ACCOUNT_SECRET_KEY) - .with_chain_name(CHAIN_NAME) - .build() - .unwrap(), + TransactionV1Builder::new_session( + is_install_upgrade, + module_bytes, + TransactionRuntime::VmCasperV1, + 0, + None, + ) + .with_secret_key(&DEFAULT_ACCOUNT_SECRET_KEY) + .with_chain_name(CHAIN_NAME) + .build() + .unwrap(), ); - - let txn_request = ExecuteRequestBuilder::from_transaction(&txn) - .with_block_time(BLOCK_TIME) - .build(); - + let txn_request = match txn { + Transaction::Deploy(ref deploy) => { + let initiator_addr = txn.initiator_addr(); + let is_standard_payment = deploy.payment().is_standard_payment(Phase::Payment); + let session_input_data = + to_deploy_session_input_data(is_standard_payment, initiator_addr, &txn); + ExecuteRequestBuilder::from_session_input_data(&session_input_data) + .with_block_time(BLOCK_TIME) + .build() + } + Transaction::V1(ref v1) => { + let initiator_addr = txn.initiator_addr(); + let is_standard_payment = if let PricingMode::Classic { + standard_payment, .. + } = v1.pricing_mode() + { + *standard_payment + } else { + true + }; + let args = v1.deserialize_field::(ARGS_MAP_KEY).unwrap(); + let target = v1 + .deserialize_field::(TARGET_MAP_KEY) + .unwrap(); + let entry_point = v1 + .deserialize_field::(ENTRY_POINT_MAP_KEY) + .unwrap(); + let session_input_data = to_v1_session_input_data( + is_standard_payment, + initiator_addr, + &args, + &target, + &entry_point, + &txn, + ); + ExecuteRequestBuilder::from_session_input_data(&session_input_data) + .with_block_time(BLOCK_TIME) + .build() + } + }; builder.exec(txn_request); if should_succeed { @@ -54,14 +102,69 @@ fn try_add_contract_version(kind: TransactionLane, should_succeed: bool) { } } +fn to_deploy_session_input_data( + is_standard_payment: bool, + initiator_addr: InitiatorAddr, + txn: &Transaction, +) -> SessionInputData<'_> { + match txn { + Transaction::Deploy(deploy) => { + let data = SessionDataDeploy::new( + deploy.hash(), + deploy.session(), + initiator_addr, + txn.signers().clone(), + is_standard_payment, + ); + SessionInputData::DeploySessionData { data } + } + Transaction::V1(_) => { + panic!("unexpected transaction v1"); + } + } +} + +fn to_v1_session_input_data<'a>( + is_standard_payment: bool, + initiator_addr: InitiatorAddr, + args: &'a RuntimeArgs, + target: &'a TransactionTarget, + entry_point: &'a TransactionEntryPoint, + txn: &'a Transaction, +) -> SessionInputData<'a> { + let is_install_upgrade = match target { + TransactionTarget::Session { + is_install_upgrade, .. + } => *is_install_upgrade, + _ => false, + }; + match txn { + Transaction::Deploy(_) => panic!("unexpected deploy transaction"), + Transaction::V1(transaction_v1) => { + let data = SessionDataV1::new( + args, + target, + entry_point, + is_install_upgrade, + transaction_v1.hash(), + transaction_v1.pricing_mode(), + initiator_addr, + txn.signers().clone(), + is_standard_payment, + ); + SessionInputData::SessionDataV1 { data } + } + } +} + #[ignore] #[test] fn should_allow_add_contract_version_via_transaction_v1_installer_upgrader() { - try_add_contract_version(TransactionLane::InstallUpgrade, true) + try_add_contract_version(true, true) } #[ignore] #[test] fn should_disallow_add_contract_version_via_transaction_v1_standard() { - try_add_contract_version(TransactionLane::Large, false) + try_add_contract_version(false, false) } diff --git a/execution_engine_testing/tests/src/test/contract_api/get_block_info.rs b/execution_engine_testing/tests/src/test/contract_api/get_block_info.rs new file mode 100644 index 0000000000..191b927f41 --- /dev/null +++ b/execution_engine_testing/tests/src/test/contract_api/get_block_info.rs @@ -0,0 +1,112 @@ +use casper_engine_test_support::{ + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, LOCAL_GENESIS_REQUEST, +}; +use casper_types::{bytesrepr::ToBytes, runtime_args, BlockHash}; + +const CONTRACT_GET_BLOCKINFO: &str = "get_blockinfo.wasm"; +const ARG_FIELD_IDX: &str = "field_idx"; + +const FIELD_IDX_BLOCK_TIME: u8 = 0; +const ARG_KNOWN_BLOCK_TIME: &str = "known_block_time"; + +#[ignore] +#[test] +fn should_run_get_block_time() { + let block_time: u64 = 42; + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKINFO, + runtime_args! { + ARG_FIELD_IDX => FIELD_IDX_BLOCK_TIME, + ARG_KNOWN_BLOCK_TIME => block_time + }, + ) + .with_block_time(block_time) + .build(); + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec(exec_request) + .commit() + .expect_success(); +} + +const FIELD_IDX_BLOCK_HEIGHT: u8 = 1; +const ARG_KNOWN_BLOCK_HEIGHT: &str = "known_block_height"; + +#[ignore] +#[test] +fn should_run_get_block_height() { + let block_height: u64 = 1; + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKINFO, + runtime_args! { + ARG_FIELD_IDX => FIELD_IDX_BLOCK_HEIGHT, + ARG_KNOWN_BLOCK_HEIGHT => block_height + }, + ) + .with_block_height(block_height) + .build(); + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec(exec_request) + .expect_success() + .commit(); +} + +const FIELD_IDX_PARENT_BLOCK_HASH: u8 = 2; +const ARG_KNOWN_BLOCK_PARENT_HASH: &str = "known_block_parent_hash"; + +#[ignore] +#[test] +fn should_run_get_block_parent_hash() { + let block_hash = BlockHash::default(); + let digest = block_hash.inner(); + let digest_bytes = digest.to_bytes().expect("should serialize"); + let bytes = casper_types::bytesrepr::Bytes::from(digest_bytes); + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKINFO, + runtime_args! { + ARG_FIELD_IDX => FIELD_IDX_PARENT_BLOCK_HASH, + ARG_KNOWN_BLOCK_PARENT_HASH => bytes + }, + ) + .with_parent_block_hash(block_hash) + .build(); + LmdbWasmTestBuilder::default() + .run_genesis(LOCAL_GENESIS_REQUEST.clone()) + .exec(exec_request) + .expect_success() + .commit(); +} + +const FIELD_IDX_STATE_HASH: u8 = 3; +const ARG_KNOWN_STATE_HASH: &str = "known_state_hash"; + +#[ignore] +#[test] +fn should_run_get_state_hash() { + let mut builder = LmdbWasmTestBuilder::default(); + builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()); + + let state_hash = builder.get_post_state_hash(); + let digest_bytes = state_hash.to_bytes().expect("should serialize"); + let bytes = casper_types::bytesrepr::Bytes::from(digest_bytes); + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKINFO, + runtime_args! { + ARG_FIELD_IDX => FIELD_IDX_STATE_HASH, + ARG_KNOWN_STATE_HASH => bytes + }, + ) + .with_state_hash(state_hash) + .build(); + + builder.exec(exec_request).expect_success().commit(); +} diff --git a/execution_engine_testing/tests/src/test/contract_api/mod.rs b/execution_engine_testing/tests/src/test/contract_api/mod.rs index 4b9b90f553..935a4d9cf1 100644 --- a/execution_engine_testing/tests/src/test/contract_api/mod.rs +++ b/execution_engine_testing/tests/src/test/contract_api/mod.rs @@ -3,6 +3,7 @@ mod add_contract_version; mod create_purse; mod dictionary; mod get_arg; +mod get_block_info; mod get_blocktime; mod get_call_stack; mod get_caller; diff --git a/execution_engine_testing/tests/src/test/contract_messages.rs b/execution_engine_testing/tests/src/test/contract_messages.rs index a9b36f02a0..47d8d90132 100644 --- a/execution_engine_testing/tests/src/test/contract_messages.rs +++ b/execution_engine_testing/tests/src/test/contract_messages.rs @@ -10,8 +10,8 @@ use casper_types::{ contract_messages::{MessageChecksum, MessagePayload, MessageTopicSummary, TopicNameHash}, crypto, runtime_args, AddressableEntity, AddressableEntityHash, BlockGlobalAddr, BlockTime, CLValue, CoreConfig, Digest, EntityAddr, HostFunction, HostFunctionCosts, Key, MessageLimits, - OpcodeCosts, RuntimeArgs, StorageCosts, StoredValue, SystemConfig, WasmConfig, - DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + OpcodeCosts, RuntimeArgs, StorageCosts, StoredValue, SystemConfig, WasmConfig, WasmV1Config, + DEFAULT_V1_MAX_STACK_HEIGHT, DEFAULT_V1_WASM_MAX_MEMORY, U512, }; const MESSAGE_EMITTER_INSTALLER_WASM: &str = "contract_messages_emitter.wasm"; @@ -491,23 +491,26 @@ fn should_not_add_duplicate_topics() { #[ignore] #[test] fn should_not_exceed_configured_limits() { - let default_wasm_config = WasmConfig::default(); - let wasm_config = WasmConfig::new( - default_wasm_config.max_memory, - default_wasm_config.max_stack_height, + let default_wasm_config = WasmV1Config::default(); + let wasm_v1_config = WasmV1Config::new( + default_wasm_config.max_memory(), + default_wasm_config.max_stack_height(), default_wasm_config.opcode_costs(), - default_wasm_config.storage_costs(), default_wasm_config.take_host_function_costs(), + ); + let wasm_config = WasmConfig::new( MessageLimits { max_topic_name_size: 32, max_message_size: 100, max_topics_per_contract: 2, }, + wasm_v1_config, ); let chainspec = ChainspecConfig { system_costs_config: SystemConfig::default(), core_config: CoreConfig::default(), wasm_config, + storage_costs: StorageCosts::default(), }; let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config(chainspec)); @@ -659,18 +662,18 @@ fn should_not_emit_messages_from_account() { fn should_charge_expected_gas_for_storage() { const GAS_PER_BYTE_COST: u32 = 100; - let wasm_config = WasmConfig::new( - DEFAULT_WASM_MAX_MEMORY, - DEFAULT_MAX_STACK_HEIGHT, + let wasm_v1_config = WasmV1Config::new( + DEFAULT_V1_WASM_MAX_MEMORY, + DEFAULT_V1_MAX_STACK_HEIGHT, OpcodeCosts::zero(), - StorageCosts::new(GAS_PER_BYTE_COST), HostFunctionCosts::zero(), - MessageLimits::default(), ); + let wasm_config = WasmConfig::new(MessageLimits::default(), wasm_v1_config); let chainspec = ChainspecConfig { wasm_config, core_config: CoreConfig::default(), system_costs_config: SystemConfig::default(), + storage_costs: StorageCosts::new(GAS_PER_BYTE_COST), }; let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config(chainspec)); builder @@ -759,22 +762,22 @@ fn should_charge_increasing_gas_cost_for_multiple_messages_emitted() { const EMIT_MESSAGES_FROM_MULTIPLE_CONTRACTS: u32 = emit_cost_per_execution(EMIT_MESSAGE_FROM_EACH_VERSION_NUM_MESSAGES); - let wasm_config = WasmConfig::new( - DEFAULT_WASM_MAX_MEMORY, - DEFAULT_MAX_STACK_HEIGHT, + let wasm_v1_config = WasmV1Config::new( + DEFAULT_V1_WASM_MAX_MEMORY, + DEFAULT_V1_MAX_STACK_HEIGHT, OpcodeCosts::zero(), - StorageCosts::zero(), HostFunctionCosts { emit_message: HostFunction::fixed(FIRST_MESSAGE_EMIT_COST), cost_increase_per_message: COST_INCREASE_PER_MESSAGE, ..Zero::zero() }, - MessageLimits::default(), ); + let wasm_config = WasmConfig::new(MessageLimits::default(), wasm_v1_config); let chainspec = ChainspecConfig { wasm_config, core_config: CoreConfig::default(), system_costs_config: SystemConfig::default(), + storage_costs: StorageCosts::zero(), }; let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config(chainspec)); @@ -875,23 +878,26 @@ fn should_register_topic_on_contract_creation() { #[ignore] #[test] fn should_not_exceed_configured_topic_name_limits_on_contract_upgrade_no_init() { - let default_wasm_config = WasmConfig::default(); + let default_wasm_v1_config = WasmV1Config::default(); + let wasm_v1_config = WasmV1Config::new( + default_wasm_v1_config.max_memory(), + default_wasm_v1_config.max_stack_height(), + default_wasm_v1_config.opcode_costs(), + default_wasm_v1_config.take_host_function_costs(), + ); let wasm_config = WasmConfig::new( - default_wasm_config.max_memory, - default_wasm_config.max_stack_height, - default_wasm_config.opcode_costs(), - default_wasm_config.storage_costs(), - default_wasm_config.take_host_function_costs(), MessageLimits { max_topic_name_size: 16, //length of MESSAGE_EMITTER_GENERIC_TOPIC max_message_size: 100, max_topics_per_contract: 3, }, + wasm_v1_config, ); let chainspec = ChainspecConfig { wasm_config, core_config: CoreConfig::default(), system_costs_config: SystemConfig::default(), + storage_costs: StorageCosts::default(), }; let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config(chainspec)); @@ -906,24 +912,27 @@ fn should_not_exceed_configured_topic_name_limits_on_contract_upgrade_no_init() #[ignore] #[test] fn should_not_exceed_configured_max_topics_per_contract_upgrade_no_init() { - let default_wasm_config = WasmConfig::default(); - let wasm_config = WasmConfig::new( - default_wasm_config.max_memory, - default_wasm_config.max_stack_height, + let default_wasm_config = WasmV1Config::default(); + let wasm_v1_config = WasmV1Config::new( + default_wasm_config.max_memory(), + default_wasm_config.max_stack_height(), default_wasm_config.opcode_costs(), - default_wasm_config.storage_costs(), default_wasm_config.take_host_function_costs(), + ); + let wasm_config = WasmConfig::new( MessageLimits { max_topic_name_size: 32, max_message_size: 100, max_topics_per_contract: 1, /* only allow 1 topic. Since on upgrade previous * topics carry over, the upgrade should fail. */ }, + wasm_v1_config, ); let chainspec = ChainspecConfig { wasm_config, system_costs_config: SystemConfig::default(), core_config: CoreConfig::default(), + storage_costs: StorageCosts::default(), }; let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config(chainspec)); @@ -1117,12 +1126,10 @@ fn emit_message_should_charge_variable_gas_cost_based_on_topic_and_message_size( const COST_PER_MESSAGE_TOPIC_NAME_SIZE: u32 = 2; const COST_PER_MESSAGE_LENGTH: u32 = 1_000; const MESSAGE_SUFFIX: &str = "test"; - - let wasm_config = WasmConfig::new( - DEFAULT_WASM_MAX_MEMORY, - DEFAULT_MAX_STACK_HEIGHT, + let wasm_v1_config = WasmV1Config::new( + DEFAULT_V1_WASM_MAX_MEMORY, + DEFAULT_V1_MAX_STACK_HEIGHT, OpcodeCosts::zero(), - StorageCosts::zero(), HostFunctionCosts { emit_message: HostFunction::new( MESSAGE_EMIT_COST, @@ -1135,12 +1142,13 @@ fn emit_message_should_charge_variable_gas_cost_based_on_topic_and_message_size( ), ..Zero::zero() }, - MessageLimits::default(), ); + let wasm_config = WasmConfig::new(MessageLimits::default(), wasm_v1_config); let chainspec = ChainspecConfig { wasm_config, core_config: CoreConfig::default(), system_costs_config: SystemConfig::default(), + storage_costs: StorageCosts::zero(), }; let builder = RefCell::new(LmdbWasmTestBuilder::new_temporary_with_config(chainspec)); diff --git a/execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs b/execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs index c232f00066..47830dfd83 100644 --- a/execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs +++ b/execution_engine_testing/tests/src/test/deploy/non_standard_payment.rs @@ -3,9 +3,11 @@ use casper_engine_test_support::{ DEFAULT_PAYMENT, DEFAULT_PROTOCOL_VERSION, LOCAL_GENESIS_REQUEST, MINIMUM_ACCOUNT_CREATION_BALANCE, }; -use casper_execution_engine::engine_state::WasmV1Request; +use casper_execution_engine::engine_state::{BlockInfo, WasmV1Request}; use casper_storage::data_access_layer::BalanceIdentifier; -use casper_types::{account::AccountHash, runtime_args, Digest, Gas, RuntimeArgs, Timestamp, U512}; +use casper_types::{ + account::AccountHash, runtime_args, BlockHash, Digest, Gas, RuntimeArgs, Timestamp, U512, +}; const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); const DO_NOTHING_WASM: &str = "do_nothing.wasm"; @@ -88,12 +90,12 @@ fn should_charge_non_main_purse() { .build(); let block_time = Timestamp::now().millis(); - + let parent_block_hash = BlockHash::default(); + let block_info = BlockInfo::new(Digest::default(), block_time.into(), parent_block_hash, 1); builder .exec_wasm_v1( WasmV1Request::new_custom_payment_from_deploy_item( - Digest::default(), - block_time.into(), + block_info, Gas::from(12_500_000_000_u64), &deploy_item, ) diff --git a/execution_engine_testing/tests/src/test/explorer/faucet.rs b/execution_engine_testing/tests/src/test/explorer/faucet.rs index a2b66a47d8..7d16b9f373 100644 --- a/execution_engine_testing/tests/src/test/explorer/faucet.rs +++ b/execution_engine_testing/tests/src/test/explorer/faucet.rs @@ -660,11 +660,11 @@ fn faucet_costs() { // This test will fail if execution costs vary. The expected costs should not be updated // without understanding why the cost has changed. If the costs do change, it should be // reflected in the "Costs by Entry Point" section of the faucet crate's README.md. - const EXPECTED_FAUCET_INSTALL_COST: u64 = 160_995_706_637; + const EXPECTED_FAUCET_INSTALL_COST: u64 = 142_640_262_074; - const EXPECTED_FAUCET_SET_VARIABLES_COST: u64 = 135_355_310; - const EXPECTED_FAUCET_CALL_BY_INSTALLER_COST: u64 = 2_884_534_947; - const EXPECTED_FAUCET_CALL_BY_USER_COST: u64 = 2_623_238_526; + const EXPECTED_FAUCET_SET_VARIABLES_COST: u64 = 134_259_210; + const EXPECTED_FAUCET_CALL_BY_INSTALLER_COST: u64 = 2_879_594_967; + const EXPECTED_FAUCET_CALL_BY_USER_COST: u64 = 2_615_492_876; let installer_account = AccountHash::new([1u8; 32]); let user_account: AccountHash = AccountHash::new([2u8; 32]); diff --git a/execution_engine_testing/tests/src/test/gas_counter.rs b/execution_engine_testing/tests/src/test/gas_counter.rs index 605505c417..d10326ff60 100644 --- a/execution_engine_testing/tests/src/test/gas_counter.rs +++ b/execution_engine_testing/tests/src/test/gas_counter.rs @@ -6,10 +6,12 @@ use casper_wasm::{ use casper_engine_test_support::{ DeployItemBuilder, ExecuteRequestBuilder, LmdbWasmTestBuilder, ARG_AMOUNT, - DEFAULT_ACCOUNT_ADDR, DEFAULT_PAYMENT, DEFAULT_WASM_CONFIG, LOCAL_GENESIS_REQUEST, + DEFAULT_ACCOUNT_ADDR, DEFAULT_PAYMENT, LOCAL_GENESIS_REQUEST, }; use casper_execution_engine::{engine_state::Error, runtime::PreprocessingError}; -use casper_types::{addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, Gas, RuntimeArgs}; +use casper_types::{ + addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, Gas, OpcodeCosts, RuntimeArgs, +}; use crate::test::regression::test_utils::make_gas_counter_overflow; @@ -71,7 +73,7 @@ fn should_fail_to_overflow_gas_counter() { #[ignore] #[test] fn should_correctly_measure_gas_for_opcodes() { - let opcode_costs = DEFAULT_WASM_CONFIG.opcode_costs(); + let opcode_costs = OpcodeCosts::default(); const GROW_PAGES: u32 = 1; @@ -163,7 +165,12 @@ fn should_correctly_measure_gas_for_opcodes() { builder.exec(exec_request).commit().expect_success(); let gas_cost = builder.last_exec_gas_cost(); - let expected_cost = accounted_opcodes.clone().into_iter().map(Gas::from).sum(); + let expected_cost = accounted_opcodes + .clone() + .into_iter() + .map(Gas::from) + .try_fold(Gas::default(), |acc, cost| acc.checked_add(cost)) + .expect("should add gas costs"); assert_eq!( gas_cost, expected_cost, "accounted costs {:?}", diff --git a/execution_engine_testing/tests/src/test/private_chain.rs b/execution_engine_testing/tests/src/test/private_chain.rs index 8a202fd069..9eab9268a0 100644 --- a/execution_engine_testing/tests/src/test/private_chain.rs +++ b/execution_engine_testing/tests/src/test/private_chain.rs @@ -9,8 +9,8 @@ use casper_engine_test_support::{ ChainspecConfig, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_INITIAL_BALANCE, DEFAULT_AUCTION_DELAY, DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_GENESIS_TIMESTAMP_MILLIS, DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS, DEFAULT_PROPOSER_PUBLIC_KEY, DEFAULT_PROTOCOL_VERSION, - DEFAULT_ROUND_SEIGNIORAGE_RATE, DEFAULT_SYSTEM_CONFIG, DEFAULT_UNBONDING_DELAY, - DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG, + DEFAULT_ROUND_SEIGNIORAGE_RATE, DEFAULT_STORAGE_COSTS, DEFAULT_SYSTEM_CONFIG, + DEFAULT_UNBONDING_DELAY, DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG, }; use num_rational::Ratio; use once_cell::sync::Lazy; @@ -20,7 +20,8 @@ use casper_types::{ account::AccountHash, system::auction::DELEGATION_RATE_DENOMINATOR, AdministratorAccount, CoreConfig, FeeHandling, GenesisAccount, GenesisConfig, GenesisConfigBuilder, GenesisValidator, HostFunction, HostFunctionCosts, MessageLimits, Motes, OpcodeCosts, PublicKey, RefundHandling, - SecretKey, StorageCosts, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + SecretKey, StorageCosts, WasmConfig, WasmV1Config, DEFAULT_V1_MAX_STACK_HEIGHT, + DEFAULT_V1_WASM_MAX_MEMORY, U512, }; use tempfile::TempDir; @@ -144,6 +145,7 @@ static DEFUALT_PRIVATE_CHAIN_EXEC_CONFIG: Lazy = Lazy::new(|| { .with_round_seigniorage_rate(DEFAULT_ROUND_SEIGNIORAGE_RATE) .with_unbonding_delay(DEFAULT_UNBONDING_DELAY) .with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS) + .with_storage_costs(*DEFAULT_STORAGE_COSTS) .build() }); @@ -194,14 +196,13 @@ fn make_wasm_config() -> WasmConfig { transfer_from_purse_to_account: HostFunction::fixed(0), ..HostFunctionCosts::default() }; - WasmConfig::new( - DEFAULT_WASM_MAX_MEMORY, - DEFAULT_MAX_STACK_HEIGHT, + let wasm_v1_config = WasmV1Config::new( + DEFAULT_V1_WASM_MAX_MEMORY, + DEFAULT_V1_MAX_STACK_HEIGHT, OpcodeCosts::default(), - StorageCosts::default(), host_functions, - MessageLimits::default(), - ) + ); + WasmConfig::new(MessageLimits::default(), wasm_v1_config) } fn make_private_chain_config( @@ -222,10 +223,12 @@ fn make_private_chain_config( ..Default::default() }; let wasm_config = make_wasm_config(); + let storage_costs = StorageCosts::default(); ChainspecConfig { core_config, wasm_config, system_costs_config: Default::default(), + storage_costs, } } diff --git a/execution_engine_testing/tests/src/test/private_chain/management.rs b/execution_engine_testing/tests/src/test/private_chain/management.rs index 899510dd66..f6a2dd648c 100644 --- a/execution_engine_testing/tests/src/test/private_chain/management.rs +++ b/execution_engine_testing/tests/src/test/private_chain/management.rs @@ -3,8 +3,8 @@ use casper_engine_test_support::{ TransferRequestBuilder, DEFAULT_AUCTION_DELAY, DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_GENESIS_TIMESTAMP_MILLIS, DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS, DEFAULT_PAYMENT, DEFAULT_PROTOCOL_VERSION, - DEFAULT_ROUND_SEIGNIORAGE_RATE, DEFAULT_SYSTEM_CONFIG, DEFAULT_UNBONDING_DELAY, - DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG, + DEFAULT_ROUND_SEIGNIORAGE_RATE, DEFAULT_STORAGE_COSTS, DEFAULT_SYSTEM_CONFIG, + DEFAULT_UNBONDING_DELAY, DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG, }; use casper_execution_engine::{engine_state::Error, execution::ExecError}; use casper_storage::{data_access_layer::GenesisRequest, tracking_copy::TrackingCopyError}; @@ -74,6 +74,7 @@ fn should_not_run_genesis_with_duplicated_administrator_accounts() { core_config, wasm_config: Default::default(), system_costs_config: Default::default(), + storage_costs: Default::default(), }; let data_dir = TempDir::new().expect("should create temp dir"); @@ -100,6 +101,7 @@ fn should_not_run_genesis_with_duplicated_administrator_accounts() { .with_round_seigniorage_rate(DEFAULT_ROUND_SEIGNIORAGE_RATE) .with_unbonding_delay(DEFAULT_UNBONDING_DELAY) .with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS) + .with_storage_costs(*DEFAULT_STORAGE_COSTS) .build(); let modified_genesis_request = GenesisRequest::new( diff --git a/execution_engine_testing/tests/src/test/regression/ee_598.rs b/execution_engine_testing/tests/src/test/regression/ee_598.rs index ef42fa1663..ae5501d3c3 100644 --- a/execution_engine_testing/tests/src/test/regression/ee_598.rs +++ b/execution_engine_testing/tests/src/test/regression/ee_598.rs @@ -25,7 +25,7 @@ static ACCOUNT_1_PK: Lazy = Lazy::new(|| { const GENESIS_VALIDATOR_STAKE: u64 = 50_000; static ACCOUNT_1_ADDR: Lazy = Lazy::new(|| AccountHash::from(&*ACCOUNT_1_PK)); -static ACCOUNT_1_FUND: Lazy = Lazy::new(|| U512::from(1_500_000_000_000u64)); +static ACCOUNT_1_FUND: Lazy = Lazy::new(|| U512::from(10_000_000_000_000u64)); static ACCOUNT_1_BALANCE: Lazy = Lazy::new(|| *ACCOUNT_1_FUND + 100_000); static ACCOUNT_1_BOND: Lazy = Lazy::new(|| U512::from(25_000)); @@ -38,7 +38,9 @@ fn should_handle_unbond_for_more_than_stake_as_full_unbond_of_stake_ee_598_regre let mut tmp: Vec = DEFAULT_ACCOUNTS.clone(); let account = GenesisAccount::account( public_key, - Motes::new(GENESIS_VALIDATOR_STAKE) * Motes::new(2), + Motes::new(GENESIS_VALIDATOR_STAKE) + .checked_mul(Motes::new(2)) + .unwrap(), Some(GenesisValidator::new( Motes::new(GENESIS_VALIDATOR_STAKE), DelegationRate::zero(), diff --git a/execution_engine_testing/tests/src/test/regression/ee_966.rs b/execution_engine_testing/tests/src/test/regression/ee_966.rs index ee472bf628..4f025485a0 100644 --- a/execution_engine_testing/tests/src/test/regression/ee_966.rs +++ b/execution_engine_testing/tests/src/test/regression/ee_966.rs @@ -10,8 +10,8 @@ use casper_engine_test_support::{ use casper_execution_engine::{engine_state::Error, execution::ExecError}; use casper_types::{ addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, ApiError, EraId, HostFunctionCosts, - MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, StorageCosts, WasmConfig, - DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, + MessageLimits, OpcodeCosts, ProtocolVersion, RuntimeArgs, WasmConfig, WasmV1Config, + DEFAULT_V1_MAX_STACK_HEIGHT, DEFAULT_V1_WASM_MAX_MEMORY, }; const CONTRACT_EE_966_REGRESSION: &str = "ee_966_regression.wasm"; @@ -19,14 +19,13 @@ const MINIMUM_INITIAL_MEMORY: u32 = 16; const DEFAULT_ACTIVATION_POINT: EraId = EraId::new(0); static DOUBLED_WASM_MEMORY_LIMIT: Lazy = Lazy::new(|| { - WasmConfig::new( - DEFAULT_WASM_MAX_MEMORY * 2, - DEFAULT_MAX_STACK_HEIGHT, + let wasm_v1_config = WasmV1Config::new( + DEFAULT_V1_WASM_MAX_MEMORY * 2, + DEFAULT_V1_MAX_STACK_HEIGHT, OpcodeCosts::default(), - StorageCosts::default(), HostFunctionCosts::default(), - MessageLimits::default(), - ) + ); + WasmConfig::new(MessageLimits::default(), wasm_v1_config) }); const NEW_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::from_parts( DEFAULT_PROTOCOL_VERSION.value().major, @@ -89,7 +88,7 @@ fn should_run_ee_966_with_zero_min_and_zero_max_memory() { #[ignore] #[test] fn should_run_ee_966_cant_have_too_much_initial_memory() { - let session_code = make_session_code_with_memory_pages(DEFAULT_WASM_MAX_MEMORY + 1, None); + let session_code = make_session_code_with_memory_pages(DEFAULT_V1_WASM_MAX_MEMORY + 1, None); let exec_request = make_request_with_session_bytes(session_code); @@ -109,8 +108,10 @@ fn should_run_ee_966_cant_have_too_much_initial_memory() { #[ignore] #[test] fn should_run_ee_966_should_request_exactly_maximum() { - let session_code = - make_session_code_with_memory_pages(DEFAULT_WASM_MAX_MEMORY, Some(DEFAULT_WASM_MAX_MEMORY)); + let session_code = make_session_code_with_memory_pages( + DEFAULT_V1_WASM_MAX_MEMORY, + Some(DEFAULT_V1_WASM_MAX_MEMORY), + ); let exec_request = make_request_with_session_bytes(session_code); @@ -124,7 +125,7 @@ fn should_run_ee_966_should_request_exactly_maximum() { #[ignore] #[test] fn should_run_ee_966_should_request_exactly_maximum_as_initial() { - let session_code = make_session_code_with_memory_pages(DEFAULT_WASM_MAX_MEMORY, None); + let session_code = make_session_code_with_memory_pages(DEFAULT_V1_WASM_MAX_MEMORY, None); let exec_request = make_request_with_session_bytes(session_code); @@ -140,7 +141,7 @@ fn should_run_ee_966_should_request_exactly_maximum_as_initial() { fn should_run_ee_966_cant_have_too_much_max_memory() { let session_code = make_session_code_with_memory_pages( MINIMUM_INITIAL_MEMORY, - Some(DEFAULT_WASM_MAX_MEMORY + 1), + Some(DEFAULT_V1_WASM_MAX_MEMORY + 1), ); let exec_request = make_request_with_session_bytes(session_code); @@ -163,7 +164,7 @@ fn should_run_ee_966_cant_have_too_much_max_memory() { fn should_run_ee_966_cant_have_way_too_much_max_memory() { let session_code = make_session_code_with_memory_pages( MINIMUM_INITIAL_MEMORY, - Some(DEFAULT_WASM_MAX_MEMORY + 42), + Some(DEFAULT_V1_WASM_MAX_MEMORY + 42), ); let exec_request = make_request_with_session_bytes(session_code); @@ -184,8 +185,10 @@ fn should_run_ee_966_cant_have_way_too_much_max_memory() { #[ignore] #[test] fn should_run_ee_966_cant_have_larger_initial_than_max_memory() { - let session_code = - make_session_code_with_memory_pages(DEFAULT_WASM_MAX_MEMORY, Some(MINIMUM_INITIAL_MEMORY)); + let session_code = make_session_code_with_memory_pages( + DEFAULT_V1_WASM_MAX_MEMORY, + Some(MINIMUM_INITIAL_MEMORY), + ); let exec_request = make_request_with_session_bytes(session_code); diff --git a/execution_engine_testing/tests/src/test/regression/gh_1902.rs b/execution_engine_testing/tests/src/test/regression/gh_1902.rs index 51b7111bf5..0f0c928dea 100644 --- a/execution_engine_testing/tests/src/test/regression/gh_1902.rs +++ b/execution_engine_testing/tests/src/test/regression/gh_1902.rs @@ -73,7 +73,7 @@ fn should_not_charge_for_create_purse_in_first_time_bond() { .standard_payment_costs() .pay; - let add_bid_payment_amount = U512::from(add_bid_cost + pay_cost) * 2; + let add_bid_payment_amount = U512::from(add_bid_cost + pay_cost as u64) * 2; let sender = *DEFAULT_ACCOUNT_ADDR; let contract_hash = builder.get_auction_contract_hash(); diff --git a/execution_engine_testing/tests/src/test/regression/gh_2280.rs b/execution_engine_testing/tests/src/test/regression/gh_2280.rs index 77003f52b1..d0e6f80b8f 100644 --- a/execution_engine_testing/tests/src/test/regression/gh_2280.rs +++ b/execution_engine_testing/tests/src/test/regression/gh_2280.rs @@ -8,8 +8,8 @@ use casper_engine_test_support::{ use casper_types::{ account::AccountHash, runtime_args, system::mint, AddressableEntityHash, EraId, Gas, HostFunction, HostFunctionCost, HostFunctionCosts, Key, MintCosts, Motes, - ProtocolUpgradeConfig, ProtocolVersion, PublicKey, SecretKey, WasmConfig, - DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + ProtocolUpgradeConfig, ProtocolVersion, PublicKey, SecretKey, WasmConfig, WasmV1Config, + DEFAULT_V1_MAX_STACK_HEIGHT, DEFAULT_V1_WASM_MAX_MEMORY, U512, }; const TRANSFER_TO_ACCOUNT_CONTRACT: &str = "transfer_to_account.wasm"; @@ -217,7 +217,11 @@ fn gh_2280_create_purse_should_always_cost_the_same_gas() { // Increase "transfer_to_account" host function call exactly by X, so we can assert that // transfer cost increased by exactly X without hidden fees. - let host_function_costs = builder.chainspec().wasm_config.take_host_function_costs(); + let host_function_costs = builder + .chainspec() + .wasm_config + .v1() + .take_host_function_costs(); let default_create_purse_cost = host_function_costs.create_purse.cost(); let new_create_purse_cost = default_create_purse_cost @@ -662,14 +666,13 @@ fn make_wasm_config( new_host_function_costs: HostFunctionCosts, old_wasm_config: WasmConfig, ) -> WasmConfig { - WasmConfig::new( - DEFAULT_WASM_MAX_MEMORY, - DEFAULT_MAX_STACK_HEIGHT, - old_wasm_config.opcode_costs(), - old_wasm_config.storage_costs(), + let wasm_v1_config = WasmV1Config::new( + DEFAULT_V1_WASM_MAX_MEMORY, + DEFAULT_V1_MAX_STACK_HEIGHT, + old_wasm_config.v1().opcode_costs(), new_host_function_costs, - old_wasm_config.messages_limits(), - ) + ); + WasmConfig::new(old_wasm_config.messages_limits(), wasm_v1_config) } fn make_upgrade_request() -> ProtocolUpgradeConfig { diff --git a/execution_engine_testing/tests/src/test/regression/gh_4898.rs b/execution_engine_testing/tests/src/test/regression/gh_4898.rs new file mode 100644 index 0000000000..cd6be1e7c8 --- /dev/null +++ b/execution_engine_testing/tests/src/test/regression/gh_4898.rs @@ -0,0 +1,30 @@ +use casper_engine_test_support::{ + utils, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, LOCAL_GENESIS_REQUEST, +}; + +use casper_types::runtime_args; + +const ARG_DATA: &str = "data"; +const GH_4898_REGRESSION_WASM: &str = "gh_4898_regression.wasm"; + +#[ignore] +#[test] +fn should_not_contain_f64_opcodes() { + let module_bytes = utils::read_wasm_file(GH_4898_REGRESSION_WASM); + let wat = wasmprinter::print_bytes(module_bytes).expect("WASM parse error"); + assert!(!wat.contains("f64."), "WASM contains f64 opcodes"); + + let mut builder = LmdbWasmTestBuilder::default(); + builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()); + + let exec_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + GH_4898_REGRESSION_WASM, + runtime_args! { + ARG_DATA => "account-hash-2c4a11c062a8a337bfc97e27fd66291caeb2c65865dcb5d3ef3759c4c97efecb" + }, + ) + .build(); + + builder.exec(exec_request).commit(); +} diff --git a/execution_engine_testing/tests/src/test/regression/gov_427.rs b/execution_engine_testing/tests/src/test/regression/gov_427.rs index c343d84468..80f86f0fc0 100644 --- a/execution_engine_testing/tests/src/test/regression/gov_427.rs +++ b/execution_engine_testing/tests/src/test/regression/gov_427.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::{ - ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_WASM_CONFIG, + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_WASM_V1_CONFIG, LOCAL_GENESIS_REQUEST, }; use casper_execution_engine::{engine_state::Error, execution::ExecError}; @@ -63,7 +63,7 @@ fn too_many_locals_should_exceed_stack_height() { const CALL_COST: usize = 1; let extra_types = [ValType::I32]; let repeat_pattern = [ValType::I64]; - let max_stack_height = DEFAULT_WASM_CONFIG.max_stack_height as usize; + let max_stack_height = DEFAULT_WASM_V1_CONFIG.max_stack_height() as usize; let success_wasm_bytes: Vec = make_arbitrary_local_count( max_stack_height - extra_types.len() - CALL_COST - 1, diff --git a/execution_engine_testing/tests/src/test/regression/gov_74.rs b/execution_engine_testing/tests/src/test/regression/gov_74.rs index d45d2a8bdc..6efd99f023 100644 --- a/execution_engine_testing/tests/src/test/regression/gov_74.rs +++ b/execution_engine_testing/tests/src/test/regression/gov_74.rs @@ -7,7 +7,7 @@ use casper_execution_engine::{ execution::ExecError, runtime::{PreprocessingError, WasmValidationError, DEFAULT_MAX_PARAMETER_COUNT}, }; -use casper_types::{EraId, ProtocolVersion, RuntimeArgs, WasmConfig}; +use casper_types::{EraId, ProtocolVersion, RuntimeArgs, WasmV1Config}; use crate::wasm_utils; @@ -79,7 +79,7 @@ fn should_pass_max_parameter_count() { fn should_observe_stack_height_limit() { let mut builder = initialize_builder(); - assert!(WasmConfig::default().max_stack_height > NEW_WASM_STACK_HEIGHT); + assert!(WasmV1Config::default().max_stack_height() > NEW_WASM_STACK_HEIGHT); // This runs out of the interpreter stack limit let exec_request_1 = { diff --git a/execution_engine_testing/tests/src/test/regression/host_function_metrics_size_and_gas_cost.rs b/execution_engine_testing/tests/src/test/regression/host_function_metrics_size_and_gas_cost.rs index 1e65c06472..02db172092 100644 --- a/execution_engine_testing/tests/src/test/regression/host_function_metrics_size_and_gas_cost.rs +++ b/execution_engine_testing/tests/src/test/regression/host_function_metrics_size_and_gas_cost.rs @@ -16,7 +16,7 @@ const CONTRACT_TRANSFER_TO_ACCOUNT_U512: &str = "transfer_to_account_u512.wasm"; // This value is not systemic, as code is added the size of WASM will increase, // you can change this value to reflect the increase in WASM size. -const HOST_FUNCTION_METRICS_STANDARD_SIZE: usize = 122_154; +const HOST_FUNCTION_METRICS_STANDARD_SIZE: usize = 132_461; const HOST_FUNCTION_METRICS_STANDARD_GAS_COST: u64 = 422_402_224_490; /// Acceptable size regression/improvement in percentage. diff --git a/execution_engine_testing/tests/src/test/regression/mod.rs b/execution_engine_testing/tests/src/test/regression/mod.rs index 6cc2c98759..52cc2dbfb1 100644 --- a/execution_engine_testing/tests/src/test/regression/mod.rs +++ b/execution_engine_testing/tests/src/test/regression/mod.rs @@ -38,6 +38,7 @@ mod gh_2280; mod gh_3097; mod gh_3208; mod gh_3710; +mod gh_4898; mod gov_116; mod gov_42; mod gov_427; diff --git a/execution_engine_testing/tests/src/test/regression/regression_20240105.rs b/execution_engine_testing/tests/src/test/regression/regression_20240105.rs index cada5fbbfb..d2ee4f0931 100644 --- a/execution_engine_testing/tests/src/test/regression/regression_20240105.rs +++ b/execution_engine_testing/tests/src/test/regression/regression_20240105.rs @@ -94,7 +94,7 @@ mod repeated_ffi_call_should_gas_out_quickly { ChainspecConfig::from_chainspec_path(&*CHAINSPEC_SYMLINK).unwrap(); // Increase the `max_memory` available in order to avoid hitting unreachable // instruction during execution. - chainspec_config.wasm_config.max_memory = 10_000; + *chainspec_config.wasm_config.v1_mut().max_memory_mut() = 10_000; let mut builder = LmdbWasmTestBuilder::open( data_dir.path(), chainspec_config, diff --git a/execution_engine_testing/tests/src/test/regression/slow_input.rs b/execution_engine_testing/tests/src/test/regression/slow_input.rs index e8ef51f76b..526e33aee1 100644 --- a/execution_engine_testing/tests/src/test/regression/slow_input.rs +++ b/execution_engine_testing/tests/src/test/regression/slow_input.rs @@ -172,8 +172,10 @@ fn should_charge_extra_per_amount_of_br_table_elements() { ); assert_eq!( - gas_cost_2 - gas_cost_1, - Gas::from((M_ELEMENTS - N_ELEMENTS) * DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER), + gas_cost_2.checked_sub(gas_cost_1), + Some(Gas::from( + (M_ELEMENTS - N_ELEMENTS) * DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER + )), "the cost difference should equal to exactly the size of br_table difference " ); } diff --git a/execution_engine_testing/tests/src/test/regression/test_utils.rs b/execution_engine_testing/tests/src/test/regression/test_utils.rs index 44f6043b06..4d7d994a88 100644 --- a/execution_engine_testing/tests/src/test/regression/test_utils.rs +++ b/execution_engine_testing/tests/src/test/regression/test_utils.rs @@ -1,4 +1,4 @@ -use casper_engine_test_support::DEFAULT_WASM_CONFIG; +use casper_engine_test_support::DEFAULT_WASM_V1_CONFIG; use casper_types::addressable_entity::DEFAULT_ENTRY_POINT_NAME; use casper_wasm::{ builder, @@ -8,7 +8,7 @@ use casper_wasm::{ /// Prepare malicious payload with amount of opcodes that could potentially overflow injected gas /// counter. pub(crate) fn make_gas_counter_overflow() -> Vec { - let opcode_costs = DEFAULT_WASM_CONFIG.opcode_costs(); + let opcode_costs = DEFAULT_WASM_V1_CONFIG.opcode_costs(); // Create a lot of `nop` opcodes to potentially overflow gas injector's batching counter. let upper_bound = (u32::max_value() as usize / opcode_costs.nop as usize) + 1; diff --git a/execution_engine_testing/tests/src/test/storage_costs.rs b/execution_engine_testing/tests/src/test/storage_costs.rs index a9f0588a75..ab34a7bf69 100644 --- a/execution_engine_testing/tests/src/test/storage_costs.rs +++ b/execution_engine_testing/tests/src/test/storage_costs.rs @@ -17,8 +17,8 @@ use casper_types::{ bytesrepr::{Bytes, ToBytes}, AddressableEntityHash, BrTableCost, CLValue, ControlFlowCosts, EntityVersionKey, EraId, Group, Groups, HostFunctionCosts, Key, MessageLimits, OpcodeCosts, Package, ProtocolVersion, - RuntimeArgs, StorageCosts, StoredValue, URef, WasmConfig, DEFAULT_MAX_STACK_HEIGHT, - DEFAULT_WASM_MAX_MEMORY, U512, + RuntimeArgs, StorageCosts, StoredValue, URef, WasmConfig, WasmV1Config, + DEFAULT_V1_MAX_STACK_HEIGHT, DEFAULT_V1_WASM_MAX_MEMORY, U512, }; #[cfg(not(feature = "use-as-wasm"))] use casper_types::{ @@ -90,18 +90,18 @@ const NEW_OPCODE_COSTS: OpcodeCosts = OpcodeCosts { nop: 0, current_memory: 0, grow_memory: 0, + sign: 0, }; static NEW_HOST_FUNCTION_COSTS: Lazy = Lazy::new(HostFunctionCosts::zero); -static STORAGE_COSTS_ONLY: Lazy = Lazy::new(|| { - WasmConfig::new( - DEFAULT_WASM_MAX_MEMORY, - DEFAULT_MAX_STACK_HEIGHT, +static NO_COSTS_WASM_CONFIG: Lazy = Lazy::new(|| { + let wasm_v1_config = WasmV1Config::new( + DEFAULT_V1_WASM_MAX_MEMORY, + DEFAULT_V1_MAX_STACK_HEIGHT, NEW_OPCODE_COSTS, - StorageCosts::default(), *NEW_HOST_FUNCTION_COSTS, - MessageLimits::default(), - ) + ); + WasmConfig::new(MessageLimits::default(), wasm_v1_config) }); static NEW_PROTOCOL_VERSION: Lazy = Lazy::new(|| { @@ -130,7 +130,7 @@ fn initialize_isolated_storage_costs() -> LmdbWasmTestBuilder { let updated_chainspec = builder .chainspec() .clone() - .with_wasm_config(*STORAGE_COSTS_ONLY); + .with_wasm_config(*NO_COSTS_WASM_CONFIG); builder .with_chainspec(updated_chainspec) @@ -718,7 +718,7 @@ fn should_verify_new_uref_storage_cost() { assert_eq!( // should charge for storage of a u64 behind a URef builder.last_exec_gas_cost(), - STORAGE_COSTS_ONLY.storage_costs().calculate_gas_cost( + StorageCosts::default().calculate_gas_cost( StoredValue::CLValue(CLValue::from_t(0u64).expect("should create CLValue")) .serialized_length() ) @@ -763,7 +763,7 @@ fn should_verify_put_key_is_charging_for_storage() { assert_eq!( // should charge for storage of a named key builder.last_exec_gas_cost(), - STORAGE_COSTS_ONLY.storage_costs().calculate_gas_cost( + StorageCosts::default().calculate_gas_cost( StoredValue::NamedKey( NamedKeyValue::from_concrete_values(Key::Hash([0u8; 32]), "new_key".to_owned()) .expect("should create NamedKey") @@ -811,7 +811,7 @@ fn should_verify_remove_key_is_not_charging_for_storage() { assert_eq!( // should charge zero, because we do not charge for storage when removing a key builder.last_exec_gas_cost(), - STORAGE_COSTS_ONLY.storage_costs().calculate_gas_cost(0), + StorageCosts::default().calculate_gas_cost(0), ) } @@ -853,7 +853,7 @@ fn should_verify_create_contract_at_hash_is_charging_for_storage() { assert_eq!( // should charge at least enough for storage of a package and unit CLValue (for a URef) builder.last_exec_gas_cost(), - STORAGE_COSTS_ONLY.storage_costs().calculate_gas_cost( + StorageCosts::default().calculate_gas_cost( StoredValue::Package(Package::default()).serialized_length() + StoredValue::CLValue(CLValue::unit()).serialized_length() ) @@ -915,8 +915,7 @@ fn should_verify_create_contract_user_group_is_charging_for_storage() { assert_eq!( // should charge for storage of the new package builder.last_exec_gas_cost(), - STORAGE_COSTS_ONLY - .storage_costs() + StorageCosts::default() .calculate_gas_cost(StoredValue::Package(package.clone()).serialized_length()), ); @@ -939,7 +938,7 @@ fn should_verify_create_contract_user_group_is_charging_for_storage() { assert_eq!( // should charge for storage of the new package and a unit CLValue (for a URef) builder.last_exec_gas_cost(), - STORAGE_COSTS_ONLY.storage_costs().calculate_gas_cost( + StorageCosts::default().calculate_gas_cost( StoredValue::Package(package.clone()).serialized_length() + StoredValue::CLValue(CLValue::unit()).serialized_length() ) @@ -960,8 +959,7 @@ fn should_verify_create_contract_user_group_is_charging_for_storage() { assert_eq!( // should charge for storage of the new package builder.last_exec_gas_cost(), - STORAGE_COSTS_ONLY - .storage_costs() + StorageCosts::default() .calculate_gas_cost(StoredValue::Package(package).serialized_length()) ) } @@ -1024,7 +1022,7 @@ fn should_verify_subcall_new_uref_is_charging_for_storage() { assert_eq!( // should charge for storage of a u64 behind a URef builder.last_exec_gas_cost(), - STORAGE_COSTS_ONLY.storage_costs().calculate_gas_cost( + StorageCosts::default().calculate_gas_cost( StoredValue::CLValue(CLValue::from_t(0u64).expect("should create CLValue")) .serialized_length() ) diff --git a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs index 3a2fc46dbe..b6832dba58 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs @@ -22,7 +22,8 @@ use casper_types::{ ARG_DELEGATOR, ARG_PUBLIC_KEY, ARG_REWARDS_MAP, ARG_VALIDATOR, DELEGATION_RATE_DENOMINATOR, METHOD_DISTRIBUTE, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, }, - EntityAddr, EraId, Key, ProtocolVersion, PublicKey, SecretKey, Timestamp, U512, + EntityAddr, EraId, Key, ProtocolVersion, PublicKey, SecretKey, Timestamp, + DEFAULT_MINIMUM_BID_AMOUNT, U512, }; const ARG_ENTRY_POINT: &str = "entry_point"; @@ -1028,11 +1029,13 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { delegation_rate: 0, minimum_delegation_amount: updelegate_amount.as_u64(), maximum_delegation_amount: updelegate_amount.as_u64(), + minimum_bid_amount: DEFAULT_MINIMUM_BID_AMOUNT, } } else { AuctionMethod::WithdrawBid { public_key: VALIDATOR_1.clone(), amount, + minimum_bid_amount: DEFAULT_MINIMUM_BID_AMOUNT, } } }; diff --git a/execution_engine_testing/tests/src/test/system_contracts/genesis.rs b/execution_engine_testing/tests/src/test/system_contracts/genesis.rs index 3811d02a19..600eb91b7f 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/genesis.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/genesis.rs @@ -4,8 +4,8 @@ use once_cell::sync::Lazy; use casper_engine_test_support::{ ChainspecConfig, LmdbWasmTestBuilder, DEFAULT_AUCTION_DELAY, DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_GENESIS_TIMESTAMP_MILLIS, DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS, DEFAULT_PROTOCOL_VERSION, - DEFAULT_ROUND_SEIGNIORAGE_RATE, DEFAULT_SYSTEM_CONFIG, DEFAULT_UNBONDING_DELAY, - DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG, + DEFAULT_ROUND_SEIGNIORAGE_RATE, DEFAULT_STORAGE_COSTS, DEFAULT_SYSTEM_CONFIG, + DEFAULT_UNBONDING_DELAY, DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG, }; use casper_storage::data_access_layer::GenesisRequest; use casper_types::{ @@ -144,6 +144,7 @@ fn should_track_total_token_supply_in_mint() { .with_round_seigniorage_rate(round_seigniorage_rate) .with_unbonding_delay(unbonding_delay) .with_genesis_timestamp_millis(genesis_timestamp) + .with_storage_costs(*DEFAULT_STORAGE_COSTS) .build(); let genesis_request = GenesisRequest::new( diff --git a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs index 8a0980834b..3e8c90231e 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/upgrade.rs @@ -20,8 +20,8 @@ use casper_types::{ }, mint::ROUND_SEIGNIORAGE_RATE_KEY, }, - Account, CLValue, CoreConfig, EntityAddr, EraId, Key, ProtocolVersion, StoredValue, - SystemEntityRegistry, U256, U512, + Account, CLValue, CoreConfig, EntityAddr, EraId, Key, ProtocolVersion, StorageCosts, + StoredValue, SystemEntityRegistry, U256, U512, }; use rand::Rng; @@ -618,6 +618,7 @@ fn should_increase_max_associated_keys_after_upgrade() { core_config, wasm_config: Default::default(), system_costs_config: Default::default(), + storage_costs: StorageCosts::default(), }; builder.with_chainspec(chainspec); diff --git a/execution_engine_testing/tests/src/test/system_costs.rs b/execution_engine_testing/tests/src/test/system_costs.rs index d4bebb14ca..3be4f4b3ad 100644 --- a/execution_engine_testing/tests/src/test/system_costs.rs +++ b/execution_engine_testing/tests/src/test/system_costs.rs @@ -17,8 +17,9 @@ use casper_types::{ AuctionCosts, BrTableCost, ControlFlowCosts, CoreConfig, EraId, Gas, GenesisAccount, GenesisValidator, HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessageLimits, MintCosts, Motes, OpcodeCosts, ProtocolVersion, PublicKey, RuntimeArgs, - SecretKey, StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, DEFAULT_ADD_BID_COST, - DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY, U512, + SecretKey, StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, WasmV1Config, + DEFAULT_ADD_BID_COST, DEFAULT_MINIMUM_BID_AMOUNT, DEFAULT_V1_MAX_STACK_HEIGHT, + DEFAULT_V1_WASM_MAX_MEMORY, U512, }; use crate::wasm_utils; @@ -34,12 +35,12 @@ const VALIDATOR_1_STAKE: u64 = 250_000; static VALIDATOR_2_SECRET_KEY: Lazy = Lazy::new(|| SecretKey::ed25519_from_bytes([124; SecretKey::ED25519_LENGTH]).unwrap()); static VALIDATOR_2: Lazy = Lazy::new(|| PublicKey::from(&*VALIDATOR_2_SECRET_KEY)); -const BOND_AMOUNT: u64 = 42; +const BOND_AMOUNT: u64 = DEFAULT_MINIMUM_BID_AMOUNT + 42; const BID_AMOUNT: u64 = 99 + DEFAULT_MINIMUM_DELEGATION_AMOUNT; const TRANSFER_AMOUNT: u64 = 123; const BID_DELEGATION_RATE: DelegationRate = auction::DELEGATION_RATE_DENOMINATOR; const UPDATED_CALL_CONTRACT_COST: HostFunctionCost = 12_345; -const NEW_ADD_BID_COST: u32 = 2_500_000_000; +const NEW_ADD_BID_COST: u64 = 2_500_000_000; const NEW_WITHDRAW_BID_COST: u32 = 2_500_000_000; const NEW_DELEGATE_COST: u32 = 2_500_000_000; const NEW_UNDELEGATE_COST: u32 = NEW_DELEGATE_COST; @@ -640,62 +641,62 @@ fn should_charge_for_erroneous_system_contract_calls() { ( auction_hash, auction::METHOD_WITHDRAW_BID, - system_config.auction_costs().withdraw_bid, + system_config.auction_costs().withdraw_bid.into(), ), ( auction_hash, auction::METHOD_DELEGATE, - system_config.auction_costs().delegate, + system_config.auction_costs().delegate.into(), ), ( auction_hash, auction::METHOD_UNDELEGATE, - system_config.auction_costs().undelegate, + system_config.auction_costs().undelegate.into(), ), ( auction_hash, auction::METHOD_REDELEGATE, - system_config.auction_costs().redelegate, + system_config.auction_costs().redelegate.into(), ), ( auction_hash, auction::METHOD_RUN_AUCTION, - system_config.auction_costs().run_auction, + system_config.auction_costs().run_auction.into(), ), ( auction_hash, auction::METHOD_SLASH, - system_config.auction_costs().slash, + system_config.auction_costs().slash.into(), ), ( auction_hash, auction::METHOD_DISTRIBUTE, - system_config.auction_costs().distribute, + system_config.auction_costs().distribute.into(), ), ( mint_hash, mint::METHOD_MINT, - system_config.mint_costs().mint, + system_config.mint_costs().mint.into(), ), ( mint_hash, mint::METHOD_REDUCE_TOTAL_SUPPLY, - system_config.mint_costs().reduce_total_supply, + system_config.mint_costs().reduce_total_supply.into(), ), ( mint_hash, mint::METHOD_BALANCE, - system_config.mint_costs().balance, + system_config.mint_costs().balance.into(), ), ( mint_hash, mint::METHOD_TRANSFER, - system_config.mint_costs().transfer, + system_config.mint_costs().transfer.into(), ), ( handle_payment_hash, handle_payment::METHOD_SET_REFUND_PURSE, - system_config.handle_payment_costs().set_refund_purse, + system_config.handle_payment_costs().set_refund_purse.into(), ), // ( // handle_payment_hash, @@ -826,6 +827,7 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { nop: 0, current_memory: 0, grow_memory: 0, + sign: 0, }; let new_storage_costs = StorageCosts::new(0); @@ -836,15 +838,13 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { call_contract: HostFunction::fixed(UPDATED_CALL_CONTRACT_COST), ..Zero::zero() }; - - let wasm_config = WasmConfig::new( - DEFAULT_WASM_MAX_MEMORY, - DEFAULT_MAX_STACK_HEIGHT, + let wasm_v1_config = WasmV1Config::new( + DEFAULT_V1_WASM_MAX_MEMORY, + DEFAULT_V1_MAX_STACK_HEIGHT, new_opcode_costs, - new_storage_costs, new_host_function_costs, - MessageLimits::default(), ); + let wasm_config = WasmConfig::new(MessageLimits::default(), wasm_v1_config); let new_max_associated_keys = DEFAULT_MAX_ASSOCIATED_KEYS; let new_auction_costs = AuctionCosts::default(); @@ -871,6 +871,7 @@ fn should_verify_wasm_add_bid_wasm_cost_is_not_recursive() { system_costs_config, wasm_config, core_config, + storage_costs: new_storage_costs, }; builder.with_chainspec(chainspec); diff --git a/executor/wasm-host/src/context.rs b/executor/wasm-host/src/context.rs index 98e6d0de91..ae085e1405 100644 --- a/executor/wasm-host/src/context.rs +++ b/executor/wasm-host/src/context.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use bytes::Bytes; use casper_executor_wasm_interface::executor::Executor; use casper_storage::{global_state::GlobalStateReader, AddressGenerator, TrackingCopy}; -use casper_types::{account::AccountHash, Key, Timestamp, TransactionHash}; +use casper_types::{account::AccountHash, BlockTime, Key, Timestamp, TransactionHash}; use parking_lot::RwLock; /// Container that holds all relevant modules necessary to process an execution request. @@ -26,5 +26,5 @@ pub struct Context { pub address_generator: Arc>, pub chain_name: Arc, pub input: Bytes, - pub block_time: Timestamp, + pub block_time: BlockTime, } diff --git a/executor/wasm-host/src/host.rs b/executor/wasm-host/src/host.rs index 9b28ee0367..29d808b97c 100644 --- a/executor/wasm-host/src/host.rs +++ b/executor/wasm-host/src/host.rs @@ -1124,5 +1124,5 @@ pub fn casper_env_block_time( caller: impl Caller>, ) -> VMResult { let block_time = caller.context().block_time; - Ok(block_time.millis()) + Ok(block_time.value()) } diff --git a/executor/wasm-interface/src/executor.rs b/executor/wasm-interface/src/executor.rs index 18f9f0627a..8e7833a848 100644 --- a/executor/wasm-interface/src/executor.rs +++ b/executor/wasm-interface/src/executor.rs @@ -8,7 +8,8 @@ use casper_storage::{ AddressGenerator, TrackingCopy, }; use casper_types::{ - account::AccountHash, execution::Effects, Digest, EntityAddr, Key, Timestamp, TransactionHash, + account::AccountHash, execution::Effects, BlockHash, BlockTime, Digest, EntityAddr, Key, + StorageCosts, Timestamp, TransactionHash, }; use parking_lot::RwLock; use thiserror::Error; @@ -47,8 +48,14 @@ pub struct ExecuteRequest { /// /// This is very important ingredient for deriving contract hashes on the network. pub chain_name: Arc, - /// Block time. - pub block_time: Timestamp, + /// Block time represented as a unix timestamp. + pub block_time: BlockTime, + /// State root hash of the global state in which the transaction will be executed. + pub state_hash: Digest, + /// Parent block hash. + pub parent_block_hash: BlockHash, + /// Block height. + pub block_height: u64, } /// Builder for `ExecuteRequest`. @@ -64,7 +71,10 @@ pub struct ExecuteRequestBuilder { transaction_hash: Option, address_generator: Option>>, chain_name: Option>, - block_time: Option, + block_time: Option, + state_hash: Option, + parent_block_hash: Option, + block_height: Option, } impl ExecuteRequestBuilder { @@ -152,7 +162,7 @@ impl ExecuteRequestBuilder { } /// Set the block time. - pub fn with_block_time(mut self, block_time: Timestamp) -> Self { + pub fn with_block_time(mut self, block_time: BlockTime) -> Self { self.block_time = Some(block_time); self } @@ -172,6 +182,11 @@ impl ExecuteRequestBuilder { .ok_or("Address generator is not set")?; let chain_name = self.chain_name.ok_or("Chain name is not set")?; let block_time = self.block_time.ok_or("Block time is not set")?; + let state_hash = self.state_hash.ok_or("State hash is not set")?; + let parent_block_hash = self + .parent_block_hash + .ok_or("Parent block hash is not set")?; + let block_height = self.block_height.ok_or("Block height is not set")?; Ok(ExecuteRequest { initiator, caller_key, @@ -184,6 +199,9 @@ impl ExecuteRequestBuilder { address_generator, chain_name, block_time, + state_hash, + parent_block_hash, + block_height, }) } } diff --git a/executor/wasm/src/install.rs b/executor/wasm/src/install.rs index fe2aadb689..9303a1716c 100644 --- a/executor/wasm/src/install.rs +++ b/executor/wasm/src/install.rs @@ -7,7 +7,7 @@ use casper_types::{ account::AccountHash, contracts::{ContractHash, ContractPackageHash}, execution::Effects, - Digest, Timestamp, TransactionHash, + BlockTime, Digest, Timestamp, TransactionHash, }; use parking_lot::RwLock; use thiserror::Error; @@ -35,7 +35,7 @@ pub struct InstallContractRequest { /// Chain name. pub(crate) chain_name: Arc, /// Block time. - pub(crate) block_time: Timestamp, + pub(crate) block_time: BlockTime, /// Seed used for smart contract hash computation. pub(crate) seed: Option<[u8; 32]>, } @@ -51,7 +51,7 @@ pub struct InstallContractRequestBuilder { transaction_hash: Option, address_generator: Option>>, chain_name: Option>, - block_time: Option, + block_time: Option, seed: Option<[u8; 32]>, } @@ -109,12 +109,12 @@ impl InstallContractRequestBuilder { self } - pub fn with_block_time(mut self, block_time: Timestamp) -> Self { + pub fn with_block_time(mut self, block_time: BlockTime) -> Self { self.block_time = Some(block_time); self } - pub(crate) fn with_seed(mut self, seed: [u8; 32]) -> Self { + pub fn with_seed(mut self, seed: [u8; 32]) -> Self { self.seed = Some(seed); self } diff --git a/executor/wasm/src/lib.rs b/executor/wasm/src/lib.rs index 1835d52992..4d881313e8 100644 --- a/executor/wasm/src/lib.rs +++ b/executor/wasm/src/lib.rs @@ -9,7 +9,9 @@ use std::{ use bytes::Bytes; use casper_execution_engine::{ - engine_state::{EngineConfig, Error as EngineError, ExecutableItem, ExecutionEngineV1}, + engine_state::{ + BlockInfo, EngineConfig, Error as EngineError, ExecutableItem, ExecutionEngineV1, + }, execution::ExecError, }; use casper_executor_wasm_common::flags::ReturnFlags; @@ -36,10 +38,11 @@ use casper_types::{ bytesrepr, contracts::{ContractHash, ContractPackageHash}, execution::Effects, - AddressableEntity, BlockTime, ByteCode, ByteCodeAddr, ByteCodeHash, ByteCodeKind, Digest, - EntityAddr, EntityKind, Gas, Groups, InitiatorAddr, Key, Package, PackageHash, PackageStatus, - Phase, ProtocolVersion, StoredValue, Timestamp, Transaction, TransactionEntryPoint, - TransactionInvocationTarget, TransactionRuntime, TransactionTarget, URef, U512, + AddressableEntity, BlockHash, BlockTime, ByteCode, ByteCodeAddr, ByteCodeHash, ByteCodeKind, + Digest, EntityAddr, EntityKind, Gas, Groups, InitiatorAddr, Key, Package, PackageHash, + PackageStatus, Phase, ProtocolVersion, StoredValue, Timestamp, Transaction, + TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntime, TransactionTarget, + URef, U512, }; use either::Either; use install::{ @@ -319,6 +322,9 @@ impl ExecutorV2 { address_generator, chain_name, block_time, + state_hash, + parent_block_hash, + block_height, } = execute_request; // TODO: Purse uref does not need to be optional once value transfers to WasmBytes are @@ -350,6 +356,13 @@ impl ExecutorV2 { // We need to short circuit here to execute v1 contracts with legacy // execut + let block_info = BlockInfo::new( + state_hash, + block_time, + parent_block_hash, + block_height, + ); + return self.execute_legacy_wasm_byte_code( initiator, block_time, @@ -357,6 +370,7 @@ impl ExecutorV2 { entry_point, &input, &mut tracking_copy, + block_info, transaction_hash, gas_limit, ); @@ -422,6 +436,13 @@ impl ExecutorV2 { (Bytes::from(wasm_bytes), Either::Left(entry_point.as_str())) } Some(StoredValue::Contract(_legacy_contract)) => { + let block_info = BlockInfo::new( + state_hash, + block_time.into(), + parent_block_hash, + block_height, + ); + return self.execute_legacy_wasm_byte_code( initiator, block_time, @@ -429,6 +450,7 @@ impl ExecutorV2 { entry_point, &input, &mut tracking_copy, + block_info, transaction_hash, gas_limit, ); @@ -553,11 +575,12 @@ impl ExecutorV2 { fn execute_legacy_wasm_byte_code( &self, initiator: AccountHash, - block_time: Timestamp, + block_time: BlockTime, entity_addr: &EntityAddr, entry_point: &String, input: &Bytes, tracking_copy: &mut TrackingCopy, + block_info: BlockInfo, transaction_hash: casper_types::TransactionHash, gas_limit: u64, ) -> Result @@ -572,11 +595,12 @@ impl ExecutorV2 { let entry_point = entry_point.clone(); let args = bytesrepr::deserialize_from_slice(input).expect("should deserialize"); let phase = Phase::Session; + let wasm_v1_result = { let forked_tc = tracking_copy.fork2(); self.execution_engine_v1.execute_with_tracking_copy( forked_tc, - block_time, + block_info, transaction_hash, Gas::from(gas_limit), initiator_addr, @@ -715,32 +739,6 @@ impl ExecutorV2 { let mut execution_stack = self.execution_stack.write(); execution_stack.pop_back() } - - pub fn execute_wasm_v2_request

( - &self, - state_root_hash: Digest, - state_provider: &P, - request: WasmV2Request, - ) -> Result - where - P: StateProvider + CommitProvider, -

::Reader: 'static, - { - match request { - WasmV2Request::Install(install_request) => { - match self.install_contract(state_root_hash, state_provider, install_request) { - Ok(result) => Ok(WasmV2Result::Install(result)), - Err(error) => Err(WasmV2Error::Install(error)), - } - } - WasmV2Request::Execute(execute_request) => { - match self.execute_with_provider(state_root_hash, state_provider, execute_request) { - Ok(result) => Ok(WasmV2Result::Execute(result)), - Err(error) => Err(WasmV2Error::Execute(error)), - } - } - } - } } impl Executor for ExecutorV2 { @@ -789,232 +787,3 @@ fn get_purse_for_entity( other => panic!("should be account or contract received {other:?}"), } } - -/// The request to execute a Wasm contract. -pub enum WasmV2Request { - /// The request to install a Wasm contract. - Install(InstallContractRequest), - /// The request to execute a Wasm contract. - Execute(ExecuteRequest), -} - -/// The result of executing a Wasm contract. -pub enum WasmV2Result { - /// The result of installing a Wasm contract. - Install(InstallContractResult), - /// The result of executing a Wasm contract. - Execute(ExecuteWithProviderResult), -} - -impl WasmV2Result { - /// Returns the state root hash after the contract execution. - pub fn state_root_hash(&self) -> Digest { - match self { - WasmV2Result::Install(result) => result.post_state_hash, - WasmV2Result::Execute(result) => result.post_state_hash, - } - } - - /// Returns the gas usage of the contract execution. - pub fn gas_usage(&self) -> &GasUsage { - match self { - WasmV2Result::Install(result) => &result.gas_usage, - WasmV2Result::Execute(result) => &result.gas_usage, - } - } - - /// Returns the effects of the contract execution. - pub fn effects(&self) -> &Effects { - match self { - WasmV2Result::Install(result) => &result.effects, - WasmV2Result::Execute(result) => &result.effects, - } - } -} - -#[derive(Error, Debug)] -pub enum WasmV2Error { - #[error(transparent)] - Install(InstallContractError), - #[error(transparent)] - Execute(ExecuteWithProviderError), -} - -#[derive(Clone, Eq, PartialEq, Error, Debug)] -pub enum InvalidRequest { - #[error("Invalid transaction variant")] - InvalidTransactionVariant, - #[error("Invalid gas limit: {0}")] - InvalidGasLimit(U512), -} - -impl WasmV2Request { - pub fn new( - gas_limit: Gas, - network_name: impl Into>, - transaction: &Transaction, - ) -> Result { - // don't panic, return error that similar to V1Request - let transaction_v1 = match transaction.as_transaction_v1() { - Some(transaction_v1) => transaction_v1, - None => { - return Err(InvalidRequest::InvalidTransactionVariant); - } - }; - - let transaction_hash = transaction.hash(); - let initiator_addr = transaction.initiator_addr(); - - let gas_limit: u64 = gas_limit - .value() - .try_into() - .map_err(|_| InvalidRequest::InvalidGasLimit(gas_limit.value()))?; - - let address_generator = AddressGeneratorBuilder::default() - .seed_with(transaction_hash.as_ref()) - .build(); - - // If it's wrong args variant => invalid request => penalty payment - let input_data = transaction_v1.body().args().clone().into_bytesrepr(); // TODO: Make non optional - let value = transaction_v1.body().transferred_value(); - - enum Target { - Install { - module_bytes: Bytes, - entry_point: String, - seed: Option<[u8; 32]>, - }, - Session { - module_bytes: Bytes, - }, - Stored { - id: TransactionInvocationTarget, - entry_point: String, - }, - } - - let target = match transaction_v1.body().target() { - TransactionTarget::Native => todo!(), // - TransactionTarget::Stored { - id, - runtime: _, - transferred_value: _, - } => match transaction_v1.body().entry_point() { - TransactionEntryPoint::Custom(entry_point) => Target::Stored { - id: id.clone(), - entry_point: entry_point.clone(), - }, - _ => todo!(), - }, - TransactionTarget::Session { - module_bytes, - runtime: _, - transferred_value: _, - seed, - } => match transaction_v1.body().entry_point() { - TransactionEntryPoint::Call => Target::Session { - module_bytes: module_bytes.clone().take_inner().into(), - }, - TransactionEntryPoint::Custom(entry_point) => Target::Install { - module_bytes: module_bytes.clone().take_inner().into(), - entry_point: entry_point.to_string(), - seed: *seed, - }, - _ => todo!(), - }, - }; - - info!(%transaction_hash, "executing v1 contract"); - - match target { - Target::Install { - module_bytes, - entry_point, - seed, - } => { - let mut builder = InstallContractRequestBuilder::default(); - - let entry_point = (!entry_point.is_empty()).then_some(entry_point); - - match entry_point { - Some(entry_point) => { - builder = builder.with_entry_point(entry_point.clone()); - - if let Some(input_data) = input_data { - builder = builder.with_input(input_data.take_inner().into()); - } - } - None => { - assert!( - input_data.is_none() - || matches!(input_data, Some(input_data) if input_data.is_empty()) - ); - } - } - - if let Some(seed) = seed { - builder = builder.with_seed(seed); - } - - let install_request = builder - .with_initiator(initiator_addr.account_hash()) - .with_gas_limit(gas_limit) - .with_transaction_hash(transaction_hash) - .with_wasm_bytes(module_bytes) - .with_address_generator(address_generator) - .with_transferred_value(value.into()) // TODO: Replace u128 to u64 - .with_chain_name(network_name) - .with_block_time(transaction.timestamp()) - .build() - .expect("should build"); - - Ok(Self::Install(install_request)) - } - Target::Session { .. } | Target::Stored { .. } => { - let mut builder = ExecuteRequestBuilder::default(); - - let initiator_account_hash = &initiator_addr.account_hash(); - - let initiator_key = Key::Account(*initiator_account_hash); - - builder = builder - .with_address_generator(address_generator) - .with_gas_limit(gas_limit) - .with_transaction_hash(transaction_hash) - .with_initiator(*initiator_account_hash) - .with_caller_key(initiator_key) - // TODO: Callee is unnecessary as it can be derived from the - // execution target inside the executor - .with_callee_key(initiator_key) - .with_chain_name(network_name) - .with_transferred_value(value.into()) // TODO: Remove u128 internally - .with_block_time(transaction.timestamp()); - - if let Some(input_data) = input_data.clone() { - builder = builder.with_input(input_data.clone().take_inner().into()); - } - - let execution_kind = match target { - Target::Session { module_bytes } => ExecutionKind::SessionBytes(module_bytes), - Target::Stored { - id: TransactionInvocationTarget::ByHash(address), - entry_point, - } => ExecutionKind::Stored { - address: EntityAddr::SmartContract(address), - entry_point: entry_point.clone(), - }, - Target::Stored { id, entry_point } => { - todo!("Unsupported target {entry_point} {id:?}") - } - Target::Install { .. } => unreachable!(), - }; - - builder = builder.with_target(execution_kind); - - let execute_request = builder.build().expect("should build"); - - Ok(Self::Execute(execute_request)) - } - } - } -} diff --git a/executor/wasm/tests/integration.rs b/executor/wasm/tests/integration.rs index 526f2e972a..545fc9bba2 100644 --- a/executor/wasm/tests/integration.rs +++ b/executor/wasm/tests/integration.rs @@ -20,7 +20,7 @@ use casper_storage::{ AddressGenerator, }; use casper_types::{ - account::AccountHash, bytesrepr::ToBytes, ChainspecRegistry, Digest, EntityAddr, + account::AccountHash, bytesrepr::ToBytes, BlockTime, ChainspecRegistry, Digest, EntityAddr, GenesisAccount, GenesisConfigBuilder, Key, Motes, Phase, ProtocolVersion, PublicKey, RuntimeArgs, SecretKey, StoredValue, Timestamp, TransactionHash, TransactionV1Hash, U512, }; @@ -74,7 +74,7 @@ fn base_execute_builder() -> ExecuteRequestBuilder { .with_transferred_value(1000) .with_transaction_hash(TRANSACTION_HASH) .with_chain_name(DEFAULT_CHAIN_NAME) - .with_block_time(Timestamp::now()) + .with_block_time(Timestamp::now().into()) } fn base_install_request_builder() -> InstallContractRequestBuilder { @@ -83,7 +83,7 @@ fn base_install_request_builder() -> InstallContractRequestBuilder { .with_gas_limit(1_000_000) .with_transaction_hash(TRANSACTION_HASH) .with_chain_name(DEFAULT_CHAIN_NAME) - .with_block_time(Timestamp::now()) + .with_block_time(Timestamp::now().into()) } // #[test] @@ -160,7 +160,7 @@ fn harness() { .with_serialized_input((flipper_address,)) .with_shared_address_generator(address_generator) .with_chain_name(DEFAULT_CHAIN_NAME) - .with_block_time(Timestamp::now()) + .with_block_time(Timestamp::now().into()) .build() .expect("should build"); run_wasm_session( @@ -202,7 +202,7 @@ fn cep18() { .with_entry_point("new".to_string()) .with_input(input_data) .with_chain_name(DEFAULT_CHAIN_NAME) - .with_block_time(Timestamp::now()) + .with_block_time(Timestamp::now().into()) .build() .expect("should build"); @@ -229,7 +229,7 @@ fn cep18() { .with_transferred_value(0) .with_shared_address_generator(Arc::clone(&address_generator)) .with_chain_name(DEFAULT_CHAIN_NAME) - .with_block_time(Timestamp::now()) + .with_block_time(Timestamp::now().into()) .build() .expect("should build"); diff --git a/node/CHANGELOG.md b/node/CHANGELOG.md index 8766af4e21..f613a469f0 100644 --- a/node/CHANGELOG.md +++ b/node/CHANGELOG.md @@ -542,4 +542,4 @@ All notable changes to this project will be documented in this file. The format [1.1.1]: https://github.com/casper-network/casper-node/compare/v1.0.1...v1.1.1 [1.1.0]: https://github.com/casper-network/casper-node/compare/v1.0.1...v1.1.1 [1.0.1]: https://github.com/casper-network/casper-node/compare/v1.0.0...v1.0.1 -[1.0.0]: https://github.com/casper-network/casper-node/releases/tag/v1.0.0 +[1.0.0]: https://github.com/casper-network/casper-node/releases/tag/v1.0.0 \ No newline at end of file diff --git a/node/Cargo.toml b/node/Cargo.toml index 770aa34346..cd460fd790 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -7,7 +7,7 @@ description = "The Casper blockchain node" documentation = "https://docs.rs/casper-node" readme = "README.md" homepage = "https://casperlabs.io" -repository = "https://github.com/CasperLabs/casper-node/tree/master/node" +repository = "https://github.com/casper-network/casper-node/tree/master/node" license = "Apache-2.0" default-run = "casper-node" exclude = ["proptest-regressions"] diff --git a/node/README.md b/node/README.md index f28055a03c..aab5c1d4a1 100644 --- a/node/README.md +++ b/node/README.md @@ -5,7 +5,7 @@ [![Build Status](https://drone-auto-casper-network.casperlabs.io/api/badges/casper-network/casper-node/status.svg?branch=dev)](http://drone-auto-casper-network.casperlabs.io/casper-network/casper-node) [![Crates.io](https://img.shields.io/crates/v/casper-node)](https://crates.io/crates/casper-node) [![Documentation](https://docs.rs/casper-node/badge.svg)](https://docs.rs/casper-node) -[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/CasperLabs/casper-node/blob/master/LICENSE) +[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/casper-network/casper-node/blob/master/LICENSE) The component for running a node on the casper network. diff --git a/node/build.rs b/node/build.rs index afa9876009..42f0f6a06a 100644 --- a/node/build.rs +++ b/node/build.rs @@ -20,24 +20,23 @@ fn main() { .output() { Ok(output) => { - //In the event the git command is successful, export the properly formatted git hash to + // In the event the git command is successful, export the properly formatted git hash to // cargo at compile time. let git_hash_raw = String::from_utf8(output.stdout).expect("Failed to obtain commit hash to string"); let git_hash = git_hash_raw.trim_end_matches('\n'); - println!("cargo:rustc-env={}={}", NODE_GIT_HASH_ENV_VAR, git_hash); + println!("cargo:rustc-env={NODE_GIT_HASH_ENV_VAR}={git_hash}"); } Err(error) => { - println!("cargo:warning={}", error); + println!("cargo:warning={error}"); println!("cargo:warning=casper-node build version will not include git short hash"); } } println!( - "cargo:rustc-env={}={}", - NODE_BUILD_PROFILE_ENV_VAR, + "cargo:rustc-env={NODE_BUILD_PROFILE_ENV_VAR}={}", env::var(CARGO_BUILD_PROFILE_ENV_VAR).unwrap() ); } diff --git a/node/src/app/main.rs b/node/src/app/main.rs index 607c4e6a5a..9b42acb1ab 100644 --- a/node/src/app/main.rs +++ b/node/src/app/main.rs @@ -25,12 +25,12 @@ fn panic_hook(info: &PanicInfo) { // Print panic info if let Some(s) = info.payload().downcast_ref::<&str>() { - eprintln!("node panicked: {}", s); + eprintln!("node panicked: {s}"); // TODO - use `info.message()` once https://github.com/rust-lang/rust/issues/66745 is fixed // } else if let Some(message) = info.message() { - // eprintln!("{}", message); + // eprintln!("{message}"); } else { - eprintln!("{}", info); + eprintln!("{info}"); } // Abort after a panic, even if only a worker thread panicked. @@ -55,7 +55,7 @@ fn main() -> anyhow::Result<()> { // Parse CLI args and run selected subcommand. let opts = Cli::from_args(); - runtime.block_on(async { opts.run().await })? + runtime.block_on(opts.run())? }; info!(%exit_code, "exiting casper-node"); diff --git a/node/src/cli.rs b/node/src/cli.rs index 960bc6b7de..e83656fe70 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -203,8 +203,7 @@ impl Cli { let old_root = old_config .parent() - .map(|path| path.to_owned()) - .unwrap_or_else(|| "/".into()); + .map_or_else(|| "/".into(), Path::to_path_buf); let encoded_old_config = fs::read_to_string(&old_config) .context("could not read old configuration file") .with_context(|| old_config.display().to_string())?; @@ -225,8 +224,7 @@ impl Cli { let old_root = old_config .parent() - .map(|path| path.to_owned()) - .unwrap_or_else(|| "/".into()); + .map_or_else(|| "/".into(), Path::to_path_buf); let encoded_old_config = fs::read_to_string(&old_config) .context("could not read old configuration file") .with_context(|| old_config.display().to_string())?; @@ -251,8 +249,7 @@ impl Cli { // Otherwise, we default to `/`. let root = config .parent() - .map(|path| path.to_owned()) - .unwrap_or_else(|| "/".into()); + .map_or_else(|| "/".into(), Path::to_path_buf); // The app supports running without a config file, using default values. let encoded_config = fs::read_to_string(config) diff --git a/node/src/cli/arglang.rs b/node/src/cli/arglang.rs index 2d39d7a5f8..1964708a5e 100644 --- a/node/src/cli/arglang.rs +++ b/node/src/cli/arglang.rs @@ -64,14 +64,14 @@ fn tokenize(input: &str) -> Result, Error> { // Check if we need to complete a token. if !buffer.is_empty() { match ch { - Some(' ') | Some('"') | Some('[') | Some(']') | Some(',') | None => { + Some(' ' | '"' | '[' | ']' | ',') | None => { // Try to parse as number or bool first. if let Ok(value) = i64::from_str(&buffer) { tokens.push(Token::I64(value)); } else if let Ok(value) = bool::from_str(&buffer) { tokens.push(Token::Boolean(value)); } else { - tokens.push(Token::String(buffer.clone())) + tokens.push(Token::String(buffer.clone())); } buffer.clear(); @@ -161,7 +161,7 @@ where } } } - Some(t @ Token::CloseBracket) | Some(t @ Token::Comma) => Err(Error::UnexpectedToken(t)), + Some(t @ (Token::CloseBracket | Token::Comma)) => Err(Error::UnexpectedToken(t)), None => Err(Error::UnexpectedEndOfInput), } } diff --git a/node/src/components/binary_port.rs b/node/src/components/binary_port.rs index 2012a74608..e58c3ed67e 100644 --- a/node/src/components/binary_port.rs +++ b/node/src/components/binary_port.rs @@ -3,6 +3,7 @@ mod config; mod error; mod event; mod metrics; +mod rate_limiter; #[cfg(test)] mod tests; @@ -13,9 +14,10 @@ use casper_binary_port::{ BinaryMessageCodec, BinaryRequest, BinaryRequestHeader, BinaryRequestTag, BinaryResponse, BinaryResponseAndRequest, ContractInformation, DictionaryItemIdentifier, DictionaryQueryResult, EntityIdentifier, EraIdentifier, ErrorCode, GetRequest, GetTrieFullResult, - GlobalStateQueryResult, GlobalStateRequest, InformationRequest, InformationRequestTag, - KeyPrefix, NodeStatus, PackageIdentifier, PurseIdentifier, ReactorStateName, RecordId, - ResponseType, RewardResponse, TransactionWithExecutionInfo, ValueWithProof, + GlobalStateEntityQualifier, GlobalStateQueryResult, GlobalStateRequest, InformationRequest, + InformationRequestTag, KeyPrefix, NodeStatus, PackageIdentifier, PurseIdentifier, + ReactorStateName, RecordId, ResponseType, RewardResponse, TransactionWithExecutionInfo, + ValueWithProof, }; use casper_storage::{ data_access_layer::{ @@ -40,20 +42,25 @@ use casper_types::{ ContractWasmHash, Digest, EntityAddr, GlobalStateIdentifier, Key, Package, PackageAddr, Peers, ProtocolVersion, Rewards, SignedBlock, StoredValue, TimeDiff, Transaction, URef, }; +use thiserror::Error as ThisError; use datasize::DataSize; use either::Either; -use futures::{future::BoxFuture, FutureExt, SinkExt, StreamExt}; +use futures::{SinkExt, StreamExt}; use once_cell::sync::OnceCell; use prometheus::Registry; +use rate_limiter::{LimiterResponse, RateLimiter, RateLimiterError}; use tokio::{ join, net::{TcpListener, TcpStream}, - sync::{Notify, OwnedSemaphorePermit, Semaphore}, + sync::{Mutex, Notify, OwnedSemaphorePermit, Semaphore}, }; use tokio_util::codec::Framed; use tracing::{debug, error, info, warn}; +#[cfg(test)] +use futures::{future::BoxFuture, FutureExt}; + use crate::{ contract_runtime::SpeculativeExecutionResult, effect::{ @@ -64,7 +71,7 @@ use crate::{ }, EffectBuilder, EffectExt, Effects, }, - reactor::{main_reactor::MainEvent, Finalize, QueueKind}, + reactor::{main_reactor::MainEvent, QueueKind}, types::NodeRng, utils::{display_error, ListeningError}, }; @@ -78,13 +85,37 @@ pub(crate) use event::Event; const COMPONENT_NAME: &str = "binary_port"; +#[derive(Debug, ThisError)] +pub(crate) enum BinaryPortInitializationError { + #[error("could not initialize rate limiter: {0}")] + CannotInitializeRateLimiter(String), + #[error("could not initialize metrics: {0}")] + CannotInitializeMetrics(prometheus::Error), +} + +impl From for BinaryPortInitializationError { + fn from(value: RateLimiterError) -> Self { + BinaryPortInitializationError::CannotInitializeRateLimiter(value.to_string()) + } +} + +impl From for BinaryPortInitializationError { + fn from(value: prometheus::Error) -> Self { + BinaryPortInitializationError::CannotInitializeMetrics(value) + } +} + #[derive(Debug, DataSize)] pub(crate) struct BinaryPort { + #[data_size(skip)] state: ComponentState, + #[data_size(skip)] config: Arc, - /// The chainspec. + #[data_size(skip)] chainspec: Arc, #[data_size(skip)] + protocol_version: ProtocolVersion, + #[data_size(skip)] connection_limit: Arc, #[data_size(skip)] metrics: Arc, @@ -94,6 +125,8 @@ pub(crate) struct BinaryPort { shutdown_trigger: Arc, #[data_size(skip)] server_join_handle: OnceCell>, + #[data_size(skip)] + rate_limiter: Arc>, } impl BinaryPort { @@ -101,16 +134,23 @@ impl BinaryPort { config: Config, chainspec: Arc, registry: &Registry, - ) -> Result { + ) -> Result { + let rate_limiter = Arc::new(Mutex::new( + RateLimiter::new(config.qps_limit, TimeDiff::from_seconds(1)) + .map_err(BinaryPortInitializationError::from)?, + )); + let protocol_version = chainspec.protocol_version(); Ok(Self { state: ComponentState::Uninitialized, connection_limit: Arc::new(Semaphore::new(config.max_connections)), config: Arc::new(config), chainspec, - metrics: Arc::new(Metrics::new(registry)?), + protocol_version, + metrics: Arc::new(Metrics::new(registry).map_err(BinaryPortInitializationError::from)?), local_addr: Arc::new(OnceCell::new()), shutdown_trigger: Arc::new(Notify::new()), server_join_handle: OnceCell::new(), + rate_limiter, }) } @@ -129,6 +169,7 @@ async fn handle_request( config: &Config, chainspec: &Chainspec, metrics: &Metrics, + protocol_version: ProtocolVersion, ) -> BinaryResponse where REv: From @@ -143,7 +184,6 @@ where + From + Send, { - let protocol_version = effect_builder.get_protocol_version().await; match req { BinaryRequest::TryAcceptTransaction { transaction } => { metrics.binary_port_try_accept_transaction_count.inc(); @@ -266,6 +306,17 @@ where metrics.binary_port_get_state_count.inc(); handle_state_request(effect_builder, *req, protocol_version, config, chainspec).await } + GetRequest::Trie { trie_key } => { + metrics.binary_port_get_trie_count.inc(); + handle_trie_request( + effect_builder, + trie_key, + protocol_version, + config, + chainspec, + ) + .await + } } } @@ -354,12 +405,9 @@ where + From + From, { - match request { - GlobalStateRequest::Item { - state_identifier, - base_key, - path, - } => { + let (state_identifier, qualifier) = request.destructure(); + match qualifier { + GlobalStateEntityQualifier::Item { base_key, path } => { let Some(state_root_hash) = resolve_state_root_hash(effect_builder, state_identifier).await else { @@ -371,10 +419,7 @@ where Err(err) => BinaryResponse::new_error(err, protocol_version), } } - GlobalStateRequest::AllItems { - state_identifier, - key_tag, - } => { + GlobalStateEntityQualifier::AllItems { key_tag } => { if !config.allow_request_get_all_values { debug!(%key_tag, "received a request for items by key tag while the feature is disabled"); BinaryResponse::new_error(ErrorCode::FunctionDisabled, protocol_version) @@ -383,28 +428,7 @@ where .await } } - GlobalStateRequest::Trie { trie_key } => { - if !config.allow_request_get_trie { - debug!(%trie_key, "received a trie request while the feature is disabled"); - BinaryResponse::new_error(ErrorCode::FunctionDisabled, protocol_version) - } else { - let req = TrieRequest::new(trie_key, None); - match effect_builder.get_trie(req).await.into_legacy() { - Ok(result) => BinaryResponse::from_value( - GetTrieFullResult::new(result.map(TrieRaw::into_inner)), - protocol_version, - ), - Err(error) => { - debug!(%error, "failed when querying for a trie"); - BinaryResponse::new_error(ErrorCode::InternalError, protocol_version) - } - } - } - } - GlobalStateRequest::DictionaryItem { - state_identifier, - identifier, - } => { + GlobalStateEntityQualifier::DictionaryItem { identifier } => { let Some(state_root_hash) = resolve_state_root_hash(effect_builder, state_identifier).await else { @@ -475,10 +499,7 @@ where Err(err) => BinaryResponse::new_error(err, protocol_version), } } - GlobalStateRequest::Balance { - state_identifier, - purse_identifier, - } => { + GlobalStateEntityQualifier::Balance { purse_identifier } => { let Some(state_root_hash) = resolve_state_root_hash(effect_builder, state_identifier).await else { @@ -492,10 +513,7 @@ where ) .await } - GlobalStateRequest::ItemsByPrefix { - state_identifier, - key_prefix, - } => { + GlobalStateEntityQualifier::ItemsByPrefix { key_prefix } => { handle_get_items_by_prefix( state_identifier, key_prefix, @@ -507,6 +525,37 @@ where } } +async fn handle_trie_request( + effect_builder: EffectBuilder, + trie_key: Digest, + protocol_version: ProtocolVersion, + config: &Config, + _chainspec: &Chainspec, +) -> BinaryResponse +where + REv: From + + From + + From + + From, +{ + if !config.allow_request_get_trie { + debug!(%trie_key, "received a trie request while the feature is disabled"); + BinaryResponse::new_error(ErrorCode::FunctionDisabled, protocol_version) + } else { + let req = TrieRequest::new(trie_key, None); + match effect_builder.get_trie(req).await.into_raw() { + Ok(result) => BinaryResponse::from_value( + GetTrieFullResult::new(result.map(TrieRaw::into_inner)), + protocol_version, + ), + Err(error) => { + debug!(%error, "failed when querying for a trie"); + BinaryResponse::new_error(ErrorCode::InternalError, protocol_version) + } + } + } +} + async fn get_dictionary_item_by_legacy_named_key( effect_builder: EffectBuilder, state_root_hash: Digest, @@ -1199,7 +1248,7 @@ where }; let block_rewards = match era_end.rewards() { Rewards::V2(rewards) => rewards, - _ => { + Rewards::V1(_) => { return BinaryResponse::new_error( ErrorCode::UnsupportedRewardsV1Request, protocol_version, @@ -1349,7 +1398,7 @@ where .await .map_or_else( |err| BinaryResponse::new_error(err.into(), protocol_version), - |_| BinaryResponse::new_empty(protocol_version), + |()| BinaryResponse::new_empty(protocol_version), ) } @@ -1391,6 +1440,8 @@ async fn handle_client_loop( stream: TcpStream, effect_builder: EffectBuilder, max_message_size_bytes: u32, + rate_limiter: Arc>, + version: ProtocolVersion, ) -> Result<(), Error> where REv: From @@ -1418,8 +1469,8 @@ where return Err(Error::NoPayload); }; - let version = effect_builder.get_protocol_version().await; - let (response, id) = handle_payload(effect_builder, payload, version).await; + let (response, id) = + handle_payload(effect_builder, payload, version, Arc::clone(&rate_limiter)).await; framed .send(BinaryMessage::new( BinaryResponseAndRequest::new(response, payload, id).to_bytes()?, @@ -1457,6 +1508,7 @@ async fn handle_payload( effect_builder: EffectBuilder, payload: &[u8], protocol_version: ProtocolVersion, + rate_limiter: Arc>, ) -> (BinaryResponse, u16) where REv: From, @@ -1468,6 +1520,13 @@ where let request_id = header.id(); + if let LimiterResponse::Throttled = rate_limiter.lock().await.throttle() { + return ( + BinaryResponse::new_error(ErrorCode::RequestThrottled, protocol_version), + request_id, + ); + } + if !header .protocol_version() .is_compatible_with(&protocol_version) @@ -1514,6 +1573,8 @@ async fn handle_client( effect_builder: EffectBuilder, config: Arc, _permit: OwnedSemaphorePermit, + rate_limiter: Arc>, + protocol_version: ProtocolVersion, ) where REv: From + From @@ -1527,8 +1588,14 @@ async fn handle_client( + From + Send, { - if let Err(err) = - handle_client_loop(stream, effect_builder, config.max_message_size_bytes).await + if let Err(err) = handle_client_loop( + stream, + effect_builder, + config.max_message_size_bytes, + rate_limiter, + protocol_version, + ) + .await { // Low severity is used to prevent malicious clients from causing log floods. info!(%addr, err=display_error(&err), "binary port client handler error"); @@ -1597,7 +1664,8 @@ async fn run_server( } } -impl Finalize for BinaryPort { +#[cfg(test)] +impl crate::reactor::Finalize for BinaryPort { fn finalize(mut self) -> BoxFuture<'static, ()> { self.shutdown_trigger.notify_one(); async move { @@ -1777,7 +1845,16 @@ where if let Ok(permit) = Arc::clone(&self.connection_limit).try_acquire_owned() { self.metrics.binary_port_connections_count.inc(); let config = Arc::clone(&self.config); - tokio::spawn(handle_client(peer, stream, effect_builder, config, permit)); + let rate_limiter = Arc::clone(&self.rate_limiter); + tokio::spawn(handle_client( + peer, + stream, + effect_builder, + config, + permit, + rate_limiter, + self.protocol_version, + )); } else { warn!( "connection limit reached, dropping connection from {}", @@ -1790,11 +1867,18 @@ where let config = Arc::clone(&self.config); let chainspec = Arc::clone(&self.chainspec); let metrics = Arc::clone(&self.metrics); + let protocol_version = self.protocol_version; async move { - let response = - handle_request(request, effect_builder, &config, &chainspec, &metrics) - .await; - responder.respond(response).await + let response = handle_request( + request, + effect_builder, + &config, + &chainspec, + &metrics, + protocol_version, + ) + .await; + responder.respond(response).await; } .ignore() } diff --git a/node/src/components/binary_port/config.rs b/node/src/components/binary_port/config.rs index 828a3c1ddb..808df2c194 100644 --- a/node/src/components/binary_port/config.rs +++ b/node/src/components/binary_port/config.rs @@ -5,12 +5,10 @@ use serde::{Deserialize, Serialize}; const DEFAULT_ADDRESS: &str = "0.0.0.0:0"; /// Default maximum message size. const DEFAULT_MAX_MESSAGE_SIZE: u32 = 4 * 1024 * 1024; -/// Default request limit. -const DEFAULT_CLIENT_REQUEST_LIMIT: u16 = 3; -/// Default request buffer size. -const DEFAULT_CHANNEL_BUFFER_SIZE: usize = 16; /// Default maximum number of connections. const DEFAULT_MAX_CONNECTIONS: usize = 16; +/// Default maximum number of requests per second. +const DEFAULT_MAX_QPS: usize = 100; /// Binary port server configuration. #[derive(Clone, DataSize, Debug, Deserialize, Serialize)] @@ -31,12 +29,10 @@ pub struct Config { pub allow_request_speculative_exec: bool, /// Maximum size of the binary port message. pub max_message_size_bytes: u32, - /// Maximum number of in-flight requests per client. - pub client_request_limit: u16, - /// Number of requests that can be buffered per client. - pub client_request_buffer_size: usize, /// Maximum number of connections to the server. pub max_connections: usize, + /// Maximum number of requests per second. + pub qps_limit: usize, } impl Config { @@ -48,10 +44,9 @@ impl Config { allow_request_get_all_values: false, allow_request_get_trie: false, allow_request_speculative_exec: false, - client_request_limit: DEFAULT_CLIENT_REQUEST_LIMIT, max_message_size_bytes: DEFAULT_MAX_MESSAGE_SIZE, - client_request_buffer_size: DEFAULT_CHANNEL_BUFFER_SIZE, max_connections: DEFAULT_MAX_CONNECTIONS, + qps_limit: DEFAULT_MAX_QPS, } } } diff --git a/node/src/components/binary_port/event.rs b/node/src/components/binary_port/event.rs index 78b90c574e..0f190682b2 100644 --- a/node/src/components/binary_port/event.rs +++ b/node/src/components/binary_port/event.rs @@ -3,7 +3,7 @@ use std::{ net::SocketAddr, }; -use casper_binary_port::{BinaryRequest, BinaryResponse, GetRequest, GlobalStateRequest}; +use casper_binary_port::{BinaryRequest, BinaryResponse, GetRequest}; use tokio::net::TcpStream; use crate::effect::Responder; @@ -38,26 +38,8 @@ impl Display for Event { GetRequest::Information { info_type_tag, key } => { write!(f, "get info with tag {} ({})", info_type_tag, key.len()) } - GetRequest::State(state_request) => match state_request.as_ref() { - GlobalStateRequest::Item { base_key, .. } => { - write!(f, "get item from global state ({})", base_key) - } - GlobalStateRequest::AllItems { key_tag, .. } => { - write!(f, "get all items ({})", key_tag) - } - GlobalStateRequest::Trie { trie_key } => { - write!(f, "get trie ({})", trie_key) - } - GlobalStateRequest::DictionaryItem { .. } => { - write!(f, "get dictionary item") - } - GlobalStateRequest::Balance { .. } => { - write!(f, "get balance by state root",) - } - GlobalStateRequest::ItemsByPrefix { .. } => { - write!(f, "get items by prefix") - } - }, + GetRequest::State(state_request) => state_request.as_ref().fmt(f), + GetRequest::Trie { trie_key } => write!(f, "get trie ({})", trie_key), }, BinaryRequest::TryAcceptTransaction { transaction, .. } => { write!(f, "try accept transaction ({})", transaction.hash()) diff --git a/node/src/components/binary_port/metrics.rs b/node/src/components/binary_port/metrics.rs index 748afb1077..36de7128fd 100644 --- a/node/src/components/binary_port/metrics.rs +++ b/node/src/components/binary_port/metrics.rs @@ -26,6 +26,9 @@ const BINARY_PORT_CONNECTIONS_COUNT_NAME: &str = "binary_port_connections_count" const BINARY_PORT_CONNECTIONS_COUNT_HELP: &str = "total number of external connections established to binary port"; +const BINARY_PORT_TRIE_COUNT_NAME: &str = "binary_port_get_trie_count"; +const BINARY_PORT_TRIE_COUNT_HELP: &str = "number of Get queries received for the trie state"; + /// Metrics. #[derive(Debug)] pub(super) struct Metrics { @@ -41,6 +44,8 @@ pub(super) struct Metrics { pub(super) binary_port_get_state_count: IntCounter, /// Number of distinct connections to binary port. pub(super) binary_port_connections_count: IntCounter, + /// Number of `Get::Trie` queries received. + pub(super) binary_port_get_trie_count: IntCounter, registry: Registry, } @@ -78,12 +83,18 @@ impl Metrics { BINARY_PORT_CONNECTIONS_COUNT_HELP.to_string(), )?; + let binary_port_get_trie_count = IntCounter::new( + BINARY_PORT_TRIE_COUNT_NAME.to_string(), + BINARY_PORT_TRIE_COUNT_HELP.to_string(), + )?; + registry.register(Box::new(binary_port_try_accept_transaction_count.clone()))?; registry.register(Box::new(binary_port_try_speculative_exec_count.clone()))?; registry.register(Box::new(binary_port_get_record_count.clone()))?; registry.register(Box::new(binary_port_get_info_count.clone()))?; registry.register(Box::new(binary_port_get_state_count.clone()))?; registry.register(Box::new(binary_port_connections_count.clone()))?; + registry.register(Box::new(binary_port_get_trie_count.clone()))?; Ok(Metrics { binary_port_try_accept_transaction_count, @@ -92,6 +103,7 @@ impl Metrics { binary_port_get_info_count, binary_port_get_state_count, binary_port_connections_count, + binary_port_get_trie_count, registry: registry.clone(), }) } diff --git a/node/src/components/binary_port/rate_limiter.rs b/node/src/components/binary_port/rate_limiter.rs new file mode 100644 index 0000000000..b87ec22e94 --- /dev/null +++ b/node/src/components/binary_port/rate_limiter.rs @@ -0,0 +1,379 @@ +use casper_types::{TimeDiff, Timestamp}; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub(crate) enum RateLimiterError { + #[error("Cannot create Rate limiter with 0 max_requests")] + EmptyWindowNotAllowed, + #[error("Maximum window duration is too large")] + WindowDurationTooLarge, + #[error("Maximum window duration is too small")] + WindowDurationTooSmall, +} + +const MAX_WINDOW_DURATION_MS: u64 = 1000 * 60 * 60; // 1 hour + +#[derive(PartialEq, Eq, Debug)] +/// Response from the rate limiter. +pub(crate) enum LimiterResponse { + /// when limiter allowed the request + Allowed, + /// when limiter throttled the request + Throttled, +} + +/// A buffer to store timestamps of requests. The assumption is that the buffer will keep the +/// monotonical order of timestamps as they are pushed. +#[derive(Debug)] +struct Buffer { + buffer: Vec, + in_index: usize, + out_index: usize, + capacity: usize, +} + +impl Buffer { + fn new(size: usize) -> Self { + Buffer { + buffer: vec![0; size + 1], + in_index: 0, + out_index: 0, + capacity: size + 1, + } + } + + fn is_full(&self) -> bool { + self.in_index == (self.out_index + self.capacity - 1) % self.capacity + } + + fn is_empty(&self) -> bool { + self.in_index == self.out_index + } + + //This should only be used from `push` + fn push_and_slide(&mut self, value: u64) -> bool { + let out_index = self.out_index as i32; + let capacity = self.capacity as i32; + let mut to_index = self.in_index as i32; + let mut from_index = (self.in_index as i32 + capacity - 1) % capacity; + + while to_index != out_index && self.buffer[from_index as usize] > value { + self.buffer[to_index as usize] = self.buffer[from_index as usize]; + to_index = (to_index + capacity - 1) % capacity; + from_index = (from_index + capacity - 1) % capacity; + } + self.buffer[to_index as usize] = value; + self.in_index = (self.in_index + 1) % self.capacity; + true + } + + fn push(&mut self, value: u64) -> bool { + if self.is_full() { + return false; + } + if !self.is_empty() { + let last_stored_index = (self.in_index + self.capacity - 1) % self.capacity; + let last_stored = self.buffer[last_stored_index]; + // We are expecting values to be monotonically increasing. But there is a scenario in + // which the system time might be changed to a previous time. + // We handle that by wiggling it inside the buffer + if last_stored > value { + return self.push_and_slide(value); + } + } + self.buffer[self.in_index] = value; + self.in_index = (self.in_index + 1) % self.capacity; + true + } + + fn prune_lt(&mut self, value: u64) -> usize { + if self.is_empty() { + return 0; + } + let mut number_of_pruned = 0; + while self.in_index != self.out_index { + if self.buffer[self.out_index] >= value { + break; + } + self.out_index = (self.out_index + 1) % self.capacity; + number_of_pruned += 1; + } + number_of_pruned + } + + #[cfg(test)] + fn to_vec(&self) -> Vec { + let mut vec = Vec::new(); + let mut local_out = self.out_index; + while self.in_index != local_out { + vec.push(self.buffer[local_out]); + local_out = (local_out + 1) % self.capacity; + } + vec + } +} + +#[derive(Debug)] +pub(crate) struct RateLimiter { + /// window duration. + window_ms: u64, + /// Log of unix epoch time in ms when requests were made. + buffer: Buffer, +} + +impl RateLimiter { + //ctor + pub(crate) fn new( + max_requests: usize, + window_duration: TimeDiff, + ) -> Result { + if max_requests == 0 { + // We consider 0-max_requests as a misconfiguration + return Err(RateLimiterError::EmptyWindowNotAllowed); + } + let window_duration_in_ms = window_duration.millis(); + if window_duration_in_ms >= MAX_WINDOW_DURATION_MS { + return Err(RateLimiterError::WindowDurationTooLarge); + } + let window_duration_in_ms = window_duration.millis(); + if window_duration_in_ms == 0 { + return Err(RateLimiterError::WindowDurationTooSmall); + } + Ok(RateLimiter { + window_ms: window_duration_in_ms, + buffer: Buffer::new(max_requests), + }) + } + + pub(crate) fn throttle(&mut self) -> LimiterResponse { + self.internal_throttle(Timestamp::now().millis()) + } + + fn internal_throttle(&mut self, now: u64) -> LimiterResponse { + let is_full = self.buffer.is_full(); + if !is_full { + self.buffer.push(now); + return LimiterResponse::Allowed; + } else { + //The following subtraction could theoretically not fit in unsigned, but in real-life + // cases we limit the window duration to 1 hour (it's checked in ctor). So unless + // someone calls it from the perspective of 1970, it should be fine. + let no_of_pruned = self.buffer.prune_lt(now - self.window_ms); + if no_of_pruned == 0 { + //No pruning was done, so we are still at max_requests + return LimiterResponse::Throttled; + } + } + self.buffer.push(now); + LimiterResponse::Allowed + } +} + +#[cfg(test)] +mod tests { + use casper_types::TimeDiff; + + use super::*; + + #[test] + fn sliding_window_should_validate_ctor_inputs() { + assert!(RateLimiter::new(0, TimeDiff::from_millis(1000)).is_err()); + assert!(RateLimiter::new(10, TimeDiff::from_millis(MAX_WINDOW_DURATION_MS + 1)).is_err()); + assert!(RateLimiter::new(10, TimeDiff::from_millis(0)).is_err()); + } + + #[test] + fn sliding_window_throttle_should_limit_requests() { + let mut rate_limiter = rate_limiter(); + let t_1 = 10000_u64; + let t_2 = 10002_u64; + let t_3 = 10003_u64; + + assert_eq!( + rate_limiter.internal_throttle(t_1), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_2), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_3), + LimiterResponse::Throttled + ); + } + + #[test] + fn sliding_window_throttle_should_limit_requests_on_burst() { + let mut rate_limiter = rate_limiter(); + let t_1 = 10000; + assert_eq!( + rate_limiter.internal_throttle(t_1), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_1), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_1), + LimiterResponse::Throttled + ); + } + + #[test] + fn sliding_window_should_slide_away_from_old_checks() { + let mut rate_limiter = rate_limiter(); + let t_1 = 10000_u64; + let t_2 = 10002_u64; + let t_3 = 11002_u64; + assert_eq!( + rate_limiter.internal_throttle(t_1), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_2), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_3), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_3), + LimiterResponse::Throttled + ); + } + + #[test] + fn sliding_window_should_take_past_timestamp() { + let mut rate_limiter = rate_limiter(); + let t_1 = 10000_u64; + let t_2 = 9999_u64; + let t_3 = 10001_u64; + assert_eq!( + rate_limiter.internal_throttle(t_1), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_2), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_3), + LimiterResponse::Throttled + ); + } + + #[test] + fn sliding_window_should_anneal_timestamp_from_past_() { + let mut rate_limiter = rate_limiter(); + let t_1 = 10000_u64; + let t_2 = 9999_u64; + let t_3 = 12001_u64; + let t_4 = 12002_u64; + assert_eq!( + rate_limiter.internal_throttle(t_1), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_2), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_3), + LimiterResponse::Allowed + ); + assert_eq!( + rate_limiter.internal_throttle(t_4), + LimiterResponse::Allowed + ); + } + + #[test] + fn buffer_should_saturate_with_values() { + let mut buffer = Buffer::new(3); + assert!(buffer.push(1)); + assert!(buffer.push(2)); + assert!(buffer.push(3)); + assert!(!buffer.push(4)); + assert_eq!(buffer.to_vec(), vec![1_u64, 2_u64, 3_u64]); + } + + #[test] + fn buffer_should_prune() { + let mut buffer = Buffer::new(3); + assert!(buffer.push(1)); + assert!(buffer.push(2)); + assert!(buffer.push(3)); + assert_eq!(buffer.prune_lt(3), 2); + assert!(buffer.push(4)); + assert_eq!(buffer.to_vec(), vec![3_u64, 4_u64]); + assert_eq!(buffer.prune_lt(5), 2); + + assert!(buffer.push(1)); + assert!(buffer.push(2)); + assert!(buffer.push(3)); + assert_eq!(buffer.prune_lt(5), 3); + assert!(buffer.to_vec().is_empty()); + + assert!(buffer.push(5)); + assert!(buffer.push(6)); + assert!(buffer.push(7)); + assert_eq!(buffer.to_vec(), vec![5, 6, 7]); + } + + #[test] + fn push_and_slide_should_keep_order() { + let mut buffer = Buffer::new(5); + assert!(buffer.push(1)); + assert!(buffer.push(2)); + assert!(buffer.push(7)); + assert!(buffer.push(6)); + assert_eq!(buffer.to_vec(), vec![1, 2, 6, 7]); + assert_eq!(buffer.prune_lt(7), 3); + assert_eq!(buffer.to_vec(), vec![7]); + + let mut buffer = Buffer::new(4); + assert!(buffer.push(2)); + assert!(buffer.push(8)); + assert!(buffer.push(5)); + assert!(buffer.push(1)); + assert_eq!(buffer.to_vec(), vec![1, 2, 5, 8]); + assert_eq!(buffer.prune_lt(5), 2); + assert_eq!(buffer.to_vec(), vec![5, 8]); + + let mut buffer = Buffer::new(4); + assert!(buffer.push(2)); + assert!(buffer.push(8)); + assert!(buffer.push(2)); + assert!(buffer.push(1)); + assert_eq!(buffer.to_vec(), vec![1, 2, 2, 8]); + + let mut buffer = Buffer::new(4); + assert!(buffer.push(2)); + assert!(buffer.push(8)); + assert!(buffer.push(3)); + assert!(buffer.push(1)); + assert_eq!(buffer.prune_lt(2), 1); + assert!(buffer.push(0)); + assert_eq!(buffer.to_vec(), vec![0, 2, 3, 8]); + + let mut buffer = Buffer::new(4); + assert!(buffer.push(8)); + assert!(buffer.push(7)); + assert!(buffer.push(6)); + assert!(buffer.push(5)); + assert_eq!(buffer.prune_lt(7), 2); + assert!(buffer.push(9)); + assert!(buffer.push(10)); + assert_eq!(buffer.prune_lt(9), 2); + assert!(buffer.push(11)); + assert!(buffer.push(1)); + assert_eq!(buffer.to_vec(), vec![1, 9, 10, 11]); + } + + fn rate_limiter() -> RateLimiter { + RateLimiter::new(2, TimeDiff::from_millis(1000)).unwrap() + } +} diff --git a/node/src/components/binary_port/tests.rs b/node/src/components/binary_port/tests.rs index 25a76127c4..000254fa68 100644 --- a/node/src/components/binary_port/tests.rs +++ b/node/src/components/binary_port/tests.rs @@ -4,7 +4,9 @@ use derive_more::From; use rand::Rng; use serde::Serialize; -use casper_binary_port::{BinaryRequest, BinaryResponse, GetRequest, GlobalStateRequest}; +use casper_binary_port::{ + BinaryRequest, BinaryResponse, GetRequest, GlobalStateEntityQualifier, GlobalStateRequest, +}; use casper_types::{ BlockHeader, Digest, GlobalStateIdentifier, KeyTag, PublicKey, Timestamp, Transaction, @@ -30,7 +32,7 @@ use prometheus::Registry; use thiserror::Error as ThisError; use casper_binary_port::ErrorCode; -use casper_types::{testing::TestRng, Chainspec, ChainspecRawBytes, ProtocolVersion}; +use casper_types::{testing::TestRng, Chainspec, ChainspecRawBytes}; use crate::{ components::{ @@ -164,8 +166,6 @@ async fn run_test_case( allow_request_get_trie, allow_request_speculative_exec, max_message_size_bytes: 1024, - client_request_limit: 2, - client_request_buffer_size: 16, max_connections: 2, ..Default::default() }; @@ -251,9 +251,6 @@ impl Reactor for MockReactor { self.binary_port.handle_event(effect_builder, rng, event), ), Event::ControlAnnouncement(_) => panic!("unexpected control announcement"), - Event::ReactorInfoRequest(ReactorInfoRequest::ProtocolVersion { responder }) => { - responder.respond(ProtocolVersion::V1_0_0).ignore() - } Event::ContractRuntimeRequest(_) | Event::ReactorInfoRequest(_) => { // We're only interested if the binary port actually created a request to Contract // Runtime component, but we're not interested in the result. @@ -384,16 +381,18 @@ impl ReactorEvent for Event { fn all_values_request() -> BinaryRequest { let state_identifier = GlobalStateIdentifier::StateRootHash(Digest::hash([1u8; 32])); - BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::AllItems { - state_identifier: Some(state_identifier), - key_tag: KeyTag::Account, - }))) + BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(state_identifier), + GlobalStateEntityQualifier::AllItems { + key_tag: KeyTag::Account, + }, + )))) } fn trie_request() -> BinaryRequest { - BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::Trie { + BinaryRequest::Get(GetRequest::Trie { trie_key: Digest::hash([1u8; 32]), - }))) + }) } fn try_speculative_exec_request(rng: &mut TestRng) -> BinaryRequest { diff --git a/node/src/components/block_accumulator/block_acceptor.rs b/node/src/components/block_accumulator/block_acceptor.rs index 2b80a455fd..518aa8b873 100644 --- a/node/src/components/block_accumulator/block_acceptor.rs +++ b/node/src/components/block_accumulator/block_acceptor.rs @@ -286,27 +286,26 @@ impl BlockAcceptor { }, faulty_senders, ); - } else { - if meta_block - .state - .register_as_stored() - .was_already_registered() - { - error!( - %block_hash, - block_height = meta_block.block.height(), - meta_block_state = ?meta_block.state, - "should not store the same block more than once" - ); - } - return ( - ShouldStore::SufficientlySignedBlock { - meta_block: meta_block.clone(), - block_signatures, - }, - faulty_senders, + } + if meta_block + .state + .register_as_stored() + .was_already_registered() + { + error!( + %block_hash, + block_height = meta_block.block.height(), + meta_block_state = ?meta_block.state, + "should not store the same block more than once" ); } + return ( + ShouldStore::SufficientlySignedBlock { + meta_block: meta_block.clone(), + block_signatures, + }, + faulty_senders, + ); } } diff --git a/node/src/components/block_synchronizer/execution_results_acquisition.rs b/node/src/components/block_synchronizer/execution_results_acquisition.rs index 833575ed41..f99afb5483 100644 --- a/node/src/components/block_synchronizer/execution_results_acquisition.rs +++ b/node/src/components/block_synchronizer/execution_results_acquisition.rs @@ -3,7 +3,7 @@ mod tests; use std::{ collections::HashMap, - fmt::{self, Debug, Display, Formatter}, + fmt::{self, Display, Formatter}, }; use datasize::DataSize; diff --git a/node/src/components/block_validator.rs b/node/src/components/block_validator.rs index e0b1bc3c13..56019b8cbe 100644 --- a/node/src/components/block_validator.rs +++ b/node/src/components/block_validator.rs @@ -552,7 +552,7 @@ impl BlockValidator { Err(error) => warn!(%transaction_hash, %error, "could not fetch transaction"), } match result { - Ok(FetchedData::FromStorage { item }) | Ok(FetchedData::FromPeer { item, .. }) => { + Ok(FetchedData::FromStorage { item } | FetchedData::FromPeer { item, .. }) => { let item_hash = item.hash(); if item_hash != transaction_hash { // Hard failure - change state to Invalid. @@ -670,7 +670,7 @@ impl BlockValidator { } } match result { - Ok(FetchedData::FromStorage { .. }) | Ok(FetchedData::FromPeer { .. }) => { + Ok(FetchedData::FromStorage { .. } | FetchedData::FromPeer { .. }) => { let mut effects = Effects::new(); for state in self.validation_states.values_mut() { let responders = state.try_add_signature(&finality_signature_id); diff --git a/node/src/components/block_validator/state.rs b/node/src/components/block_validator/state.rs index fb9ffc2c42..0ea89037e0 100644 --- a/node/src/components/block_validator/state.rs +++ b/node/src/components/block_validator/state.rs @@ -191,7 +191,7 @@ impl BlockValidationState { block: &ProposedBlock, config: &TransactionConfig, ) -> Result<(), ()> { - for supported_lane in config.transaction_v1_config.get_supported_categories() { + for supported_lane in config.transaction_v1_config.get_supported_lanes() { let transactions = block.value().count(Some(supported_lane)); let lane_count_limit = config .transaction_v1_config @@ -550,7 +550,7 @@ mod tests { use rand::Rng; use casper_types::{ - testing::TestRng, ChainspecRawBytes, TimeDiff, Transaction, TransactionHash, + testing::TestRng, ChainspecRawBytes, TimeDiff, Transaction, TransactionHash, TransactionV1, }; use super::{super::tests::*, *}; @@ -611,8 +611,8 @@ mod tests { let mut ret = vec![]; for _ in 0..auction_count { let txn = new_auction(self.rng, timestamp, ttl); - ret.push((TransactionHash::V1(*txn.hash()), txn.approvals().clone())); - self.transactions.push(Transaction::V1(txn)); + ret.push((txn.hash(), txn.approvals().clone())); + self.transactions.push(txn); } ret }; @@ -620,9 +620,11 @@ mod tests { let install_upgrade_for_block = { let mut ret = vec![]; for _ in 0..install_upgrade_count { - let txn = new_install_upgrade(self.rng, timestamp, ttl); - ret.push((TransactionHash::V1(*txn.hash()), txn.approvals().clone())); - self.transactions.push(Transaction::V1(txn)); + let txn: Transaction = + TransactionV1::random_install_upgrade(self.rng, Some(timestamp), Some(ttl)) + .into(); + ret.push((txn.hash(), txn.approvals().clone())); + self.transactions.push(txn); } ret }; diff --git a/node/src/components/block_validator/tests.rs b/node/src/components/block_validator/tests.rs index 9362230ae0..bc07d91153 100644 --- a/node/src/components/block_validator/tests.rs +++ b/node/src/components/block_validator/tests.rs @@ -8,8 +8,8 @@ use casper_types::{ bytesrepr::Bytes, runtime_args, system::standard_payment::ARG_AMOUNT, testing::TestRng, Block, BlockSignatures, BlockSignaturesV2, Chainspec, ChainspecRawBytes, Deploy, ExecutableDeployItem, FinalitySignatureV2, RuntimeArgs, SecretKey, TestBlockBuilder, TimeDiff, Transaction, - TransactionV1, TransactionV1Config, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, - U512, + TransactionHash, TransactionId, TransactionV1, TransactionV1Config, AUCTION_LANE_ID, + INSTALL_UPGRADE_LANE_ID, LARGE_WASM_LANE_ID, MINT_LANE_ID, U512, }; use crate::{ @@ -25,8 +25,6 @@ use crate::{ use super::*; -const LARGE_LANE_ID: u8 = 3; - #[derive(Debug, From)] enum ReactorEvent { #[from] @@ -154,7 +152,7 @@ pub(super) fn new_proposed_block_with_cited_signatures( INSTALL_UPGRADE_LANE_ID, install_upgrade.into_iter().collect(), ); - ret.insert(LARGE_LANE_ID, standard.into_iter().collect()); + ret.insert(LARGE_WASM_LANE_ID, standard.into_iter().collect()); ret }; let block_payload = BlockPayload::new(transactions, vec![], cited_signatures, true, 1u8); @@ -182,23 +180,29 @@ pub(super) fn new_v1_standard( rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff, -) -> TransactionV1 { - TransactionV1::random_wasm(rng, Some(timestamp), Some(ttl)) +) -> Transaction { + let transaction_v1 = TransactionV1::random_wasm(rng, Some(timestamp), Some(ttl)); + Transaction::V1(transaction_v1) } -pub(super) fn new_auction(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> TransactionV1 { - TransactionV1::random_auction(rng, Some(timestamp), Some(ttl)) +pub(super) fn new_auction(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> Transaction { + let transaction_v1 = TransactionV1::random_auction(rng, Some(timestamp), Some(ttl)); + Transaction::V1(transaction_v1) } pub(super) fn new_install_upgrade( rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff, -) -> TransactionV1 { - TransactionV1::random_install_upgrade(rng, Some(timestamp), Some(ttl)) +) -> Transaction { + TransactionV1::random_install_upgrade(rng, Some(timestamp), Some(ttl)).into() } -pub(super) fn new_legacy_deploy(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> Deploy { +pub(super) fn new_legacy_deploy( + rng: &mut TestRng, + timestamp: Timestamp, + ttl: TimeDiff, +) -> Transaction { let secret_key = SecretKey::random(rng); let chain_name = "chain".to_string(); let payment = ExecutableDeployItem::ModuleBytes { @@ -223,21 +227,22 @@ pub(super) fn new_legacy_deploy(rng: &mut TestRng, timestamp: Timestamp, ttl: Ti &secret_key, None, ) + .into() } pub(super) fn new_v1_transfer( rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff, -) -> TransactionV1 { - TransactionV1::random_transfer(rng, Some(timestamp), Some(ttl)) +) -> Transaction { + TransactionV1::random_transfer(rng, Some(timestamp), Some(ttl)).into() } pub(super) fn new_legacy_transfer( rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff, -) -> Deploy { +) -> Transaction { let secret_key = SecretKey::random(rng); let chain_name = "chain".to_string(); let payment = ExecutableDeployItem::ModuleBytes { @@ -261,21 +266,22 @@ pub(super) fn new_legacy_transfer( &secret_key, None, ) + .into() } pub(super) fn new_mint(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> Transaction { if rng.gen() { - new_v1_transfer(rng, timestamp, ttl).into() + new_v1_transfer(rng, timestamp, ttl) } else { - new_legacy_transfer(rng, timestamp, ttl).into() + new_legacy_transfer(rng, timestamp, ttl) } } pub(super) fn new_standard(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> Transaction { if rng.gen() { - new_v1_standard(rng, timestamp, ttl).into() + new_v1_standard(rng, timestamp, ttl) } else { - new_legacy_deploy(rng, timestamp, ttl).into() + new_legacy_deploy(rng, timestamp, ttl) } } @@ -286,8 +292,8 @@ pub(super) fn new_non_transfer( ) -> Transaction { match rng.gen_range(0..3) { 0 => new_standard(rng, timestamp, ttl), - 1 => new_install_upgrade(rng, timestamp, ttl).into(), - 2 => new_auction(rng, timestamp, ttl).into(), + 1 => new_install_upgrade(rng, timestamp, ttl), + 2 => new_auction(rng, timestamp, ttl), _ => unreachable!(), } } @@ -795,28 +801,28 @@ async fn ttl() { new_non_transfer(&mut rng, 900.into(), ttl), ]; let transfers: Vec = vec![ - new_v1_transfer(&mut rng, 1000.into(), ttl).into(), - new_v1_transfer(&mut rng, 900.into(), ttl).into(), + new_v1_transfer(&mut rng, 1000.into(), ttl), + new_v1_transfer(&mut rng, 900.into(), ttl), ]; let mut transactions_context = ValidationContext::new() .with_num_validators(&mut rng, 1) .with_transactions(transactions.clone()) .with_count_limits(Some(3000), Some(3000), Some(3000), Some(3000)) - .with_block_gas_limit(10_300_000_000_000) + .with_block_gas_limit(15_300_000_000_000) .include_all_transactions(); let mut transfers_context = ValidationContext::new() .with_num_validators(&mut rng, 1) .with_transfers(transfers.clone()) .with_count_limits(Some(3000), Some(3000), Some(3000), Some(3000)) - .with_block_gas_limit(10_300_000_000_000) + .with_block_gas_limit(15_300_000_000_000) .include_all_transfers(); let mut both_context = ValidationContext::new() .with_num_validators(&mut rng, 1) .with_transactions(transactions) .with_transfers(transfers) .with_count_limits(Some(3000), Some(3000), Some(3000), Some(3000)) - .with_block_gas_limit(10_300_000_000_000) + .with_block_gas_limit(15_300_000_000_000) .include_all_transactions() .include_all_transfers(); @@ -854,10 +860,10 @@ async fn transfer_transaction_mixup_and_replay() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); // First we make sure that our transfers and transactions would normally be valid. let transactions = vec![deploy_legacy.clone(), transaction_v1.clone()]; @@ -985,7 +991,7 @@ async fn should_fetch_from_multiple_peers() { .map(|i| new_non_transfer(&mut rng, (900 + i).into(), ttl)) .collect_vec(); let transfers = (0..peer_count) - .map(|i| Transaction::V1(new_v1_transfer(&mut rng, (1000 + i).into(), ttl))) + .map(|i| new_v1_transfer(&mut rng, (1000 + i).into(), ttl)) .collect_vec(); // Assemble the block to be validated. @@ -1156,10 +1162,10 @@ async fn should_validate_block_with_signatures() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); let context = ValidationContext::new() .with_num_validators(&mut rng, 3) @@ -1183,10 +1189,10 @@ async fn should_fetch_missing_signature() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); let context = ValidationContext::new() .with_num_validators(&mut rng, 3) @@ -1213,10 +1219,10 @@ async fn should_fail_if_unable_to_fetch_signature() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); let context = ValidationContext::new() .with_num_validators(&mut rng, 3) @@ -1263,10 +1269,10 @@ async fn should_validate_with_delayed_block() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); let context = ValidationContext::new() .with_num_validators(&mut rng, 3) diff --git a/node/src/components/consensus/era_supervisor.rs b/node/src/components/consensus/era_supervisor.rs index c6f20cba45..dd20214e62 100644 --- a/node/src/components/consensus/era_supervisor.rs +++ b/node/src/components/consensus/era_supervisor.rs @@ -389,7 +389,7 @@ impl EraSupervisor { our_id.clone(), ); let instance_id = self.era(era_id).consensus.instance_id(); - let unit_hash_file = self.unit_file(instance_id); + let unit_hash_file = self.protocol_state_file(instance_id); self.era_mut(era_id).consensus.activate_validator( our_id, secret, @@ -510,6 +510,7 @@ impl EraSupervisor { .collect(); // Create and insert the new era instance. + let protocol_state_file = self.protocol_state_file(&instance_id); let (consensus, mut outcomes) = match self.chainspec.core_config.consensus_protocol { ConsensusProtocolName::Highway => HighwayProtocol::new_boxed( instance_id, @@ -522,6 +523,7 @@ impl EraSupervisor { start_time, seed, now, + Some(protocol_state_file), ), ConsensusProtocolName::Zug => Zug::new_boxed( instance_id, @@ -534,7 +536,7 @@ impl EraSupervisor { start_time, seed, now, - self.unit_file(&instance_id), + protocol_state_file, ), }; @@ -579,7 +581,7 @@ impl EraSupervisor { self.validator_matrix.secret_signing_key().clone(), our_id.clone(), ); - let unit_hash_file = self.unit_file(&instance_id); + let unit_hash_file = self.protocol_state_file(&instance_id); outcomes.extend(self.era_mut(era_id).consensus.activate_validator( our_id, secret, @@ -624,7 +626,7 @@ impl EraSupervisor { } }); for instance_id in removed_instance_ids { - if let Err(err) = fs::remove_file(self.unit_file(&instance_id)) { + if let Err(err) = fs::remove_file(self.protocol_state_file(&instance_id)) { match err.kind() { io::ErrorKind::NotFound => {} err => warn!(?err, "could not delete unit hash file"), @@ -637,7 +639,7 @@ impl EraSupervisor { } /// Returns the path to the era's unit file. - fn unit_file(&self, instance_id: &Digest) -> PathBuf { + fn protocol_state_file(&self, instance_id: &Digest) -> PathBuf { self.unit_files_folder.join(format!( "unit_{:?}_{}.dat", instance_id, diff --git a/node/src/components/consensus/highway_core/active_validator.rs b/node/src/components/consensus/highway_core/active_validator.rs index 82919a8148..3943091a01 100644 --- a/node/src/components/consensus/highway_core/active_validator.rs +++ b/node/src/components/consensus/highway_core/active_validator.rs @@ -1,9 +1,6 @@ use std::{ fmt::{self, Debug}, - fs::{self, File}, - io::{self, Read, Write}, iter, - path::{Path, PathBuf}, }; use datasize::DataSize; @@ -73,10 +70,6 @@ where next_timer: Timestamp, /// Panorama and context for a block we are about to propose when we get a consensus value. next_proposal: Option<(BlockContext, Panorama)>, - /// The path to the file storing the hash of our latest known unit (if any). - unit_file: Option, - /// The last known unit created by us. - own_last_unit: Option>, /// The target fault tolerance threshold. The validator pauses (i.e. doesn't create new units) /// if not enough validators are online to finalize values at this FTT. target_ftt: Weight, @@ -104,28 +97,15 @@ impl ActiveValidator { current_time: Timestamp, start_time: Timestamp, state: &State, - unit_file: Option, target_ftt: Weight, instance_id: C::InstanceId, ) -> (Self, Vec>) { - let own_last_unit = unit_file - .as_ref() - .map(read_last_unit) - .transpose() - .map_err(|err| match err.kind() { - io::ErrorKind::NotFound => (), - _ => panic!("got an error reading unit file {:?}: {:?}", unit_file, err), - }) - .ok() - .flatten(); let mut av = ActiveValidator { vidx, secret, next_round_len: state.params().init_round_len(), next_timer: state.params().start_timestamp(), next_proposal: None, - unit_file, - own_last_unit, target_ftt, paused: false, }; @@ -134,34 +114,6 @@ impl ActiveValidator { (av, effects) } - /// Returns whether validator's protocol state is fully synchronized and it's safe to start - /// creating units. - /// - /// If validator restarted within an era, it most likely had created units before that event. It - /// cannot start creating new units until its state is fully synchronized, otherwise it will - /// most likely equivocate. - fn can_vote(&self, state: &State) -> bool { - self.own_last_unit - .as_ref() - .map_or(true, |swunit| state.has_unit(&swunit.hash())) - } - - /// Returns whether validator's protocol state is synchronized up until the panorama of its own - /// last unit. - pub(crate) fn is_own_last_unit_panorama_sync(&self, state: &State) -> bool { - self.own_last_unit.as_ref().map_or(true, |swunit| { - swunit - .wire_unit() - .panorama - .iter_correct_hashes() - .all(|hash| state.has_unit(hash)) - }) - } - - pub(crate) fn take_own_last_unit(&mut self) -> Option> { - self.own_last_unit.take() - } - /// Sets the next round length to the new value. pub(crate) fn set_round_len(&mut self, new_round_len: TimeDiff) { self.next_round_len = new_round_len; @@ -430,10 +382,6 @@ impl ActiveValidator { if value.is_none() && !panorama.has_correct() { return None; // Wait for the first proposal before creating a unit without a value. } - if !self.can_vote(state) { - info!(?self.own_last_unit, "not voting - last own unit unknown"); - return None; - } if let Some((prop_context, _)) = self.next_proposal.take() { warn!(?prop_context, "canceling proposal due to unit"); } @@ -470,12 +418,6 @@ impl ActiveValidator { } .into_hashed(); let swunit = SignedWireUnit::new(hwunit, &self.secret); - write_last_unit(&self.unit_file, swunit.clone()).unwrap_or_else(|err| { - panic!( - "should successfully write unit's hash to {:?}, got {:?}", - self.unit_file, err - ) - }); Some(swunit) } @@ -607,9 +549,6 @@ impl ActiveValidator { /// Returns whether the incoming vertex was signed by our key even though we don't have it yet. /// This can only happen if another node is running with the same signing key. pub(crate) fn is_doppelganger_vertex(&self, vertex: &Vertex, state: &State) -> bool { - if !self.can_vote(state) { - return false; - } match vertex { Vertex::Unit(swunit) => { // If we already have the unit in our local state, @@ -641,45 +580,10 @@ impl ActiveValidator { } } -pub(crate) fn read_last_unit(path: P) -> io::Result> -where - C: Context, - P: AsRef, -{ - let mut file = File::open(path)?; - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - Ok(serde_json::from_slice(&bytes)?) -} - -pub(crate) fn write_last_unit( - unit_file: &Option, - swunit: SignedWireUnit, -) -> io::Result<()> { - // If there is no unit_file set, do not write to it - let unit_file = if let Some(file) = unit_file.as_ref() { - file - } else { - return Ok(()); - }; - - // Create the file (and its parents) as necessary - if let Some(parent_directory) = unit_file.parent() { - fs::create_dir_all(parent_directory)?; - } - let mut file = File::create(unit_file)?; - - // Finally, write the data to file we created - let bytes = serde_json::to_vec(&swunit)?; - - file.write_all(&bytes) -} - #[cfg(test)] #[allow(clippy::arithmetic_side_effects)] // Overflows in tests panic anyway. mod tests { use std::{collections::BTreeSet, fmt::Debug}; - use tempfile::tempdir; use crate::components::consensus::{ highway_core::highway_testing::TEST_INSTANCE_ID, @@ -704,14 +608,6 @@ mod tests { panic!("Unexpected effect: {:?}", self); } } - - fn unwrap_timer(self) -> Timestamp { - if let Eff::ScheduleTimer(timestamp) = self { - timestamp - } else { - panic!("expected `ScheduleTimer`, got: {:?}", self) - } - } } struct TestState { @@ -747,7 +643,6 @@ mod tests { start_time, start_time, &state, - None, target_ftt, TEST_INSTANCE_ID, ); @@ -983,7 +878,6 @@ mod tests { 410.into(), 410.into(), &state, - None, Weight(2), TEST_INSTANCE_ID, ); @@ -1006,7 +900,6 @@ mod tests { 410.into(), 410.into(), &state, - None, Weight(2), TEST_INSTANCE_ID, ); @@ -1021,110 +914,4 @@ mod tests { state.add_ping(ALICE, 500.into()); assert!(!active_validator.is_doppelganger_vertex(&ping, &state)); } - - #[test] - fn waits_until_synchronized() -> Result<(), AddUnitError> { - let instance_id = TEST_INSTANCE_ID; - let mut state = State::new_test(&[Weight(3)], 0); - let a0 = { - let a0 = add_unit!(state, ALICE, 0xB0; N)?; - state.wire_unit(&a0, instance_id).unwrap() - }; - let a1 = { - let a1 = add_unit!(state, ALICE, None; a0.hash())?; - state.wire_unit(&a1, instance_id).unwrap() - }; - let a2 = { - let a2 = add_unit!(state, ALICE, None; a1.hash())?; - state.wire_unit(&a2, instance_id).unwrap() - }; - // Clean state. We want Alice to synchronize first. - state.retain_evidence_only(); - - let unit_file = { - let tmp_dir = tempdir().unwrap(); - let unit_files_folder = tmp_dir.path().to_path_buf(); - Some(unit_files_folder.join(format!("unit_{:?}.dat", instance_id))) - }; - - // Store `a2` unit as the Alice's last unit. - write_last_unit(&unit_file, a2.clone()).expect("storing unit should succeed"); - - // Alice's last unit is `a2` but `State` is empty. She must synchronize first. - let (mut alice, alice_init_effects) = ActiveValidator::new( - ALICE, - TestSecret(ALICE.0), - 410.into(), - 410.into(), - &state, - unit_file, - Weight(2), - TEST_INSTANCE_ID, - ); - - let mut next_proposal_timer = match &*alice_init_effects { - &[Effect::ScheduleTimer(timestamp), Effect::NewVertex(ValidVertex(Vertex::Ping(_)))] - if timestamp == 416.into() => - { - timestamp - } - other => panic!("unexpected effects {:?}", other), - }; - - // Alice has to synchronize up until `a2` (including) before she starts proposing. - for unit in vec![a0, a1, a2.clone()] { - next_proposal_timer = - assert_no_proposal(&mut alice, &state, instance_id, next_proposal_timer); - state.add_unit(unit)?; - } - - // After synchronizing the protocol state up until `last_own_unit`, Alice can now propose a - // new block. - let bctx = match &*alice.handle_timer(next_proposal_timer, &state, instance_id) { - [Eff::ScheduleTimer(_), Eff::RequestNewBlock(bctx, _)] => bctx.clone(), - effects => panic!("unexpected effects {:?}", effects), - }; - - let proposal_wunit = - unwrap_single(&alice.propose(0xC0FFEE, bctx, &state, instance_id)).unwrap_unit(); - assert_eq!( - proposal_wunit.wire_unit().seq_number, - a2.wire_unit().seq_number + 1, - "new unit should have correct seq_number" - ); - assert_eq!( - proposal_wunit.wire_unit().panorama, - panorama!(a2.hash()), - "new unit should cite the latest unit" - ); - - Ok(()) - } - - // Triggers new proposal by `validator` and verifies that it's empty – no block was proposed. - // Captures the next witness timer and calls the `validator` with that to return the timer for - // the next proposal. - fn assert_no_proposal( - validator: &mut ActiveValidator, - state: &State, - instance_id: u64, - proposal_timer: Timestamp, - ) -> Timestamp { - let (witness_timestamp, bctx) = - match &*validator.handle_timer(proposal_timer, state, instance_id) { - [Eff::ScheduleTimer(witness_timestamp), Eff::RequestNewBlock(bctx, _)] => { - (*witness_timestamp, bctx.clone()) - } - effects => panic!("unexpected effects {:?}", effects), - }; - - let effects = validator.propose(0xC0FFEE, bctx, state, instance_id); - assert!( - effects.is_empty(), - "should not propose blocks until its dependencies are synchronized: {:?}", - effects - ); - - unwrap_single(&validator.handle_timer(witness_timestamp, state, instance_id)).unwrap_timer() - } } diff --git a/node/src/components/consensus/highway_core/highway.rs b/node/src/components/consensus/highway_core/highway.rs index a430678ab2..56624e7805 100644 --- a/node/src/components/consensus/highway_core/highway.rs +++ b/node/src/components/consensus/highway_core/highway.rs @@ -10,6 +10,7 @@ pub use vertex::{ use std::path::PathBuf; use datasize::DataSize; +use serde::{Deserialize, Serialize}; use thiserror::Error; use tracing::{debug, error, info, trace, warn}; @@ -24,7 +25,10 @@ use crate::components::consensus::{ state::{Fault, Observation, State, UnitError}, }, traits::Context, - utils::{Validator, ValidatorIndex, Validators, Weight}, + utils::{ + wal::{ReadWal, WalEntry, WriteWal}, + Validator, ValidatorIndex, Validators, Weight, + }, }; /// If a lot of rounds were skipped between two blocks, log at most this many. @@ -102,7 +106,11 @@ impl From> for Vertex { /// /// Note that this must only be added to the `Highway` instance that created it. Can cause a panic /// or inconsistent state otherwise. -#[derive(Clone, DataSize, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, DataSize, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(bound( + serialize = "C::Hash: Serialize", + deserialize = "C::Hash: Deserialize<'de>", +))] pub(crate) struct ValidVertex(pub(crate) Vertex) where C: Context; @@ -134,6 +142,18 @@ pub(crate) enum GetDepOutcome { Evidence(C::ValidatorId), } +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(bound( + serialize = "C::Hash: Serialize", + deserialize = "C::Hash: Deserialize<'de>", +))] +struct HighwayWalEntry { + vertex: ValidVertex, + timestamp: Timestamp, +} + +impl WalEntry for HighwayWalEntry {} + /// An instance of the Highway protocol, containing its local state. /// /// Both observers and active validators must instantiate this, pass in all incoming vertices from @@ -152,6 +172,8 @@ where state: State, /// The state of an active validator, who is participating and creating new vertices. active_validator: Option>, + /// The path to the protocol state file. + write_wal: Option>>, } impl Highway { @@ -167,17 +189,65 @@ impl Highway { instance_id: C::InstanceId, validators: Validators, params: Params, + protocol_state_file: Option, ) -> Highway { info!(%validators, instance=%instance_id, "creating Highway instance"); let weights = validators.iter().map(Validator::weight); let banned = validators.iter_banned_idx(); let cannot_propose = validators.iter_cannot_propose_idx(); let state = State::new(weights, params, banned, cannot_propose); - Highway { + let (write_wal, entries) = if let Some(protocol_state_file) = protocol_state_file.as_ref() { + let entries = Self::read_stored_vertices(protocol_state_file); + let write_wal = match WriteWal::>::new(protocol_state_file) { + Ok(wal) => Some(wal), + Err(err) => { + panic!("couldn't open WriteWal: {}", err); + } + }; + (write_wal, entries) + } else { + (None, vec![]) + }; + let mut result = Highway { instance_id, validators, state, active_validator: None, + write_wal, + }; + result.restore_state(entries); + result + } + + fn read_stored_vertices(protocol_state_file: &PathBuf) -> Vec> { + let mut read_wal = match ReadWal::>::new(protocol_state_file) { + Ok(wal) => wal, + Err(err) => { + panic!("couldn't open ReadWal: {}", err); + } + }; + let mut entries = vec![]; + loop { + match read_wal.read_next_entry() { + Ok(Some(entry)) => { + entries.push(entry); + } + Ok(None) => { + break; + } + Err(err) => { + panic!("error while reading ReadWal: {}", err); + } + } + } + entries + } + + fn restore_state(&mut self, entries: Vec>) { + for entry in entries { + // we can safely ignore the effects - they were properly processed when persisting the + // vertex + self.add_valid_vertex(entry.vertex, entry.timestamp); } } @@ -190,7 +260,7 @@ impl Highway { id: C::ValidatorId, secret: C::ValidatorSecret, current_time: Timestamp, - unit_hash_file: Option, + _unit_hash_file: Option, target_ftt: Weight, ) -> Vec> { if self.active_validator.is_some() { @@ -211,7 +281,6 @@ impl Highway { current_time, start_time, &self.state, - unit_hash_file, target_ftt, self.instance_id, ); @@ -292,6 +361,15 @@ impl Highway { now: Timestamp, ) -> Vec> { if !self.has_vertex(&vertex) { + if let Some(ref mut wal) = self.write_wal { + let entry = HighwayWalEntry { + vertex: ValidVertex(vertex.clone()), + timestamp: now, + }; + if let Err(err) = wal.record_entry(&entry) { + error!("error recording entry: {}", err); + } + } match vertex { Vertex::Unit(unit) => self.add_valid_unit(unit, now), Vertex::Evidence(evidence) => self.add_evidence(evidence), @@ -638,30 +716,9 @@ impl Highway { }) .unwrap_or_default(); evidence_effects.extend(self.on_new_unit(&unit_hash, now)); - evidence_effects.extend(self.add_own_last_unit(now)); evidence_effects } - /// If validator's protocol state is synchronized, adds its own last unit (if any) to the - /// protocol state - fn add_own_last_unit(&mut self, now: Timestamp) -> Vec> { - self.map_active_validator( - |av, state| { - if av.is_own_last_unit_panorama_sync(state) { - if let Some(own_last_unit) = av.take_own_last_unit() { - vec![Effect::NewVertex(ValidVertex(Vertex::Unit(own_last_unit)))] - } else { - vec![] - } - } else { - vec![] - } - }, - now, - ) - .unwrap_or_default() - } - /// Adds endorsements to the state. If there are conflicting endorsements, `NewVertex` effects /// are returned containing evidence to prove them faulty. fn add_endorsements(&mut self, endorsements: Endorsements) -> Vec> { @@ -805,6 +862,7 @@ pub(crate) mod tests { validators: test_validators(), state, active_validator: None, + write_wal: None, }; let wunit = WireUnit { panorama: Panorama::new(WEIGHTS.len()), @@ -862,6 +920,7 @@ pub(crate) mod tests { validators: test_validators(), state: State::new_test(WEIGHTS, 0), active_validator: None, + write_wal: None, }; let vertex_end_a = Vertex::Endorsements(end_a); @@ -914,6 +973,7 @@ pub(crate) mod tests { validators: test_validators(), state, active_validator: None, + write_wal: None, }; let validate = |wunit0: &WireUnit, @@ -1012,6 +1072,7 @@ pub(crate) mod tests { validators: test_validators(), state, active_validator: None, + write_wal: None, }; // Ping by validator that is not bonded, with an index that is outside of boundaries of the @@ -1038,6 +1099,7 @@ pub(crate) mod tests { validators: test_validators(), state, active_validator: None, + write_wal: None, }; let _effects = diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index d41b533199..d67d53e835 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -935,7 +935,8 @@ impl HighwayTestHarnessBuilder { |(vid, secrets): (ValidatorId, &mut HashMap)| { let v_sec = secrets.remove(&vid).expect("Secret key should exist."); - let mut highway = Highway::new(instance_id, validators.clone(), params.clone()); + let mut highway = + Highway::new(instance_id, validators.clone(), params.clone(), None); let effects = highway.activate_validator(vid, v_sec, start_time, None, Weight(ftt)); let finality_detector = FinalityDetector::new(Weight(ftt)); diff --git a/node/src/components/consensus/highway_core/state.rs b/node/src/components/consensus/highway_core/state.rs index 2352ff8ac5..ef878c4052 100644 --- a/node/src/components/consensus/highway_core/state.rs +++ b/node/src/components/consensus/highway_core/state.rs @@ -452,7 +452,7 @@ impl State { } let idx = evidence.perpetrator(); match self.faults.get(&idx) { - Some(&Fault::Banned) | Some(&Fault::Direct(_)) => return false, + Some(&Fault::Banned | &Fault::Direct(_)) => return false, None | Some(&Fault::Indirect) => (), } // TODO: Should use Display, not Debug! diff --git a/node/src/components/consensus/highway_core/state/panorama.rs b/node/src/components/consensus/highway_core/state/panorama.rs index 320712541b..cc3fe8168c 100644 --- a/node/src/components/consensus/highway_core/state/panorama.rs +++ b/node/src/components/consensus/highway_core/state/panorama.rs @@ -215,7 +215,7 @@ impl Panorama { .find(|(_, unit)| unit.timestamp <= timestamp) .map(|(vh, _)| *vh) .map_or(Observation::None, Observation::Correct), - obs @ Observation::None | obs @ Observation::Faulty => obs.clone(), + obs @ (Observation::None | Observation::Faulty) => obs.clone(), }; Panorama::from(self.iter().map(obs_cutoff).collect_vec()) } diff --git a/node/src/components/consensus/highway_core/synchronizer/tests.rs b/node/src/components/consensus/highway_core/synchronizer/tests.rs index 694f609f0e..672264f25e 100644 --- a/node/src/components/consensus/highway_core/synchronizer/tests.rs +++ b/node/src/components/consensus/highway_core/synchronizer/tests.rs @@ -38,7 +38,7 @@ fn purge_vertices() { // A Highway instance that's just used to create PreValidatedVertex instances below. let util_highway = - Highway::::new(TEST_INSTANCE_ID, test_validators(), params.clone()); + Highway::::new(TEST_INSTANCE_ID, test_validators(), params.clone(), None); // Returns the WireUnit with the specified hash. let unit = |hash: u64| Vertex::Unit(state.wire_unit(&hash, TEST_INSTANCE_ID).unwrap()); @@ -50,7 +50,8 @@ fn purge_vertices() { // Create a synchronizer with a 0x20 ms timeout, and a Highway instance. let max_requests_for_vertex = 5; let mut sync = Synchronizer::::new(WEIGHTS.len(), TEST_INSTANCE_ID); - let mut highway = Highway::::new(TEST_INSTANCE_ID, test_validators(), params); + let mut highway = + Highway::::new(TEST_INSTANCE_ID, test_validators(), params, None); // At time 0x20, we receive c2, b0 and b1 — the latter ahead of their timestamp. // Since c2 is the first entry in the main queue, processing is scheduled. @@ -126,7 +127,7 @@ fn do_not_download_synchronized_dependencies() { let mut state = State::new(WEIGHTS, params.clone(), vec![], vec![]); let util_highway = - Highway::::new(TEST_INSTANCE_ID, test_validators(), params.clone()); + Highway::::new(TEST_INSTANCE_ID, test_validators(), params.clone(), None); // We use round exponent 0u8, so a round is 0x40 ms. With seed 0, Carol is the first leader. // @@ -153,7 +154,8 @@ fn do_not_download_synchronized_dependencies() { let max_requests_for_vertex = 5; let mut sync = Synchronizer::::new(WEIGHTS.len(), TEST_INSTANCE_ID); - let mut highway = Highway::::new(TEST_INSTANCE_ID, test_validators(), params); + let mut highway = + Highway::::new(TEST_INSTANCE_ID, test_validators(), params, None); let now = 0x20.into(); assert!(matches!( @@ -232,7 +234,7 @@ fn transitive_proposal_dependency() { let mut state = State::new(WEIGHTS, params.clone(), vec![], vec![]); let util_highway = - Highway::::new(TEST_INSTANCE_ID, test_validators(), params.clone()); + Highway::::new(TEST_INSTANCE_ID, test_validators(), params.clone(), None); // Alice a0 — a1 // / \ @@ -257,7 +259,8 @@ fn transitive_proposal_dependency() { let max_requests_for_vertex = 5; let mut sync = Synchronizer::::new(WEIGHTS.len(), TEST_INSTANCE_ID); - let mut highway = Highway::::new(TEST_INSTANCE_ID, test_validators(), params); + let mut highway = + Highway::::new(TEST_INSTANCE_ID, test_validators(), params, None); let now = 0x100.into(); assert!(matches!( diff --git a/node/src/components/consensus/protocols/highway.rs b/node/src/components/consensus/protocols/highway.rs index c6589fc004..3f14c9f7d2 100644 --- a/node/src/components/consensus/protocols/highway.rs +++ b/node/src/components/consensus/protocols/highway.rs @@ -97,6 +97,7 @@ impl HighwayProtocol { era_start_time: Timestamp, seed: u64, now: Timestamp, + protocol_state_file: Option, ) -> (Box>, ProtocolOutcomes) { let validators_count = validator_stakes.len(); let validators = protocols::common::validators::(faulty, inactive, validator_stakes); @@ -170,7 +171,7 @@ impl HighwayProtocol { let outcomes = Self::initialize_timers(now, era_start_time, &config.highway); - let highway = Highway::new(instance_id, validators, params); + let highway = Highway::new(instance_id, validators, params, protocol_state_file); let hw_proto = Box::new(HighwayProtocol { pending_values: HashMap::new(), finality_detector: FinalityDetector::new(ftt), @@ -339,9 +340,8 @@ impl HighwayProtocol { }); } return outcomes; - } else { - self.log_proposal(vertex, "proposal does not need validation"); } + self.log_proposal(vertex, "proposal does not need validation"); } // Either consensus value doesn't need validation or it's not a proposal. diff --git a/node/src/components/consensus/protocols/highway/tests.rs b/node/src/components/consensus/protocols/highway/tests.rs index 5c06da33bf..83e7e781da 100644 --- a/node/src/components/consensus/protocols/highway/tests.rs +++ b/node/src/components/consensus/protocols/highway/tests.rs @@ -89,6 +89,7 @@ where start_timestamp, 0, start_timestamp, + None, ); // We expect three messages: // * log participation timer, diff --git a/node/src/components/consensus/protocols/zug.rs b/node/src/components/consensus/protocols/zug.rs index 87135cebdb..121169410b 100644 --- a/node/src/components/consensus/protocols/zug.rs +++ b/node/src/components/consensus/protocols/zug.rs @@ -64,7 +64,6 @@ mod proposal; mod round; #[cfg(test)] mod tests; -mod wal; use std::{ any::Any, @@ -93,7 +92,10 @@ use crate::{ era_supervisor::SerializedMessage, protocols, traits::{ConsensusValueT, Context}, - utils::{ValidatorIndex, ValidatorMap, Validators, Weight}, + utils::{ + wal::{ReadWal, WalEntry, WriteWal}, + ValidatorIndex, ValidatorMap, Validators, Weight, + }, ActionId, LeaderSequence, TimerId, }, types::NodeId, @@ -105,7 +107,7 @@ use params::Params; use participation::{Participation, ParticipationStatus}; use proposal::{HashedProposal, Proposal}; use round::Round; -use wal::{Entry, ReadWal, WriteWal}; +use serde::{Deserialize, Serialize}; pub(crate) use message::{Message, SyncRequest}; @@ -126,6 +128,23 @@ pub(crate) type RoundId = u32; type ProposalsAwaitingParent = HashSet<(RoundId, NodeId)>; type ProposalsAwaitingValidation = HashSet<(RoundId, HashedProposal, NodeId)>; +/// An entry in the Write-Ahead Log, storing a message we had added to our protocol state. +#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[serde(bound( + serialize = "C::Hash: Serialize", + deserialize = "C::Hash: Deserialize<'de>", +))] +pub(crate) enum ZugWalEntry { + /// A signed echo or vote. + SignedMessage(SignedMessage), + /// A proposal. + Proposal(Proposal, RoundId), + /// Evidence of a validator double-signing. + Evidence(SignedMessage, Content, C::Signature), +} + +impl WalEntry for ZugWalEntry {} + /// Contains the portion of the state required for an active validator to participate in the /// protocol. #[derive(DataSize)] @@ -200,7 +219,7 @@ where /// `update`. next_scheduled_update: Timestamp, /// The write-ahead log to prevent honest nodes from double-signing upon restart. - write_wal: Option>, + write_wal: Option>>, /// A map of random IDs -> tipmestamp of when it has been created, allowing to /// verify that a response has been asked for. sent_sync_requests: registered_sync::RegisteredSync, @@ -663,7 +682,7 @@ impl Zug { ); // We only return the new message if we are able to record it. If that fails we // wouldn't know about our own message after a restart and risk double-signing. - if self.record_entry(&Entry::SignedMessage(signed_msg.clone())) + if self.record_entry(&ZugWalEntry::SignedMessage(signed_msg.clone())) && self.add_content(signed_msg.clone()) { Some(signed_msg) @@ -708,7 +727,11 @@ impl Zug { signature2: C::Signature, now: Timestamp, ) -> ProtocolOutcomes { - self.record_entry(&Entry::Evidence(signed_msg.clone(), content2, signature2)); + self.record_entry(&ZugWalEntry::Evidence( + signed_msg.clone(), + content2, + signature2, + )); self.handle_fault_no_wal(signed_msg, validator_id, content2, signature2, now) } @@ -1108,7 +1131,7 @@ impl Zug { return Ok(vec![]); } - self.record_entry(&Entry::SignedMessage(signed_msg.clone())); + self.record_entry(&ZugWalEntry::SignedMessage(signed_msg.clone())); if self.add_content(signed_msg) { Ok(self.update(now)) } else { @@ -1329,7 +1352,7 @@ impl Zug { /// Adds a signed message to the WAL such that we can avoid double signing upon recovery if the /// node shuts down. Returns `true` if the message was added successfully. - fn record_entry(&mut self, entry: &Entry) -> bool { + fn record_entry(&mut self, entry: &ZugWalEntry) -> bool { match self.write_wal.as_mut().map(|ww| ww.record_entry(entry)) { None => false, Some(Ok(())) => true, @@ -1353,7 +1376,7 @@ impl Zug { pub(crate) fn open_wal(&mut self, wal_file: PathBuf, now: Timestamp) -> ProtocolOutcomes { let our_idx = self.our_idx(); // Open the file for reading. - let mut read_wal = match ReadWal::::new(&wal_file) { + let mut read_wal = match ReadWal::>::new(&wal_file) { Ok(read_wal) => read_wal, Err(err) => { error!(our_idx, %err, "could not create a ReadWal using this file"); @@ -1367,13 +1390,13 @@ impl Zug { loop { match read_wal.read_next_entry() { Ok(Some(next_entry)) => match next_entry { - Entry::SignedMessage(next_message) => { + ZugWalEntry::SignedMessage(next_message) => { if !self.add_content(next_message) { error!(our_idx, "Could not add content from WAL."); return outcomes; } } - Entry::Proposal(next_proposal, corresponding_round_id) => { + ZugWalEntry::Proposal(next_proposal, corresponding_round_id) => { if self .round(corresponding_round_id) .and_then(Round::proposal) @@ -1421,7 +1444,7 @@ impl Zug { } } } - Entry::Evidence( + ZugWalEntry::Evidence( conflicting_message, conflicting_message_content, conflicting_signature, @@ -1806,7 +1829,7 @@ impl Zug { } else { self.log_proposal(&proposal, round_id, "proposal does not need validation"); if self.round_mut(round_id).insert_proposal(proposal.clone()) { - self.record_entry(&Entry::Proposal(proposal.inner().clone(), round_id)); + self.record_entry(&ZugWalEntry::Proposal(proposal.inner().clone(), round_id)); self.progress_detected = true; self.mark_dirty(round_id); if let Some(block) = proposal.maybe_block().cloned() { @@ -1947,7 +1970,10 @@ impl Zug { instance_id: *self.instance_id(), echo, }; - if !self.record_entry(&Entry::Proposal(hashed_prop.inner().clone(), round_id)) { + if !self.record_entry(&ZugWalEntry::Proposal( + hashed_prop.inner().clone(), + round_id, + )) { error!( our_idx = self.our_idx(), "could not record own proposal in WAL" @@ -2314,7 +2340,7 @@ where for (round_id, proposal, _sender) in rounds_and_node_ids { info!(our_idx = self.our_idx(), %round_id, %proposal, "handling valid proposal"); if self.round_mut(round_id).insert_proposal(proposal.clone()) { - self.record_entry(&Entry::Proposal(proposal.into_inner(), round_id)); + self.record_entry(&ZugWalEntry::Proposal(proposal.into_inner(), round_id)); self.mark_dirty(round_id); self.progress_detected = true; outcomes.push(ProtocolOutcome::HandledProposedBlock( diff --git a/node/src/components/consensus/protocols/zug/participation.rs b/node/src/components/consensus/protocols/zug/participation.rs index 68200c0cfc..4ca1b22d78 100644 --- a/node/src/components/consensus/protocols/zug/participation.rs +++ b/node/src/components/consensus/protocols/zug/participation.rs @@ -50,9 +50,8 @@ impl ParticipationStatus { { if r_id.saturating_add(2) < zug.current_round { return Some(ParticipationStatus::LastSeenInRound(*r_id)); - } else { - return None; // Seen recently; considered currently active. } + return None; // Seen recently; considered currently active. } } Some(ParticipationStatus::Inactive) diff --git a/node/src/components/consensus/utils.rs b/node/src/components/consensus/utils.rs index 1e71d7833f..bcbda1b361 100644 --- a/node/src/components/consensus/utils.rs +++ b/node/src/components/consensus/utils.rs @@ -1,6 +1,7 @@ //! Various utilities relevant to consensus. mod validators; +pub(crate) mod wal; mod weight; pub use validators::{Validator, ValidatorIndex, ValidatorMap, Validators}; diff --git a/node/src/components/consensus/protocols/zug/wal.rs b/node/src/components/consensus/utils/wal.rs similarity index 66% rename from node/src/components/consensus/protocols/zug/wal.rs rename to node/src/components/consensus/utils/wal.rs index 70fa606eeb..31c9649a40 100644 --- a/node/src/components/consensus/protocols/zug/wal.rs +++ b/node/src/components/consensus/utils/wal.rs @@ -11,36 +11,16 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use tracing::warn; -use crate::components::consensus::{ - protocols::zug::{Content, Proposal, SignedMessage}, - traits::Context, -}; - -use super::RoundId; - -/// An entry in the Write-Ahead Log, storing a message we had added to our protocol state. -#[derive(Deserialize, Serialize, Debug, PartialEq)] -#[serde(bound( - serialize = "C::Hash: Serialize", - deserialize = "C::Hash: Deserialize<'de>", -))] -pub(crate) enum Entry { - /// A signed echo or vote. - SignedMessage(SignedMessage), - /// A proposal. - Proposal(Proposal, RoundId), - /// Evidence of a validator double-signing. - Evidence(SignedMessage, Content, C::Signature), -} +pub(crate) trait WalEntry: Serialize + for<'de> Deserialize<'de> {} /// A Write-Ahead Log to store every message on disk when we add it to the protocol state. #[derive(Debug)] -pub(crate) struct WriteWal { +pub(crate) struct WriteWal { writer: BufWriter, - phantom_context: PhantomData, + phantom_context: PhantomData, } -impl DataSize for WriteWal { +impl DataSize for WriteWal { const IS_DYNAMIC: bool = true; const STATIC_HEAP_SIZE: usize = 0; @@ -64,7 +44,7 @@ pub(crate) enum WriteWalError { FileCouldntBeOpened(io::Error), } -impl WriteWal { +impl WriteWal { pub(crate) fn new(wal_path: &PathBuf) -> Result { let file = OpenOptions::new() .append(true) @@ -77,7 +57,7 @@ impl WriteWal { }) } - pub(crate) fn record_entry(&mut self, entry: &Entry) -> Result<(), WriteWalError> { + pub(crate) fn record_entry(&mut self, entry: &E) -> Result<(), WriteWalError> { // First write the size of the entry as a serialized u64. let entry_size = bincode::serialized_size(entry).map_err(WriteWalError::CouldntGetSerializedSize)?; @@ -96,9 +76,9 @@ impl WriteWal { /// A buffer to read a Write-Ahead Log from disk and deserialize its messages. #[derive(Debug)] -pub(crate) struct ReadWal { +pub(crate) struct ReadWal { pub(crate) reader: BufReader, - pub(crate) phantom_context: PhantomData, + pub(crate) phantom_context: PhantomData, } #[derive(Error, Debug)] @@ -111,7 +91,7 @@ pub(crate) enum ReadWalError { CouldNotDeserialize(bincode::Error), } -impl ReadWal { +impl ReadWal { pub(crate) fn new(wal_path: &PathBuf) -> Result { let file = OpenOptions::new() .create(true) @@ -127,10 +107,10 @@ impl ReadWal { } } -impl ReadWal { +impl ReadWal { /// Reads the next entry from the WAL, or returns an error. /// If there are 0 bytes left it returns `Ok(None)`. - pub(crate) fn read_next_entry(&mut self) -> Result>, ReadWalError> { + pub(crate) fn read_next_entry(&mut self) -> Result, ReadWalError> { // Remember the current position: If we encounter an unreadable entry we trim the file at // this point so we can continue appending entries after it. let position = self.reader.stream_position()?; @@ -179,65 +159,29 @@ impl ReadWal { mod tests { use std::iter::from_fn; - use crate::components::consensus::{ - cl_context::{ClContext, Keypair}, - protocols::common, - }; - use casper_types::{PublicKey, SecretKey, Timestamp, U512}; + use casper_types::Timestamp; + use serde::{Deserialize, Serialize}; use tempfile::tempdir; use super::*; - use once_cell::sync::Lazy; - const INSTANCE_ID_DATA: &[u8; 1] = &[123u8; 1]; - const ALICE_WEIGHT: u64 = 1000000; - const ALICE_SECRET_KEY_BYTES: [u8; SecretKey::ED25519_LENGTH] = [3; SecretKey::ED25519_LENGTH]; - static ALICE_SECRET_KEY: Lazy = - Lazy::new(|| SecretKey::ed25519_from_bytes(ALICE_SECRET_KEY_BYTES).unwrap()); - static ALICE_PUBLIC_KEY: Lazy = - Lazy::new(|| PublicKey::from(Lazy::force(&ALICE_SECRET_KEY))); - - fn create_message_fn() -> Box) -> SignedMessage> { - let alice_keypair = Keypair::from(std::sync::Arc::new( - SecretKey::ed25519_from_bytes(ALICE_SECRET_KEY_BYTES).unwrap(), - )); - let weights: Vec<(PublicKey, U512)> = - vec![(ALICE_PUBLIC_KEY.clone(), U512::from(ALICE_WEIGHT))]; - let validators = common::validators::( - &Default::default(), - &Default::default(), - weights.iter().cloned().collect(), - ); - let instance_id = ClContext::hash(INSTANCE_ID_DATA); - Box::new(move |round_id, content: Content| { - let validator_idx = validators.get_index(alice_keypair.public_key()).unwrap(); - SignedMessage::sign_new( - round_id, - instance_id, - content, - validator_idx, - &alice_keypair, - ) - }) + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + enum TestWalEntry { + Variant1(u32), + Variant2(Timestamp), } + impl WalEntry for TestWalEntry {} + #[test] // Tests the functionality of the ReadWal and WriteWal by constructing one and manipulating it. fn test_read_write_wal() { // Create a bunch of test entries - let create_message = create_message_fn(); let mut entries = vec![ - Entry::SignedMessage(create_message(0, Content::Vote(true))), - Entry::SignedMessage(create_message(1, Content::Vote(false))), - Entry::SignedMessage(create_message( - 2, - Content::Echo(ClContext::hash(&[123u8; 1])), - )), - Entry::Proposal(Proposal::dummy(Timestamp::zero(), 0), 0), - Entry::Evidence( - create_message(0, Content::Echo(ClContext::hash(&[23u8; 1]))), - Content::Echo(ClContext::hash(&[52u8; 1])), - create_message(0, Content::Echo(ClContext::hash(&[4u8; 1]))).signature, - ), + TestWalEntry::Variant1(0), + TestWalEntry::Variant1(1), + TestWalEntry::Variant1(2), + TestWalEntry::Variant2(Timestamp::zero()), ]; // Create a temporary directory which will be removed upon dropping the dir variable, @@ -246,14 +190,14 @@ mod tests { let path = dir.path().join("wal"); let read_entries = || { - let mut read_wal: ReadWal = ReadWal::new(&path).unwrap(); + let mut read_wal: ReadWal = ReadWal::new(&path).unwrap(); from_fn(move || read_wal.read_next_entry().unwrap()).collect::>() }; assert_eq!(read_entries(), vec![]); // Record all of the test entries into the WAL file - let mut write_wal: WriteWal = WriteWal::new(&path).unwrap(); + let mut write_wal: WriteWal = WriteWal::new(&path).unwrap(); entries.iter().for_each(move |entry| { write_wal.record_entry(entry).unwrap(); diff --git a/node/src/components/contract_runtime.rs b/node/src/components/contract_runtime.rs index 4288173408..16d22c0ea3 100644 --- a/node/src/components/contract_runtime.rs +++ b/node/src/components/contract_runtime.rs @@ -151,6 +151,7 @@ impl ContractRuntime { .with_refund_handling(chainspec.core_config.refund_handling) .with_fee_handling(chainspec.core_config.fee_handling) .with_protocol_version(chainspec.protocol_version()) + .with_storage_costs(chainspec.storage_costs) .build(); let data_access_layer = Arc::new( @@ -782,7 +783,7 @@ impl ContractRuntime { let req = TrieRequest::new(trie_key, Some(chunk_index)); let maybe_raw = data_access_layer .trie(req) - .into_legacy() + .into_raw() .map_err(ContractRuntimeError::FailedToRetrieveTrieById)?; let ret = match maybe_raw { Some(raw) => Some(TrieOrChunk::new(raw.into(), chunk_index)?), diff --git a/node/src/components/contract_runtime/error.rs b/node/src/components/contract_runtime/error.rs index aa8b3db8b5..ebae6efabf 100644 --- a/node/src/components/contract_runtime/error.rs +++ b/node/src/components/contract_runtime/error.rs @@ -154,10 +154,15 @@ pub enum BlockExecutionError { #[error("Unsupported execution kind: {0}")] /// Unsupported execution kind UnsupportedTransactionKind(u8), + #[error("Error while converting transaction to internal representation: {0}")] + TransactionConversion(String), /// Invalid gas limit amount. #[error("Invalid gas limit amount: {0}")] InvalidGasLimit(U512), /// Invalid transaction variant. #[error("Invalid transaction variant")] InvalidTransactionVariant, + /// Invalid transaction arguments. + #[error("Invalid transaction arguments")] + InvalidTransactionArgs, } diff --git a/node/src/components/contract_runtime/operations.rs b/node/src/components/contract_runtime/operations.rs index 40eae15dce..b52af3cbe3 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -1,15 +1,20 @@ -use std::{collections::BTreeMap, convert::TryInto, sync::Arc, time::Instant}; +pub(crate) mod wasm_v2_request; -use casper_executor_wasm::{ExecutorV2, WasmV2Request}; +use casper_executor_wasm::ExecutorV2; use itertools::Itertools; +use std::{collections::BTreeMap, convert::TryInto, sync::Arc, time::Instant}; use tracing::{debug, error, info, trace, warn}; +use wasm_v2_request::WasmV2Request; -use casper_execution_engine::engine_state::{ExecutionEngineV1, WasmV1Request, WasmV1Result}; +use casper_execution_engine::engine_state::{ + BlockInfo, ExecutionEngineV1, WasmV1Request, WasmV1Result, +}; use casper_storage::{ block_store::types::ApprovalsHashes, data_access_layer::{ balance::BalanceHandling, forced_undelegate::{ForcedUndelegateRequest, ForcedUndelegateResult}, + mint::BalanceIdentifierTransferArgs, AuctionMethod, BalanceHoldKind, BalanceHoldRequest, BalanceIdentifier, BalanceRequest, BiddingRequest, BlockGlobalRequest, BlockGlobalResult, BlockRewardsRequest, BlockRewardsResult, DataAccessLayer, EraValidatorsRequest, EraValidatorsResult, EvictItem, @@ -28,8 +33,8 @@ use casper_types::{ execution::{Effects, ExecutionResult, TransformKindV2, TransformV2}, system::handle_payment::ARG_AMOUNT, BlockHash, BlockHeader, BlockTime, BlockV2, CLValue, Chainspec, ChecksumRegistry, Digest, - EraEndV2, EraId, FeeHandling, Gas, GasLimited, Key, ProtocolVersion, PublicKey, RefundHandling, - Transaction, AUCTION_LANE_ID, MINT_LANE_ID, U512, + EraEndV2, EraId, FeeHandling, Gas, InvalidTransaction, InvalidTransactionV1, Key, + ProtocolVersion, PublicKey, RefundHandling, Transaction, AUCTION_LANE_ID, MINT_LANE_ID, U512, }; use super::{ @@ -41,7 +46,7 @@ use super::{ use crate::{ components::fetcher::FetchItem, contract_runtime::types::ExecutionArtifactBuilder, - types::{self, Chunkable, ExecutableBlock, InternalEraReport}, + types::{self, Chunkable, ExecutableBlock, InternalEraReport, MetaTransaction}, }; /// Executes a finalized block. @@ -59,7 +64,8 @@ pub fn execute_finalized_block( next_era_gas_price: Option, last_switch_block_hash: Option, ) -> Result { - if executable_block.height != execution_pre_state.next_block_height() { + let block_height = executable_block.height; + if block_height != execution_pre_state.next_block_height() { return Err(BlockExecutionError::WrongBlockHeight { executable_block: Box::new(executable_block), execution_pre_state: Box::new(execution_pre_state), @@ -79,11 +85,13 @@ pub fn execute_finalized_block( // scrape variables from execution pre state let parent_hash = execution_pre_state.parent_hash(); let parent_seed = execution_pre_state.parent_seed(); + let parent_block_hash = execution_pre_state.parent_hash(); let pre_state_root_hash = execution_pre_state.pre_state_root_hash(); let mut state_root_hash = pre_state_root_hash; // initial state root is parent's state root // scrape variables from executable block let block_time = BlockTime::new(executable_block.timestamp.millis()); + let proposer = executable_block.proposer.clone(); let era_id = executable_block.era_id; let mut artifacts = Vec::with_capacity(executable_block.transactions.len()); @@ -92,6 +100,7 @@ pub fn execute_finalized_block( let insufficient_balance_handling = InsufficientBalanceHandling::HoldRemaining; let refund_handling = chainspec.core_config.refund_handling; let fee_handling = chainspec.core_config.fee_handling; + let penalty_payment_amount = *casper_execution_engine::engine_state::MAX_PAYMENT; let balance_handling = BalanceHandling::Available; // get scratch state, which must be used for all processing and post processing data @@ -127,7 +136,7 @@ pub fn execute_finalized_block( return Err(BlockExecutionError::RootNotFound(state_root_hash)); } BlockGlobalResult::Failure(err) => { - return Err(BlockExecutionError::BlockGlobal(format!("{}", err))); + return Err(BlockExecutionError::BlockGlobal(format!("{:?}", err))); } BlockGlobalResult::Success { post_state_hash, .. @@ -136,14 +145,17 @@ pub fn execute_finalized_block( } } - for transaction in executable_block.transactions { - let mut artifact_builder = ExecutionArtifactBuilder::new(&transaction); + let transaction_config = &chainspec.transaction_config; + for stored_transaction in executable_block.transactions { + let mut artifact_builder = ExecutionArtifactBuilder::new(&stored_transaction); + let transaction = MetaTransaction::from(&stored_transaction, transaction_config) + .map_err(|err| BlockExecutionError::TransactionConversion(err.to_string()))?; let initiator_addr = transaction.initiator_addr(); let transaction_hash = transaction.hash(); - let runtime_args = transaction.session_args().cloned(); + let transaction_args = transaction.session_args().clone(); let entry_point = transaction.entry_point(); - let authorization_keys = transaction.authorization_keys(); + let authorization_keys = transaction.signers(); /* we solve for halting state using a `gas limit` which is the maximum amount of @@ -166,19 +178,24 @@ pub fn execute_finalized_block( */ // NOTE: this is the allowed computation limit (gas limit) - let gas_limit = match transaction.gas_limit(chainspec) { - Ok(gas) => gas, - Err(ite) => { - debug!(%transaction_hash, %ite, "invalid transaction (gas limit)"); - artifact_builder.with_invalid_transaction(&ite); - artifacts.push(artifact_builder.build()); - continue; - } - }; + let gas_limit = + match stored_transaction.gas_limit(chainspec, transaction.transaction_lane()) { + Ok(gas) => gas, + Err(ite) => { + debug!(%transaction_hash, %ite, "invalid transaction (gas limit)"); + artifact_builder.with_invalid_transaction(&ite); + artifacts.push(artifact_builder.build()); + continue; + } + }; artifact_builder.with_gas_limit(gas_limit); // NOTE: this is the actual adjusted cost that we charge for (gas limit * gas price) - let cost = match transaction.gas_cost(chainspec, current_gas_price) { + let cost = match stored_transaction.gas_cost( + chainspec, + transaction.transaction_lane(), + current_gas_price, + ) { Ok(motes) => motes.value(), Err(ite) => { debug!(%transaction_hash, "invalid transaction (motes conversion)"); @@ -193,7 +210,7 @@ pub fn execute_finalized_block( let is_v2_wasm = transaction.is_v2_wasm(); let refund_purse_active = !is_standard_payment; if refund_purse_active { - // if custom payment before doing any processing, initialize the initiator's main purse + // if custom payment before doing any processing, initialize the initiator's main purse // to be the refund purse for this transaction. // NOTE: when executed, custom payment logic has the option to call set_refund_purse // on the handle payment contract to set up a different refund purse, if desired. @@ -214,12 +231,37 @@ pub fn execute_finalized_block( return Err(BlockExecutionError::RootNotFound(state_root_hash)); } artifacts.push(artifact_builder.build()); - continue; // no reason to commit the effects, move on + continue; // don't commit effects, move on } state_root_hash = scratch_state .commit_effects(state_root_hash, handle_refund_result.effects().clone())?; } + { + // Ensure the initiator's main purse can cover the penalty payment before proceeding. + let initial_balance_result = scratch_state.balance(BalanceRequest::new( + state_root_hash, + protocol_version, + initiator_addr.clone().into(), + balance_handling, + ProofHandling::NoProofs, + )); + + if let Err(root_not_found) = artifact_builder + .with_initial_balance_result(initial_balance_result.clone(), penalty_payment_amount) + { + if root_not_found { + return Err(BlockExecutionError::RootNotFound(state_root_hash)); + } + trace!(%transaction_hash, "insufficient initial balance"); + debug!(%transaction_hash, ?initial_balance_result, %penalty_payment_amount, "insufficient initial balance"); + artifacts.push(artifact_builder.build()); + // only reads have happened so far, and we can't charge due + // to insufficient balance, so move on with no effects committed + continue; + } + } + let mut balance_identifier = { if is_standard_payment { // this is the typical scenario; the initiating account pays using its main @@ -238,11 +280,11 @@ pub fn execute_finalized_block( // using a multiple of a small value. chainspec.transaction_config.native_transfer_minimum_motes * 5, ); + let session_input_data = transaction.to_session_input_data(); let pay_result = match WasmV1Request::new_custom_payment( - state_root_hash, - block_time, + BlockInfo::new(state_root_hash, block_time, parent_block_hash, block_height), custom_payment_gas_limit, - &transaction, + &session_input_data, ) { Ok(mut pay_request) => { // We'll send a hint to the custom payment logic on the amount @@ -259,7 +301,29 @@ pub fn execute_finalized_block( }; let balance_identifier = { if pay_result.error().is_some() { - BalanceIdentifier::PenalizedAccount(initiator_addr.account_hash()) + // Charge initiator for the penalty payment amount + // the most expedient way to do this that aligns with later code + // is to transfer from the initiator's main purse to the payment purse + let transfer_result = + scratch_state.transfer(TransferRequest::new_indirect( + native_runtime_config.clone(), + state_root_hash, + protocol_version, + transaction_hash, + initiator_addr.clone(), + authorization_keys.clone(), + BalanceIdentifierTransferArgs::new( + None, + BalanceIdentifier::Account(initiator_addr.account_hash()), + BalanceIdentifier::Payment, + penalty_payment_amount, + None, + ), + )); + artifact_builder + .with_transfer_result(transfer_result) + .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?; + BalanceIdentifier::PenalizedPayment } else { BalanceIdentifier::Payment } @@ -274,7 +338,7 @@ pub fn execute_finalized_block( } }; - let initial_balance_result = scratch_state.balance(BalanceRequest::new( + let post_payment_balance_result = scratch_state.balance(BalanceRequest::new( state_root_hash, protocol_version, balance_identifier.clone(), @@ -282,12 +346,12 @@ pub fn execute_finalized_block( ProofHandling::NoProofs, )); - let lane = transaction.transaction_lane(); + let lane_id = transaction.transaction_lane(); let allow_execution = { let is_not_penalized = !balance_identifier.is_penalty(); - let sufficient_balance = initial_balance_result.is_sufficient(cost); - let is_supported = chainspec.is_supported(lane); + let sufficient_balance = post_payment_balance_result.is_sufficient(cost); + let is_supported = chainspec.is_supported(lane_id); trace!(%transaction_hash, ?sufficient_balance, ?is_not_penalized, ?is_supported, "payment preprocessing"); is_not_penalized && sufficient_balance && is_supported }; @@ -311,10 +375,12 @@ pub fn execute_finalized_block( .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?; } - trace!(%transaction_hash, ?lane, "eligible for execution"); - match lane { - category if category == MINT_LANE_ID => { - let runtime_args = runtime_args.unwrap().clone(); + trace!(%transaction_hash, ?lane_id, "eligible for execution"); + match lane_id { + lane_id if lane_id == MINT_LANE_ID => { + let runtime_args = transaction_args + .as_named() + .ok_or(BlockExecutionError::InvalidTransactionArgs)?; let transfer_result = scratch_state.transfer(TransferRequest::with_runtime_args( native_runtime_config.clone(), @@ -323,7 +389,7 @@ pub fn execute_finalized_block( transaction_hash, initiator_addr.clone(), authorization_keys, - runtime_args, + runtime_args.clone(), )); let consumed = gas_limit; state_root_hash = scratch_state @@ -333,9 +399,11 @@ pub fn execute_finalized_block( .with_transfer_result(transfer_result) .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?; } - category if category == AUCTION_LANE_ID => { - let runtime_args = runtime_args.unwrap(); - match AuctionMethod::from_parts(entry_point, &runtime_args, chainspec) { + lane_id if lane_id == AUCTION_LANE_ID => { + let runtime_args = transaction_args + .as_named() + .ok_or(BlockExecutionError::InvalidTransactionArgs)?; + match AuctionMethod::from_parts(entry_point, runtime_args, chainspec) { Ok(auction_method) => { let bidding_result = scratch_state.bidding(BiddingRequest::new( native_runtime_config.clone(), @@ -372,11 +440,7 @@ pub fn execute_finalized_block( &transaction, ) { Ok(wasm_v2_request) => { - let result = execution_engine_v2.execute_wasm_v2_request( - state_root_hash, - &scratch_state, - wasm_v2_request, - ); + let result = wasm_v2_request.execute(&execution_engine_v2, &scratch_state); match result { Ok(wasm_v2_result) => { state_root_hash = wasm_v2_result.state_root_hash(); @@ -389,23 +453,28 @@ pub fn execute_finalized_block( } } Err(ire) => { - debug!(%transaction_hash, ?lane, ?ire, "unable to get wasm v2 request"); - artifact_builder.with_invalid_wasm_v2_request(&ire); + debug!(%transaction_hash, ?lane_id, ?ire, "unable to get wasm v2 request"); + artifact_builder.with_invalid_wasm_v2_request(ire); } }, _ if !is_v2_wasm => { let wasm_v1_start = Instant::now(); + let session_input_data = transaction.to_session_input_data(); match WasmV1Request::new_session( - state_root_hash, - block_time, + BlockInfo::new( + state_root_hash, + block_time, + parent_block_hash, + block_height, + ), gas_limit, - &transaction, + &session_input_data, ) { Ok(wasm_v1_request) => { - trace!(%transaction_hash, ?lane, ?wasm_v1_request, "able to get wasm v1 request"); + trace!(%transaction_hash, ?lane_id, ?wasm_v1_request, "able to get wasm v1 request"); let wasm_v1_result = execution_engine_v1.execute(&scratch_state, wasm_v1_request); - trace!(%transaction_hash, ?lane, ?wasm_v1_result, "able to get wasm v1 result"); + trace!(%transaction_hash, ?lane_id, ?wasm_v1_result, "able to get wasm v1 result"); state_root_hash = scratch_state.commit_effects( state_root_hash, wasm_v1_result.effects().clone(), @@ -416,7 +485,7 @@ pub fn execute_finalized_block( .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?; } Err(ire) => { - debug!(%transaction_hash, ?lane, ?ire, "unable to get wasm v1 request"); + debug!(%transaction_hash, ?lane_id, ?ire, "unable to get wasm v1 request"); artifact_builder.with_invalid_wasm_v1_request(&ire); } }; @@ -603,6 +672,7 @@ pub fn execute_finalized_block( }; state_root_hash = scratch_state.commit_effects(state_root_hash, handle_fee_result.effects().clone())?; + artifact_builder .with_handle_fee_result(&handle_fee_result) .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?; @@ -785,7 +855,7 @@ pub fn execute_finalized_block( protocol_version, state_root_hash, era_report.clone(), - executable_block.timestamp.millis(), + block_time.value(), executable_block.era_id.successor(), ) { StepResult::RootNotFound => { @@ -838,7 +908,7 @@ pub fn execute_finalized_block( // Pruning -- this is orthogonal to the contents of the block, but we deliberately do it // at the end to avoid an read ordering issue during block execution. - if let Some(previous_block_height) = executable_block.height.checked_sub(1) { + if let Some(previous_block_height) = block_height.checked_sub(1) { if let Some(keys_to_prune) = calculate_prune_eras( activation_point_era_id, key_block_height_for_activation_point, @@ -993,7 +1063,7 @@ pub fn execute_finalized_block( era_end, executable_block.timestamp, executable_block.era_id, - executable_block.height, + block_height, protocol_version, (*proposer).clone(), executable_block.transaction_map, @@ -1045,23 +1115,31 @@ pub(super) fn speculatively_execute( chainspec: &Chainspec, execution_engine_v1: &ExecutionEngineV1, block_header: BlockHeader, - transaction: Transaction, + input_transaction: Transaction, ) -> SpeculativeExecutionResult where S: StateProvider, { + let transaction_config = &chainspec.transaction_config; + let maybe_transaction = MetaTransaction::from(&input_transaction, transaction_config); + if let Err(error) = maybe_transaction { + return SpeculativeExecutionResult::invalid_transaction(error); + } + let transaction = maybe_transaction.unwrap(); let state_root_hash = block_header.state_root_hash(); + let parent_block_hash = block_header.block_hash(); + let block_height = block_header.height(); let block_time = block_header .timestamp() .saturating_add(chainspec.core_config.minimum_block_time); - let gas_limit = match transaction.gas_limit(chainspec) { + let gas_limit = match input_transaction.gas_limit(chainspec, transaction.transaction_lane()) { Ok(gas_limit) => gas_limit, Err(_) => { - return SpeculativeExecutionResult::invalid_gas_limit(transaction); + return SpeculativeExecutionResult::invalid_gas_limit(input_transaction); } }; - if transaction.is_deploy() { + if transaction.is_legacy_transaction() { if transaction.is_native() { let limit = Gas::from(chainspec.system_costs_config.mint_costs().transfer); let protocol_version = chainspec.protocol_version(); @@ -1069,7 +1147,14 @@ where let transaction_hash = transaction.hash(); let initiator_addr = transaction.initiator_addr(); let authorization_keys = transaction.authorization_keys(); - let runtime_args = transaction.session_args().unwrap().clone(); + let runtime_args = match transaction.session_args().as_named() { + Some(runtime_args) => runtime_args.clone(), + None => { + return SpeculativeExecutionResult::InvalidTransaction(InvalidTransaction::V1( + InvalidTransactionV1::ExpectedNamedArguments, + )); + } + }; let result = state_provider.transfer(TransferRequest::with_runtime_args( native_runtime_config.clone(), @@ -1086,15 +1171,20 @@ where block_header.block_hash(), )) } else { - let wasm_v1_result = match WasmV1Request::new_session( + let block_info = BlockInfo::new( *state_root_hash, block_time.into(), - gas_limit, - &transaction, - ) { - Ok(wasm_v1_request) => execution_engine_v1.execute(state_provider, wasm_v1_request), - Err(error) => WasmV1Result::invalid_executable_item(gas_limit, error), - }; + parent_block_hash, + block_height, + ); + let session_input_data = transaction.to_session_input_data(); + let wasm_v1_result = + match WasmV1Request::new_session(block_info, gas_limit, &session_input_data) { + Ok(wasm_v1_request) => { + execution_engine_v1.execute(state_provider, wasm_v1_request) + } + Err(error) => WasmV1Result::invalid_executable_item(gas_limit, error), + }; SpeculativeExecutionResult::WasmV1(utils::spec_exec_from_wasm_v1_result( wasm_v1_result, block_header.block_hash(), diff --git a/node/src/components/contract_runtime/operations/wasm_v2_request.rs b/node/src/components/contract_runtime/operations/wasm_v2_request.rs new file mode 100644 index 0000000000..5a88cb1c77 --- /dev/null +++ b/node/src/components/contract_runtime/operations/wasm_v2_request.rs @@ -0,0 +1,286 @@ +use std::sync::Arc; + +use bytes::Bytes; +use casper_executor_wasm::{ + install::{ + InstallContractError, InstallContractRequest, InstallContractRequestBuilder, + InstallContractResult, + }, + ExecutorV2, +}; +use casper_executor_wasm_interface::{ + executor::{ + ExecuteRequest, ExecuteRequestBuilder, ExecuteWithProviderError, ExecuteWithProviderResult, + ExecutionKind, + }, + GasUsage, +}; +use casper_storage::{ + global_state::state::{CommitProvider, StateProvider}, + AddressGeneratorBuilder, +}; +use casper_types::{ + execution::Effects, Digest, EntityAddr, Gas, Key, TransactionEntryPoint, + TransactionInvocationTarget, TransactionTarget, U512, +}; +use thiserror::Error; +use tracing::info; + +use super::MetaTransaction; + +/// The request to execute a Wasm contract. +pub(crate) enum WasmV2Request { + /// The request to install a Wasm contract. + Install(InstallContractRequest), + /// The request to execute a Wasm contract. + Execute(ExecuteRequest), +} + +/// The result of executing a Wasm contract. +pub(crate) enum WasmV2Result { + /// The result of installing a Wasm contract. + Install(InstallContractResult), + /// The result of executing a Wasm contract. + Execute(ExecuteWithProviderResult), +} + +impl WasmV2Result { + /// Returns the state root hash after the contract execution. + pub(crate) fn state_root_hash(&self) -> Digest { + match self { + WasmV2Result::Install(result) => result.post_state_hash(), + WasmV2Result::Execute(result) => result.post_state_hash(), + } + } + + /// Returns the gas usage of the contract execution. + pub(crate) fn gas_usage(&self) -> &GasUsage { + match self { + WasmV2Result::Install(result) => result.gas_usage(), + WasmV2Result::Execute(result) => result.gas_usage(), + } + } + + /// Returns the effects of the contract execution. + pub(crate) fn effects(&self) -> &Effects { + match self { + WasmV2Result::Install(result) => result.effects(), + WasmV2Result::Execute(result) => result.effects(), + } + } +} + +#[derive(Error, Debug)] +pub(crate) enum WasmV2Error { + #[error(transparent)] + Install(InstallContractError), + #[error(transparent)] + Execute(ExecuteWithProviderError), +} + +#[derive(Clone, Eq, PartialEq, Error, Debug)] +pub(crate) enum InvalidRequest { + #[error("Invalid gas limit: {0}")] + InvalidGasLimit(U512), +} + +impl WasmV2Request { + pub(crate) fn new( + gas_limit: Gas, + network_name: impl Into>, + transaction: &MetaTransaction, + ) -> Result { + // let transaction_v1 = transaction.ta + // let transaction_hash = transaction.hash(); + // let initiator_addr = transaction.initiator_addr(); + + // let gas_limit: u64 = gas_limit + // .value() + // .try_into() + // .map_err(|_| InvalidRequest::InvalidGasLimit(gas_limit.value()))?; + + // let address_generator = AddressGeneratorBuilder::default() + // .seed_with(transaction_hash.as_ref()) + // .build(); + + // // If it's wrong args variant => invalid request => penalty payment + // // let input_data = transaction_v1.body().args().clone().into_bytesrepr(); // TODO: Make + // non optional // let value = transaction_v1.body().transferred_value(); + // let input_data = Some(Vec::new()); + // let value = 0; + + // enum Target { + // Install { + // module_bytes: Bytes, + // entry_point: String, + // seed: Option<[u8; 32]>, + // }, + // Session { + // module_bytes: Bytes, + // }, + // Stored { + // id: TransactionInvocationTarget, + // entry_point: String, + // }, + // } + + // let target = match transaction.target() { + // TransactionTarget::Native => todo!(), // + // TransactionTarget::Stored { + // id, + // runtime: _, + // transferred_value: _, + // } => match transaction_v1.body().entry_point() { + // TransactionEntryPoint::Custom(entry_point) => Target::Stored { + // id: id.clone(), + // entry_point: entry_point.clone(), + // }, + // _ => todo!(), + // }, + // TransactionTarget::Session { + // module_bytes, + // runtime: _, + // transferred_value: _, + // seed, + // is_install_upgrade, + // } => match transaction_v1.payload().entry_point() { + // TransactionEntryPoint::Call => Target::Session { + // module_bytes: module_bytes.clone().take_inner().into(), + // }, + // TransactionEntryPoint::Custom(entry_point) => Target::Install { + // module_bytes: module_bytes.clone().take_inner().into(), + // entry_point: entry_point.to_string(), + // seed: seed.clone(), + // }, + // _ => todo!(), + // }, + // }; + + // info!(%transaction_hash, "executing v1 contract"); + + // match target { + // Target::Install { + // module_bytes, + // entry_point, + // seed, + // } => { + // let mut builder = InstallContractRequestBuilder::default(); + + // let entry_point = (!entry_point.is_empty()).then_some(entry_point); + + // match entry_point { + // Some(entry_point) => { + // builder = builder.with_entry_point(entry_point.clone()); + + // if let Some(input_data) = input_data { + // builder = builder.with_input(input_data.take_inner().into()); + // } + // } + // None => { + // assert!( + // input_data.is_none() + // || matches!(input_data, Some(input_data) if input_data.is_empty()) + // ); + // } + // } + + // if let Some(seed) = seed { + // builder = builder.with_seed(seed); + // } + + // let install_request = builder + // .with_initiator(initiator_addr.account_hash()) + // .with_gas_limit(gas_limit) + // .with_transaction_hash(transaction_hash) + // .with_wasm_bytes(module_bytes) + // .with_address_generator(address_generator) + // .with_transferred_value(value.into()) // TODO: Replace u128 to u64 + // .with_chain_name(network_name) + // .with_block_time(transaction.timestamp()) + // .build() + // .expect("should build"); + + // Ok(Self::Install(install_request)) + // } + // Target::Session { .. } | Target::Stored { .. } => { + // let mut builder = ExecuteRequestBuilder::default(); + + // let initiator_account_hash = &initiator_addr.account_hash(); + + // let initiator_key = Key::Account(*initiator_account_hash); + + // builder = builder + // .with_address_generator(address_generator) + // .with_gas_limit(gas_limit) + // .with_transaction_hash(transaction_hash) + // .with_initiator(*initiator_account_hash) + // .with_caller_key(initiator_key) + // // TODO: Callee is unnecessary as it can be derived from the + // // execution target inside the executor + // .with_callee_key(initiator_key) + // .with_chain_name(network_name) + // .with_transferred_value(value.into()) // TODO: Remove u128 internally + // .with_block_time(transaction.timestamp()); + + // if let Some(input_data) = input_data.clone() { + // builder = builder.with_input(input_data.clone().take_inner().into()); + // } + + // let execution_kind = match target { + // Target::Session { module_bytes } => + // ExecutionKind::SessionBytes(module_bytes), Target::Stored { + // id: TransactionInvocationTarget::ByHash(address), + // entry_point, + // } => ExecutionKind::Stored { + // address: EntityAddr::SmartContract(address), + // entry_point: entry_point.clone(), + // }, + // Target::Stored { id, entry_point } => { + // todo!("Unsupported target {entry_point} {id:?}") + // } + // Target::Install { .. } => unreachable!(), + // }; + + // builder = builder.with_target(execution_kind); + + // let execute_request = builder.build().expect("should build"); + + // Ok(Self::Execute(execute_request)) + // } + // } + todo!() + } + + pub(crate) fn execute

( + self, + engine: &ExecutorV2, + state_provider: &P, + ) -> Result + where + P: StateProvider + CommitProvider, +

::Reader: 'static, + { + // match self { + // WasmV2Request::Install(install_request) => { + // match engine.install_contract(install_request.state_hash(), state_provider, + // install_request) { Ok(result) => Ok(WasmV2Result::Install(result)), + // Err(error) => Err(WasmV2Error::Install(error)), + // } + // } + // WasmV2Request::Execute(execute_request) => { + // match engine.execute_with_provider(execute_request.state_root_hash(), + // state_provider, execute_request) { + // Ok(result) => Ok(WasmV2Result::Execute(result)), + // Err(error) => Err(WasmV2Error::Execute(error)), + // } + // } + // } + todo!() + } +} + +#[cfg(test)] +mod tests { + #[test] + fn smoke_test() {} +} diff --git a/node/src/components/contract_runtime/types.rs b/node/src/components/contract_runtime/types.rs index 2eaddb537e..558809253d 100644 --- a/node/src/components/contract_runtime/types.rs +++ b/node/src/components/contract_runtime/types.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, sync::Arc}; -use casper_executor_wasm::{WasmV2Error, WasmV2Result}; +use crate::types::TransactionHeader; use casper_types::{execution::PaymentInfo, InitiatorAddr, Transfer}; use datasize::DataSize; use serde::Serialize; @@ -11,18 +11,21 @@ use casper_execution_engine::engine_state::{ use casper_storage::{ block_store::types::ApprovalsHashes, data_access_layer::{ - auction::AuctionMethodError, BalanceHoldResult, BiddingResult, EraValidatorsRequest, - HandleFeeResult, HandleRefundResult, TransferResult, + auction::AuctionMethodError, BalanceHoldResult, BalanceResult, BiddingResult, + EraValidatorsRequest, HandleFeeResult, HandleRefundResult, TransferResult, }, }; use casper_types::{ contract_messages::Messages, execution::{Effects, ExecutionResult, ExecutionResultV2}, BlockHash, BlockHeaderV2, BlockV2, Digest, EraId, Gas, InvalidDeploy, InvalidTransaction, - InvalidTransactionV1, ProtocolVersion, PublicKey, Transaction, TransactionHash, - TransactionHeader, U512, + InvalidTransactionV1, ProtocolVersion, PublicKey, Transaction, TransactionHash, U512, }; +use self::wasm_v2_request::{WasmV2Error, WasmV2Result}; + +use super::operations::wasm_v2_request; + /// Request for validator weights for a specific era. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ValidatorWeightsByEraIdRequest { @@ -84,7 +87,7 @@ impl ExecutionArtifactBuilder { ExecutionArtifactBuilder { effects: Effects::new(), hash: transaction.hash(), - header: transaction.header(), + header: transaction.into(), error_message: None, transfers: vec![], messages: Default::default(), @@ -120,16 +123,43 @@ impl ExecutionArtifactBuilder { self } + pub fn with_initial_balance_result( + &mut self, + balance_result: BalanceResult, + minimum_amount: U512, + ) -> Result<&mut Self, bool> { + if let BalanceResult::RootNotFound = balance_result { + return Err(true); + } + if let (None, Some(err)) = (&self.error_message, balance_result.error()) { + self.error_message = Some(format!("{}", err)); + return Err(false); + } + if let Some(purse) = balance_result.purse_addr() { + let is_sufficient = balance_result.is_sufficient(minimum_amount); + if !is_sufficient { + self.error_message = Some(format!( + "Purse {} has less than {}", + base16::encode_lower(&purse), + minimum_amount + )); + return Ok(self); + } + } + Ok(self) + } + pub fn with_wasm_v1_result(&mut self, wasm_v1_result: WasmV1Result) -> Result<&mut Self, ()> { if let Some(Error::RootNotFound(_)) = wasm_v1_result.error() { return Err(()); } + self.with_added_consumed(wasm_v1_result.consumed()); if let (None, Some(err)) = (&self.error_message, wasm_v1_result.error()) { self.error_message = Some(format!("{}", err)); + return Ok(self); } - self.with_added_consumed(wasm_v1_result.consumed()) + self.with_appended_transfers(&mut wasm_v1_result.transfers().clone()) .with_appended_messages(&mut wasm_v1_result.messages().clone()) - .with_appended_transfers(&mut wasm_v1_result.transfers().clone()) .with_appended_effects(wasm_v1_result.effects().clone()); Ok(self) } @@ -275,6 +305,7 @@ impl ExecutionArtifactBuilder { if let TransferResult::Success { mut transfers, effects, + cache, } = transfer_result { self.with_appended_transfers(&mut transfers) @@ -328,7 +359,7 @@ impl ExecutionArtifactBuilder { /// Adds the error message from a `InvalidRequest` to the artifact. pub(crate) fn with_invalid_wasm_v2_request( &mut self, - ire: &casper_executor_wasm::InvalidRequest, + ire: wasm_v2_request::InvalidRequest, ) -> &mut Self { if self.error_message.is_none() { self.error_message = Some(format!("{}", ire)); @@ -422,6 +453,10 @@ impl SpeculativeExecutionResult { ), } } + + pub fn invalid_transaction(error: InvalidTransaction) -> Self { + SpeculativeExecutionResult::InvalidTransaction(error) + } } /// State to use to construct the next block in the blockchain. Includes the state root hash for the diff --git a/node/src/components/contract_runtime/utils.rs b/node/src/components/contract_runtime/utils.rs index 963e1f562f..fff341e7ff 100644 --- a/node/src/components/contract_runtime/utils.rs +++ b/node/src/components/contract_runtime/utils.rs @@ -36,9 +36,7 @@ use casper_storage::{ }, global_state::state::{lmdb::LmdbGlobalState, CommitProvider, StateProvider}, }; -use casper_types::{ - BlockHash, Chainspec, Digest, EraId, Gas, GasLimited, Key, ProtocolUpgradeConfig, -}; +use casper_types::{BlockHash, Chainspec, Digest, EraId, Gas, Key, ProtocolUpgradeConfig}; /// Maximum number of resource intensive tasks that can be run in parallel. /// @@ -139,12 +137,18 @@ pub(super) async fn exec_or_requeue( let switch_block_utilization_score = { let mut has_hit_slot_limt = false; + let mut transaction_hash_to_lane_id = HashMap::new(); - for (category, transactions) in executable_block.transaction_map.iter() { + for (lane_id, transactions) in executable_block.transaction_map.iter() { + transaction_hash_to_lane_id.extend( + transactions + .iter() + .map(|transaction| (transaction, *lane_id)), + ); let max_count = chainspec .transaction_config .transaction_v1_config - .get_max_transaction_count(*category); + .get_max_transaction_count(*lane_id); if max_count == transactions.len() as u64 { has_hit_slot_limt = true; } @@ -164,16 +168,25 @@ pub(super) async fn exec_or_requeue( Ratio::new(total_size_of_transactions * 100, max_block_size).to_integer() }; - let gas_utilization: u64 = { let total_gas_limit: u64 = executable_block .transactions .iter() - .map(|transaction| match transaction.gas_limit(&chainspec) { - Ok(gas_limit) => gas_limit.value().as_u64(), - Err(_) => { - warn!("Unable to determine gas limit"); - 0u64 + .map(|transaction| { + match transaction_hash_to_lane_id.get(&transaction.hash()) { + Some(lane_id) => { + match &transaction.gas_limit(&chainspec, *lane_id) { + Ok(gas_limit) => gas_limit.value().as_u64(), + Err(_) => { + warn!("Unable to determine gas limit"); + 0u64 + } + } + } + None => { + warn!("Unable to determine gas limit"); + 0u64 + } } }) .sum(); diff --git a/node/src/components/event_stream_server.rs b/node/src/components/event_stream_server.rs index e1f24122f9..e8d9820e3c 100644 --- a/node/src/components/event_stream_server.rs +++ b/node/src/components/event_stream_server.rs @@ -35,13 +35,14 @@ use tokio::sync::{ use tracing::{error, info, warn}; use warp::Filter; -use casper_types::{InitiatorAddr, ProtocolVersion, TransactionHeader}; +use casper_types::{InitiatorAddr, ProtocolVersion}; use super::Component; use crate::{ components::{ComponentState, InitializedComponent, PortBoundComponent}, effect::{EffectBuilder, Effects}, reactor::main_reactor::MainEvent, + types::TransactionHeader, utils::{self, ListeningError}, NodeRng, }; @@ -232,7 +233,7 @@ where fn handle_event( &mut self, - _effect_builder: EffectBuilder, + effect_builder: EffectBuilder, _rng: &mut NodeRng, event: Self::Event, ) -> Effects { @@ -256,7 +257,7 @@ where } ComponentState::Initializing => match event { Event::Initialize => { - let (effects, state) = self.bind(self.config.enable_server, _effect_builder); + let (effects, state) = self.bind(self.config.enable_server, effect_builder); >::set_state(self, state); effects } @@ -304,10 +305,10 @@ where deploy_header.timestamp(), deploy_header.ttl(), ), - TransactionHeader::V1(txn_header) => ( - txn_header.initiator_addr().clone(), - txn_header.timestamp(), - txn_header.ttl(), + TransactionHeader::V1(metadata) => ( + metadata.initiator_addr().clone(), + metadata.timestamp(), + metadata.ttl(), ), }; self.broadcast(SseData::TransactionProcessed { diff --git a/node/src/components/event_stream_server/event.rs b/node/src/components/event_stream_server/event.rs index 7eec8d1b68..d9414ded2b 100644 --- a/node/src/components/event_stream_server/event.rs +++ b/node/src/components/event_stream_server/event.rs @@ -3,13 +3,13 @@ use std::{ sync::Arc, }; +use crate::types::TransactionHeader; use itertools::Itertools; use casper_types::{ contract_messages::Messages, execution::{Effects, ExecutionResult}, Block, BlockHash, EraId, FinalitySignature, PublicKey, Timestamp, Transaction, TransactionHash, - TransactionHeader, }; #[derive(Debug)] diff --git a/node/src/components/event_stream_server/sse_server.rs b/node/src/components/event_stream_server/sse_server.rs index 02351b99bb..994abad7d8 100644 --- a/node/src/components/event_stream_server/sse_server.rs +++ b/node/src/components/event_stream_server/sse_server.rs @@ -234,7 +234,7 @@ pub(super) struct NewSubscriberInfo { /// Maps the `event` to a warp event, or `None` if it's a malformed event (ie.: `ApiVersion` event /// with `id` set or event other than `ApiVersion` without `id`) -async fn map_server_sent_event( +fn map_server_sent_event( event: &ServerSentEvent, ) -> Option> { let id = match event.id { @@ -292,7 +292,7 @@ async fn map_server_sent_event( /// /// If `query` is not empty, returns a 422 response if `query` doesn't have exactly one entry, /// "starts_from" mapped to a value representing an event ID. -fn parse_query(query: HashMap) -> Result, Response> { +fn parse_query(query: &HashMap) -> Result, Response> { if query.is_empty() { return Ok(None); } @@ -374,7 +374,7 @@ impl ChannelsAndFilter { return create_503(); } - let start_from = match parse_query(query) { + let start_from = match parse_query(&query) { Ok(maybe_id) => maybe_id, Err(error_response) => return error_response, }; @@ -489,7 +489,7 @@ fn stream_to_client( .chain(ongoing_stream) .filter_map(move |result| async move { match result { - Ok(event) => map_server_sent_event(&event).await, + Ok(event) => map_server_sent_event(&event), Err(error) => Some(Err(error)), } }) diff --git a/node/src/components/fetcher/fetcher_impls/transaction_fetcher.rs b/node/src/components/fetcher/fetcher_impls/transaction_fetcher.rs index 2b1bdc8e91..1e79c7a9c3 100644 --- a/node/src/components/fetcher/fetcher_impls/transaction_fetcher.rs +++ b/node/src/components/fetcher/fetcher_impls/transaction_fetcher.rs @@ -26,10 +26,7 @@ impl FetchItem for Transaction { } fn validate(&self, _metadata: &EmptyValidationMetadata) -> Result<(), Self::ValidationError> { - match self { - Transaction::Deploy(deploy) => deploy.is_valid().map_err(Into::into), - Transaction::V1(txn) => txn.verify().map_err(Into::into), - } + self.verify() } } diff --git a/node/src/components/fetcher/item_fetcher.rs b/node/src/components/fetcher/item_fetcher.rs index f16be27a53..e86e64c7db 100644 --- a/node/src/components/fetcher/item_fetcher.rs +++ b/node/src/components/fetcher/item_fetcher.rs @@ -241,10 +241,10 @@ pub(super) trait ItemFetcher { } } Err( - error @ Error::Absent { .. } - | error @ Error::Rejected { .. } - | error @ Error::CouldNotConstructGetRequest { .. } - | error @ Error::ValidationMetadataMismatch { .. }, + error @ (Error::Absent { .. } + | Error::Rejected { .. } + | Error::CouldNotConstructGetRequest { .. } + | Error::ValidationMetadataMismatch { .. }), ) => { // For all other error variants we can safely respond with failure as there's no // chance for the request to succeed. diff --git a/node/src/components/gossiper/gossip_table.rs b/node/src/components/gossiper/gossip_table.rs index 7b09cba5dc..a7ce14d921 100644 --- a/node/src/components/gossiper/gossip_table.rs +++ b/node/src/components/gossiper/gossip_table.rs @@ -136,9 +136,8 @@ impl State { exclude_peers: self.attempted_to_infect.clone(), is_already_held: !is_new, }); - } else { - return GossipAction::Noop; } + return GossipAction::Noop; } if is_new { diff --git a/node/src/components/network.rs b/node/src/components/network.rs index 51a8756075..6d16bef4ba 100644 --- a/node/src/components/network.rs +++ b/node/src/components/network.rs @@ -45,7 +45,10 @@ pub(crate) mod tasks; mod tests; use std::{ - collections::{BTreeMap, HashMap, HashSet}, + collections::{ + hash_map::{Entry, HashMap}, + BTreeMap, BTreeSet, HashSet, + }, fmt::{self, Debug, Display, Formatter}, io, net::{SocketAddr, TcpListener}, @@ -54,7 +57,6 @@ use std::{ }; use datasize::DataSize; -use futures::{future::BoxFuture, FutureExt}; use itertools::Itertools; use prometheus::Registry; use rand::{ @@ -74,6 +76,9 @@ use tokio_openssl::SslStream; use tokio_util::codec::LengthDelimitedCodec; use tracing::{debug, error, info, trace, warn, Instrument, Span}; +#[cfg(test)] +use futures::{future::BoxFuture, FutureExt}; + use casper_types::{EraId, PublicKey, SecretKey}; pub(crate) use self::{ @@ -110,7 +115,7 @@ use crate::{ requests::{BeginGossipRequest, NetworkInfoRequest, NetworkRequest, StorageRequest}, AutoClosingResponder, EffectBuilder, EffectExt, Effects, GossipTarget, }, - reactor::{Finalize, ReactorEvent}, + reactor::ReactorEvent, tls, types::{NodeId, ValidatorMatrix}, utils::{self, display_error, Source}, @@ -119,9 +124,6 @@ use crate::{ const COMPONENT_NAME: &str = "network"; -const MAX_METRICS_DROP_ATTEMPTS: usize = 25; -const DROP_RETRY_DELAY: Duration = Duration::from_millis(100); - /// How often to keep attempting to reconnect to a node before giving up. Note that reconnection /// delays increase exponentially! const RECONNECTION_ATTEMPTS: u8 = 8; @@ -179,7 +181,7 @@ where /// Tracks nodes that have announced themselves as nodes that are syncing. syncing_nodes: HashSet, - + #[data_size(skip)] channel_management: Option, /// Networking metrics. @@ -203,26 +205,26 @@ where state: ComponentState, } -#[derive(DataSize)] struct ChannelManagement { /// Channel signaling a shutdown of the network. // Note: This channel is closed when `Network` is dropped, signalling the receivers that // they should cease operation. - #[data_size(skip)] + #[allow(dead_code)] shutdown_sender: Option>, + /// Join handle for the server thread. - #[data_size(skip)] + #[allow(dead_code)] server_join_handle: Option>, /// Channel signaling a shutdown of the incoming connections. // Note: This channel is closed when we finished syncing, so the `Network` can close all // connections. When they are re-established, the proper value of the now updated `is_syncing` // flag will be exchanged on handshake. - #[data_size(skip)] + #[allow(dead_code)] close_incoming_sender: Option>, + /// Handle used by the `message_reader` task to receive a notification that incoming /// connections should be closed. - #[data_size(skip)] close_incoming_receiver: watch::Receiver<()>, } @@ -278,7 +280,7 @@ where ); let context = Arc::new(NetworkContext::new( - cfg.clone(), + &cfg, our_identity, node_key_pair.map(NodeKeyPair::new), chain_info_source.into(), @@ -421,7 +423,7 @@ where total_outgoing_manager_connected_peers += 1; if self.outgoing_limiter.is_validator_in_era(era_id, &peer_id) { total_connected_validators_in_era += 1; - self.send_message(peer_id, msg.clone(), None) + self.send_message(peer_id, msg.clone(), None); } } @@ -441,7 +443,7 @@ where msg: Arc>, gossip_target: GossipTarget, count: usize, - exclude: HashSet, + exclude: &HashSet, ) -> HashSet { let is_validator_in_era = |era: EraId, peer_id: &NodeId| self.outgoing_limiter.is_validator_in_era(era, peer_id); @@ -449,7 +451,7 @@ where rng, gossip_target, count, - exclude.clone(), + exclude, self.outgoing_manager.connected_peers(), is_validator_in_era, ); @@ -556,7 +558,7 @@ where if let Some(symmetries) = self.connection_symmetries.get(&peer_id) { let incoming_count = symmetries .incoming_addrs() - .map(|addrs| addrs.len()) + .map(BTreeSet::len) .unwrap_or_default(); if incoming_count >= self.cfg.max_incoming_peer_connections as usize { @@ -635,19 +637,16 @@ where span.in_scope(|| { // Log the outcome. match result { - Ok(()) => { - info!("regular connection closing") - } - Err(ref err) => { - warn!(err = display_error(err), "connection dropped") - } + Ok(()) => info!("regular connection closing"), + Err(ref err) => warn!(err = display_error(err), "connection dropped"), } // Update the connection symmetries. - self.connection_symmetries - .entry(peer_id) - .or_default() - .remove_incoming(peer_addr, Instant::now()); + if let Entry::Occupied(mut entry) = self.connection_symmetries.entry(peer_id) { + if entry.get_mut().remove_incoming(peer_addr, Instant::now()) { + entry.remove(); + } + } Effects::new() }) @@ -655,7 +654,6 @@ where /// Determines whether an outgoing peer should be blocked based on the connection error. fn is_blockable_offense_for_outgoing( - &self, error: &ConnectionError, ) -> Option { match error { @@ -721,7 +719,7 @@ where // We perform blocking first, to not trigger a reconnection before blocking. let mut requests = Vec::new(); - if let Some(justification) = self.is_blockable_offense_for_outgoing(&error) { + if let Some(justification) = Self::is_blockable_offense_for_outgoing(&error) { requests.extend(self.outgoing_manager.block_addr( peer_addr, now, @@ -853,7 +851,7 @@ where Arc::new(Message::Payload(*payload)), gossip_target, count, - exclude, + &exclude, ); auto_closing_responder.respond(sent_to).ignore() } @@ -869,10 +867,11 @@ where .outgoing_manager .handle_connection_drop(peer_addr, Instant::now()); - self.connection_symmetries - .entry(peer_id) - .or_default() - .unmark_outgoing(Instant::now()); + if let Entry::Occupied(mut entry) = self.connection_symmetries.entry(peer_id) { + if entry.get_mut().unmark_outgoing(Instant::now()) { + entry.remove(); + } + } self.outgoing_limiter.remove_connected_validator(&peer_id); @@ -886,7 +885,7 @@ where { let mut effects = Effects::new(); - for request in requests.into_iter() { + for request in requests { trace!(%request, "processing dial request"); match request { DialRequest::Dial { addr, span } => effects.extend( @@ -901,7 +900,7 @@ where // Dropping the `handle` is enough to signal the connection to shutdown. span.in_scope(|| { debug!("dropping connection, as requested"); - }) + }); } DialRequest::SendPing { peer_id, @@ -1031,7 +1030,14 @@ where } } -impl Finalize for Network +#[cfg(test)] +const MAX_METRICS_DROP_ATTEMPTS: usize = 25; + +#[cfg(test)] +const DROP_RETRY_DELAY: Duration = Duration::from_millis(100); + +#[cfg(test)] +impl crate::reactor::Finalize for Network where REv: Send + 'static, P: Payload, @@ -1052,7 +1058,7 @@ where our_id=%self.context.our_id(), err=display_error(err), "could not join server task cleanly" - ) + ); } } } @@ -1074,7 +1080,7 @@ fn choose_gossip_peers( rng: &mut NodeRng, gossip_target: GossipTarget, count: usize, - exclude: HashSet, + exclude: &HashSet, connected_peers: impl Iterator, is_validator_in_era: F, ) -> HashSet @@ -1442,7 +1448,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT + NON_VALIDATOR_COUNT + 1, - HashSet::new(), + &HashSet::new(), fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1453,7 +1459,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT + NON_VALIDATOR_COUNT, - HashSet::new(), + &HashSet::new(), fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1465,7 +1471,7 @@ mod gossip_target_tests { &mut rng, TARGET, 2 * VALIDATOR_COUNT, - HashSet::new(), + &HashSet::new(), fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1479,7 +1485,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT, - HashSet::new(), + &HashSet::new(), fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1496,7 +1502,7 @@ mod gossip_target_tests { &mut rng, TARGET, 2, - HashSet::new(), + &HashSet::new(), fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1514,7 +1520,7 @@ mod gossip_target_tests { &mut rng, TARGET, 1, - HashSet::new(), + &HashSet::new(), fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1538,7 +1544,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT, - exclude.clone(), + &exclude, fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1556,7 +1562,7 @@ mod gossip_target_tests { &mut rng, TARGET, 3, - exclude.clone(), + &exclude, fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1577,7 +1583,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT + NON_VALIDATOR_COUNT + 1, - HashSet::new(), + &HashSet::new(), fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1588,7 +1594,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT + NON_VALIDATOR_COUNT, - HashSet::new(), + &HashSet::new(), fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1599,7 +1605,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT, - HashSet::new(), + &HashSet::new(), fixture.validators.iter().copied(), fixture.is_validator_in_era(), ); @@ -1612,7 +1618,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT, - HashSet::new(), + &HashSet::new(), fixture.non_validators.iter().copied(), fixture.is_validator_in_era(), ); @@ -1631,7 +1637,7 @@ mod gossip_target_tests { &mut rng, TARGET, VALIDATOR_COUNT, - exclude.clone(), + &exclude, fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); @@ -1655,7 +1661,7 @@ mod gossip_target_tests { &mut rng, TARGET, 1, - exclude.clone(), + &exclude, fixture.all_peers.iter().copied(), fixture.is_validator_in_era(), ); diff --git a/node/src/components/network/event.rs b/node/src/components/network/event.rs index 59c34f1b52..90570c8216 100644 --- a/node/src/components/network/event.rs +++ b/node/src/components/network/event.rs @@ -1,6 +1,7 @@ use std::{ fmt::{self, Debug, Display, Formatter}, - io, mem, + io, + mem::size_of, net::SocketAddr, sync::Arc, }; @@ -22,8 +23,7 @@ use crate::{ protocol::Message as ProtocolMessage, }; -const _NETWORK_EVENT_SIZE: usize = mem::size_of::>(); -const_assert!(_NETWORK_EVENT_SIZE < 65); +const_assert!(size_of::>() < 65); /// A network event. #[derive(Debug, From, Serialize)] diff --git a/node/src/components/network/message_pack_format.rs b/node/src/components/network/message_pack_format.rs index 27a9ee2457..b991c6639e 100644 --- a/node/src/components/network/message_pack_format.rs +++ b/node/src/components/network/message_pack_format.rs @@ -4,10 +4,7 @@ //! our network decoder via `Cargo.toml`; using `tokio_serde::MessagePack` would instead tie it //! to the dependency specified in `tokio_serde`'s `Cargo.toml`. -use std::{ - io::{self, Cursor}, - pin::Pin, -}; +use std::{io, pin::Pin}; use bytes::{Bytes, BytesMut}; use serde::{Deserialize, Serialize}; @@ -41,7 +38,6 @@ where #[inline] fn deserialize(self: Pin<&mut Self>, src: &BytesMut) -> Result { - rmp_serde::from_read(Cursor::new(src)) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) + rmp_serde::from_read_ref(src).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) } } diff --git a/node/src/components/network/metrics.rs b/node/src/components/network/metrics.rs index 9cf382a2b4..3f296f7a86 100644 --- a/node/src/components/network/metrics.rs +++ b/node/src/components/network/metrics.rs @@ -474,11 +474,11 @@ impl Metrics { } MessageKind::BlockGossip => { metrics.out_bytes_block_gossip.inc_by(size); - metrics.out_count_block_gossip.inc() + metrics.out_count_block_gossip.inc(); } MessageKind::FinalitySignatureGossip => { metrics.out_bytes_finality_signature_gossip.inc_by(size); - metrics.out_count_finality_signature_gossip.inc() + metrics.out_count_finality_signature_gossip.inc(); } MessageKind::AddressGossip => { metrics.out_bytes_address_gossip.inc_by(size); diff --git a/node/src/components/network/outgoing.rs b/node/src/components/network/outgoing.rs index dab88505d9..550a8a6394 100644 --- a/node/src/components/network/outgoing.rs +++ b/node/src/components/network/outgoing.rs @@ -89,9 +89,6 @@ //! precedence over the previous one. This prevents problems when a notification of a terminated //! connection is overtaken by the new connection announcement. -// Clippy has a lot of false positives due to `span.clone()`-closures. -#![allow(clippy::redundant_clone)] - use std::{ collections::{hash_map::Entry, HashMap}, error::Error, @@ -102,7 +99,6 @@ use std::{ }; use datasize::DataSize; - use prometheus::IntGauge; use rand::Rng; use tracing::{debug, error, error_span, field::Empty, info, trace, warn, Span}; @@ -230,9 +226,9 @@ impl DialOutcome { /// Retrieves the socket address from the `DialOutcome`. fn addr(&self) -> SocketAddr { match self { - DialOutcome::Successful { addr, .. } => *addr, - DialOutcome::Failed { addr, .. } => *addr, - DialOutcome::Loopback { addr, .. } => *addr, + DialOutcome::Successful { addr, .. } + | DialOutcome::Failed { addr, .. } + | DialOutcome::Loopback { addr, .. } => *addr, } } } @@ -305,7 +301,7 @@ impl OutgoingConfig { /// `failed_attempts` (n) is the number of previous attempts *before* the current failure (thus /// starting at 0). The backoff time will be double for each attempt. fn calc_backoff(&self, failed_attempts: u8) -> Duration { - 2u32.pow(failed_attempts as u32) * self.base_timeout + (1u32 << failed_attempts as u32) * self.base_timeout } } @@ -539,7 +535,7 @@ where /// Iterates over all connected peer IDs. pub(crate) fn connected_peers(&'_ self) -> impl Iterator + '_ { - self.routes.keys().cloned() + self.routes.keys().copied() } /// Notify about a potentially new address that has been discovered. @@ -718,7 +714,7 @@ where let mut to_reconnect = Vec::new(); let mut to_ping = Vec::new(); - for (&addr, outgoing) in self.outgoing.iter_mut() { + for (&addr, outgoing) in &mut self.outgoing { // Note: `Span::in_scope` is no longer serviceable here due to borrow limitations. let _span_guard = make_span(addr, Some(outgoing)).entered(); @@ -806,12 +802,12 @@ where } // Remove all addresses marked for forgetting. - to_forget.into_iter().for_each(|addr| { + for addr in to_forget { self.outgoing.remove(&addr); - }); + } // Fail connections that are taking way too long to connect. - to_fail.into_iter().for_each(|(addr, failures_so_far)| { + for (addr, failures_so_far) in to_fail { let span = make_span(addr, self.outgoing.get(&addr)); span.in_scope(|| { @@ -824,7 +820,7 @@ where }, ) }); - }); + } let mut dial_requests = Vec::new(); diff --git a/node/src/components/network/tasks.rs b/node/src/components/network/tasks.rs index 671c2a11f5..f2a637a384 100644 --- a/node/src/components/network/tasks.rs +++ b/node/src/components/network/tasks.rs @@ -146,7 +146,7 @@ where Span::current().record("peer_id", &field::display(peer_id)); if peer_id == context.our_id { - info!("incoming loopback connection"); + info!("outgoing loopback connection"); return OutgoingConnection::Loopback { peer_addr }; } @@ -240,7 +240,7 @@ where impl NetworkContext { pub(super) fn new( - cfg: Config, + cfg: &Config, our_identity: Identity, node_key_pair: Option, chain_info: ChainInfo, diff --git a/node/src/components/rest_server.rs b/node/src/components/rest_server.rs index d5c2d7bce4..e6b0d23d61 100644 --- a/node/src/components/rest_server.rs +++ b/node/src/components/rest_server.rs @@ -28,10 +28,16 @@ mod info; use std::{net::SocketAddr, sync::Arc}; use datasize::DataSize; -use futures::{future::BoxFuture, join, FutureExt}; +use futures::join; use once_cell::sync::OnceCell; use tokio::{sync::oneshot, task::JoinHandle}; -use tracing::{debug, error, info, warn}; +use tracing::{error, info, warn}; + +#[cfg(test)] +use futures::{future::BoxFuture, FutureExt}; + +#[cfg(test)] +use tracing::debug; use casper_types::ProtocolVersion; @@ -46,7 +52,7 @@ use crate::{ }, EffectBuilder, EffectExt, Effects, }, - reactor::{main_reactor::MainEvent, Finalize}, + reactor::main_reactor::MainEvent, types::{ChainspecInfo, StatusFeed}, utils::{self, ListeningError}, NodeRng, @@ -95,11 +101,13 @@ impl ReactorEventT for REv where pub(crate) struct InnerRestServer { /// When the message is sent, it signals the server loop to exit cleanly. #[data_size(skip)] + #[allow(dead_code)] shutdown_sender: oneshot::Sender<()>, /// The address the server is listening on. local_addr: Arc>, /// The task handle which will only join once the server loop has exited. #[data_size(skip)] + #[allow(dead_code)] server_join_handle: Option>, /// The network name, as specified in the chainspec network_name: String, @@ -321,7 +329,8 @@ where } } -impl Finalize for RestServer { +#[cfg(test)] +impl crate::reactor::Finalize for RestServer { fn finalize(self) -> BoxFuture<'static, ()> { async { if let Some(mut rest_server) = self.inner_rest { diff --git a/node/src/components/rest_server/http_server.rs b/node/src/components/rest_server/http_server.rs index c632044949..55de370dd3 100644 --- a/node/src/components/rest_server/http_server.rs +++ b/node/src/components/rest_server/http_server.rs @@ -54,7 +54,7 @@ pub(super) async fn run( // Shutdown the server gracefully. let _ = server - .with_graceful_shutdown(async { + .with_graceful_shutdown(async move { shutdown_receiver.await.ok(); }) .map_err(|error| { @@ -109,7 +109,7 @@ pub(super) async fn run_with_cors( // Shutdown the server gracefully. let _ = server - .with_graceful_shutdown(async { + .with_graceful_shutdown(async move { shutdown_receiver.await.ok(); }) .map_err(|error| { diff --git a/node/src/components/storage.rs b/node/src/components/storage.rs index 496e1c59ee..1b530b8594 100644 --- a/node/src/components/storage.rs +++ b/node/src/components/storage.rs @@ -69,8 +69,7 @@ use casper_types::{ Approval, ApprovalsHash, AvailableBlockRange, Block, BlockBody, BlockHash, BlockHeader, BlockSignatures, BlockSignaturesV1, BlockSignaturesV2, BlockV2, ChainNameDigest, DeployHash, EraId, ExecutionInfo, FinalitySignature, ProtocolVersion, SignedBlockHeader, Timestamp, - Transaction, TransactionConfig, TransactionHash, TransactionHeader, TransactionId, Transfer, - U512, + Transaction, TransactionConfig, TransactionHash, TransactionId, Transfer, U512, }; use datasize::DataSize; use num_rational::Ratio; @@ -94,7 +93,7 @@ use crate::{ types::{ BlockExecutionResultsOrChunk, BlockExecutionResultsOrChunkId, BlockWithMetadata, ExecutableBlock, LegacyDeploy, MaxTtl, NodeId, NodeRng, SyncLeap, SyncLeapIdentifier, - VariantMismatch, + TransactionHeader, VariantMismatch, }, utils::{display_error, WithDir}, }; @@ -608,7 +607,7 @@ impl Storage { } => { let mut era_ids = HashSet::new(); let txn = self.block_store.checkout_ro()?; - for transaction_hash in transaction_hashes.iter() { + for transaction_hash in &transaction_hashes { let maybe_block_info: Option = txn.read(*transaction_hash)?; if let Some(block_info) = maybe_block_info { @@ -670,7 +669,7 @@ impl Storage { responder, } => { let ro_txn = self.block_store.checkout_ro()?; - let maybe_transaction = match self.get_transaction_with_finalized_approvals( + let maybe_transaction = match Self::get_transaction_with_finalized_approvals( &ro_txn, &transaction_id.transaction_hash(), )? { @@ -694,9 +693,10 @@ impl Storage { let ro_txn = self.block_store.checkout_ro()?; let transaction = if with_finalized_approvals { - match self - .get_transaction_with_finalized_approvals(&ro_txn, &transaction_hash)? - { + match Self::get_transaction_with_finalized_approvals( + &ro_txn, + &transaction_hash, + )? { Some((transaction, maybe_approvals)) => { if let Some(approvals) = maybe_approvals { transaction.with_approvals(approvals) @@ -747,9 +747,10 @@ impl Storage { } => { let txn = self.block_store.checkout_ro()?; responder - .respond( - self.get_execution_results_with_transaction_headers(&txn, &block_hash)?, - ) + .respond(Self::get_execution_results_with_transaction_headers( + &txn, + &block_hash, + )?) .ignore() } StorageRequest::GetBlockExecutionResultsOrChunk { id, responder } => responder @@ -793,7 +794,7 @@ impl Storage { only_from_available_block_range, responder, } => { - if !(self.should_return_block(block_height, only_from_available_block_range)?) { + if !(self.should_return_block(block_height, only_from_available_block_range)) { return Ok(responder.respond(None).ignore()); } @@ -1023,7 +1024,7 @@ impl Storage { block_height: u64, only_from_available_block_range: bool, ) -> Result, FatalStorageError> { - if !(self.should_return_block(block_height, only_from_available_block_range)?) { + if !(self.should_return_block(block_height, only_from_available_block_range)) { Ok(None) } else { let txn = self.block_store.checkout_ro()?; @@ -1050,7 +1051,7 @@ impl Storage { transaction_hashes .map(|transaction_hash| { - self.get_transaction_with_finalized_approvals(&ro_txn, transaction_hash) + Self::get_transaction_with_finalized_approvals(&ro_txn, transaction_hash) .map_err(FatalStorageError::from) }) .collect() @@ -1202,9 +1203,7 @@ impl Storage { /// Retrieves the height of the highest complete block (if any). pub(crate) fn highest_complete_block_height(&self) -> Option { - self.completed_blocks - .highest_sequence() - .map(|sequence| sequence.high()) + self.completed_blocks.highest_sequence().map(Sequence::high) } /// Retrieves the contiguous segment of the block chain starting at the highest known switch @@ -1337,7 +1336,7 @@ impl Storage { .into_iter() .flatten() { - transactions.push(transaction) + transactions.push(transaction); } Ok(Some((block, transactions))) @@ -1424,7 +1423,7 @@ impl Storage { None => return Ok(None), }; - if !(self.should_return_block(block_header.height(), only_from_available_block_range)?) { + if !(self.should_return_block(block_header.height(), only_from_available_block_range)) { return Ok(None); } @@ -1506,7 +1505,7 @@ impl Storage { )), }, }; - result.push(SignedBlockHeader::new(block_header, block_signatures)) + result.push(SignedBlockHeader::new(block_header, block_signatures)); } None => return Ok(None), } @@ -1615,7 +1614,7 @@ impl Storage { let transaction_hash = TransactionHash::from(deploy_hash); let txn = self.block_store.checkout_ro()?; let transaction = - match self.get_transaction_with_finalized_approvals(&txn, &transaction_hash)? { + match Self::get_transaction_with_finalized_approvals(&txn, &transaction_hash)? { Some((transaction, maybe_approvals)) => { if let Some(approvals) = maybe_approvals { transaction.with_approvals(approvals) @@ -1695,7 +1694,6 @@ impl Storage { /// Retrieves a single transaction along with its finalized approvals. #[allow(clippy::type_complexity)] fn get_transaction_with_finalized_approvals( - &self, txn: &(impl DataReader + DataReader>), transaction_hash: &TransactionHash, @@ -1827,11 +1825,11 @@ impl Storage { &self, block_height: u64, only_from_available_block_range: bool, - ) -> Result { + ) -> bool { if only_from_available_block_range { - Ok(self.get_available_block_range().contains(block_height)) + self.get_available_block_range().contains(block_height) } else { - Ok(true) + true } } @@ -1903,7 +1901,7 @@ impl Storage { ) -> Result, FatalStorageError> { let txn = self.block_store.checkout_ro()?; - let execution_results = match self.get_execution_results(&txn, request.block_hash())? { + let execution_results = match Self::get_execution_results(&txn, request.block_hash())? { Some(execution_results) => execution_results .into_iter() .map(|(_deploy_hash, execution_result)| execution_result) @@ -1952,7 +1950,6 @@ impl Storage { } fn get_execution_results( - &self, txn: &(impl DataReader + DataReader), block_hash: &BlockHash, ) -> Result>, FatalStorageError> { @@ -1991,14 +1988,13 @@ impl Storage { #[allow(clippy::type_complexity)] fn get_execution_results_with_transaction_headers( - &self, txn: &(impl DataReader + DataReader + DataReader), block_hash: &BlockHash, ) -> Result>, FatalStorageError> { - let execution_results = match self.get_execution_results(txn, block_hash)? { + let execution_results = match Self::get_execution_results(txn, block_hash)? { Some(execution_results) => execution_results, None => return Ok(None), }; @@ -2019,11 +2015,9 @@ impl Storage { deploy.take_header().into(), execution_result, )), - Some(Transaction::V1(transaction_v1)) => ret.push(( - transaction_hash, - transaction_v1.take_header().into(), - execution_result, - )), + Some(Transaction::V1(transaction_v1)) => { + ret.push((transaction_hash, (&transaction_v1).into(), execution_result)) + } }; } Ok(Some(ret)) @@ -2077,14 +2071,15 @@ fn should_move_storage_files_to_network_subdir( let mut files_found = vec![]; let mut files_not_found = vec![]; - file_names.iter().for_each(|file_name| { + for file_name in file_names { let file_path = root.join(file_name); - match file_path.exists() { - true => files_found.push(file_path), - false => files_not_found.push(file_path), + if file_path.exists() { + files_found.push(file_path); + } else { + files_not_found.push(file_path); } - }); + } let should_move_files = files_found.len() == file_names.len(); @@ -2179,7 +2174,7 @@ impl Storage { .block_store .checkout_ro() .expect("could not create RO transaction"); - self.get_transaction_with_finalized_approvals(&txn, transaction_hash) + Self::get_transaction_with_finalized_approvals(&txn, transaction_hash) .expect("could not retrieve a transaction with finalized approvals from storage") } @@ -2281,10 +2276,7 @@ impl Storage { .expect("should create ro txn"); let block: Block = ro_txn.read(block_hash).expect("should read block")?; - if !(self - .should_return_block(block.height(), only_from_available_block_range) - .expect("should check if block is in range")) - { + if !(self.should_return_block(block.height(), only_from_available_block_range)) { return None; } if block_hash != *block.hash() { @@ -2313,10 +2305,7 @@ impl Storage { height: u64, only_from_available_block_range: bool, ) -> Option { - if !(self - .should_return_block(height, only_from_available_block_range) - .expect("should check if block is in range")) - { + if !(self.should_return_block(height, only_from_available_block_range)) { return None; } let ro_txn = self diff --git a/node/src/components/storage/tests.rs b/node/src/components/storage/tests.rs index 79778511fd..900a6e2413 100644 --- a/node/src/components/storage/tests.rs +++ b/node/src/components/storage/tests.rs @@ -2147,51 +2147,23 @@ fn should_restrict_returned_blocks() { // Without restriction, the node should attempt to return any requested block // regardless if it is in the disjoint sequences. - assert!(storage - .should_return_block(0, false) - .expect("should return block failed")); - assert!(storage - .should_return_block(1, false) - .expect("should return block failed")); - assert!(storage - .should_return_block(2, false) - .expect("should return block failed")); - assert!(storage - .should_return_block(3, false) - .expect("should return block failed")); - assert!(storage - .should_return_block(4, false) - .expect("should return block failed")); - assert!(storage - .should_return_block(5, false) - .expect("should return block failed")); - assert!(storage - .should_return_block(6, false) - .expect("should return block failed")); + assert!(storage.should_return_block(0, false)); + assert!(storage.should_return_block(1, false)); + assert!(storage.should_return_block(2, false)); + assert!(storage.should_return_block(3, false)); + assert!(storage.should_return_block(4, false)); + assert!(storage.should_return_block(5, false)); + assert!(storage.should_return_block(6, false)); // With restriction, the node should attempt to return only the blocks that are // on the highest disjoint sequence, i.e blocks 4 and 5 only. - assert!(!storage - .should_return_block(0, true) - .expect("should return block failed")); - assert!(!storage - .should_return_block(1, true) - .expect("should return block failed")); - assert!(!storage - .should_return_block(2, true) - .expect("should return block failed")); - assert!(!storage - .should_return_block(3, true) - .expect("should return block failed")); - assert!(storage - .should_return_block(4, true) - .expect("should return block failed")); - assert!(storage - .should_return_block(5, true) - .expect("should return block failed")); - assert!(!storage - .should_return_block(6, true) - .expect("should return block failed")); + assert!(!storage.should_return_block(0, true)); + assert!(!storage.should_return_block(1, true)); + assert!(!storage.should_return_block(2, true)); + assert!(!storage.should_return_block(3, true)); + assert!(storage.should_return_block(4, true)); + assert!(storage.should_return_block(5, true)); + assert!(!storage.should_return_block(6, true)); } #[test] diff --git a/node/src/components/transaction_acceptor.rs b/node/src/components/transaction_acceptor.rs index 4745d09db1..bf71a975b4 100644 --- a/node/src/components/transaction_acceptor.rs +++ b/node/src/components/transaction_acceptor.rs @@ -6,6 +6,7 @@ mod tests; use std::{collections::BTreeSet, fmt::Debug, sync::Arc}; +use casper_types::{InvalidTransaction, InvalidTransactionV1}; use datasize::DataSize; use prometheus::Registry; use tracing::{debug, error, trace}; @@ -17,7 +18,7 @@ use casper_types::{ AddressableEntityHash, AddressableEntityIdentifier, BlockHeader, Chainspec, EntityAddr, EntityKind, EntityVersion, EntityVersionKey, EntryPoint, EntryPointAddr, ExecutableDeployItem, ExecutableDeployItemIdentifier, InitiatorAddr, Key, Package, PackageAddr, PackageHash, - PackageIdentifier, Transaction, TransactionEntryPoint, TransactionInvocationTarget, + PackageIdentifier, Timestamp, Transaction, TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntime, TransactionTarget, DEFAULT_ENTRY_POINT_NAME, U512, }; @@ -29,6 +30,7 @@ use crate::{ EffectBuilder, EffectExt, Effects, Responder, }, fatal, + types::MetaTransaction, utils::Source, NodeRng, }; @@ -105,36 +107,60 @@ impl TransactionAcceptor { fn accept( &mut self, effect_builder: EffectBuilder, - transaction: Transaction, + input_transaction: Transaction, source: Source, maybe_responder: Option>>, ) -> Effects { - debug!(%source, %transaction, "checking transaction before accepting"); - let event_metadata = Box::new(EventMetadata::new(transaction, source, maybe_responder)); + debug!(%source, %input_transaction, "checking transaction before accepting"); + let verification_start_timestamp = Timestamp::now(); + let transaction_config = &self.chainspec.as_ref().transaction_config; + let maybe_meta_transaction = MetaTransaction::from(&input_transaction, transaction_config); + let meta_transaction = match maybe_meta_transaction { + Ok(transaction) => transaction, + Err(err) => { + return self.reject_transaction_direct( + effect_builder, + input_transaction, + source, + maybe_responder, + verification_start_timestamp, + Error::InvalidTransaction(err), + ); + } + }; - if transaction.is_install_or_upgrade() - && transaction.is_v2_wasm() - && transaction.seed().is_none() + let event_metadata = Box::new(EventMetadata::new( + input_transaction, + meta_transaction.clone(), + source, + maybe_responder, + verification_start_timestamp, + )); + + if meta_transaction.is_install_or_upgrade() + && meta_transaction.is_v2_wasm() + && meta_transaction.seed().is_none() { return self.reject_transaction( effect_builder, *event_metadata, - Error::InvalidTransaction("missing seed for install or upgrade".to_string()), + Error::InvalidTransaction(InvalidTransaction::V1( + InvalidTransactionV1::MissingSeed, + )), ); } - let is_config_compliant = event_metadata.transaction.is_config_compliant( - &self.chainspec, - self.acceptor_config.timestamp_leeway, - event_metadata.verification_start_timestamp, - ); + let is_config_compliant = event_metadata + .meta_transaction + .is_config_compliant( + &self.chainspec, + self.acceptor_config.timestamp_leeway, + verification_start_timestamp, + ) + .map_err(Error::InvalidTransaction); if let Err(error) = is_config_compliant { - return self.reject_transaction( - effect_builder, - *event_metadata, - Error::InvalidTransaction(error), - ); + return self.reject_transaction(effect_builder, *event_metadata, error); } // We only perform expiry checks on transactions received from the client. @@ -360,11 +386,11 @@ impl TransactionAcceptor { event_metadata: Box, block_header: Box, ) -> Effects { - match &event_metadata.transaction { - Transaction::Deploy(_) => { + match &event_metadata.meta_transaction { + MetaTransaction::Deploy(_) => { self.verify_deploy_session(effect_builder, event_metadata, block_header) } - Transaction::V1(_) => { + MetaTransaction::V1(_) => { self.verify_transaction_v1_body(effect_builder, event_metadata, block_header) } } @@ -376,9 +402,9 @@ impl TransactionAcceptor { event_metadata: Box, block_header: Box, ) -> Effects { - let session = match &event_metadata.transaction { - Transaction::Deploy(deploy) => deploy.session(), - Transaction::V1(txn) => { + let session = match &event_metadata.meta_transaction { + MetaTransaction::Deploy(deploy) => deploy.session(), + MetaTransaction::V1(txn) => { error!(%txn, "should only handle deploys in verify_deploy_session"); return self.reject_transaction( effect_builder, @@ -482,8 +508,8 @@ impl TransactionAcceptor { CryptoValidation, } - let next_step = match &event_metadata.transaction { - Transaction::Deploy(deploy) => { + let next_step = match &event_metadata.meta_transaction { + MetaTransaction::Deploy(deploy) => { error!( %deploy, "should only handle version 1 transactions in verify_transaction_v1_body" @@ -494,7 +520,7 @@ impl TransactionAcceptor { Error::ExpectedTransactionV1, ); } - Transaction::V1(txn) => match txn.target() { + MetaTransaction::V1(txn) => match txn.target() { TransactionTarget::Stored { id, .. } => match id { TransactionInvocationTarget::ByHash(entity_addr) => { NextStep::GetContract(EntityAddr::SmartContract(*entity_addr)) @@ -566,16 +592,18 @@ impl TransactionAcceptor { } }; - let maybe_entry_point_name = match &event_metadata.transaction { - Transaction::Deploy(deploy) if is_payment => { + let maybe_entry_point_name = match &event_metadata.meta_transaction { + MetaTransaction::Deploy(deploy) if is_payment => { Some(deploy.payment().entry_point_name().to_string()) } - Transaction::Deploy(deploy) => Some(deploy.session().entry_point_name().to_string()), - Transaction::V1(_) if is_payment => { + MetaTransaction::Deploy(deploy) => { + Some(deploy.session().entry_point_name().to_string()) + } + MetaTransaction::V1(_) if is_payment => { error!("should not fetch a contract to validate payment logic for transaction v1s"); None } - Transaction::V1(txn) => match txn.entry_point() { + MetaTransaction::V1(txn) => match txn.entry_point() { TransactionEntryPoint::Call => Some(DEFAULT_ENTRY_POINT_NAME.to_owned()), TransactionEntryPoint::Custom(name) => Some(name.clone()), TransactionEntryPoint::Transfer @@ -759,11 +787,11 @@ impl TransactionAcceptor { effect_builder: EffectBuilder, event_metadata: Box, ) -> Effects { - let is_valid = match &event_metadata.transaction { - Transaction::Deploy(deploy) => deploy + let is_valid = match &event_metadata.meta_transaction { + MetaTransaction::Deploy(deploy) => deploy .is_valid() .map_err(|err| Error::InvalidTransaction(err.into())), - Transaction::V1(txn) => txn + MetaTransaction::V1(txn) => txn .verify() .map_err(|err| Error::InvalidTransaction(err.into())), }; @@ -797,11 +825,32 @@ impl TransactionAcceptor { ) -> Effects { debug!(%error, transaction = %event_metadata.transaction, "rejected transaction"); let EventMetadata { + meta_transaction: _, transaction, source, maybe_responder, verification_start_timestamp, } = event_metadata; + self.reject_transaction_direct( + effect_builder, + transaction, + source, + maybe_responder, + verification_start_timestamp, + error, + ) + } + + fn reject_transaction_direct( + &self, + effect_builder: EffectBuilder, + transaction: Transaction, + source: Source, + maybe_responder: Option>>, + verification_start_timestamp: Timestamp, + error: Error, + ) -> Effects { + debug!(%error, transaction = %transaction, "rejected transaction"); self.metrics.observe_rejected(verification_start_timestamp); let mut effects = Effects::new(); if let Some(responder) = maybe_responder { @@ -869,6 +918,7 @@ impl TransactionAcceptor { is_new: bool, ) -> Effects { let EventMetadata { + meta_transaction: _, transaction, source, maybe_responder, diff --git a/node/src/components/transaction_acceptor/event.rs b/node/src/components/transaction_acceptor/event.rs index a2962d492f..46380239ad 100644 --- a/node/src/components/transaction_acceptor/event.rs +++ b/node/src/components/transaction_acceptor/event.rs @@ -8,12 +8,13 @@ use casper_types::{ }; use super::{Error, Source}; -use crate::effect::Responder; +use crate::{effect::Responder, types::MetaTransaction}; /// A utility struct to hold duplicated information across events. #[derive(Debug, Serialize)] pub(crate) struct EventMetadata { pub(crate) transaction: Transaction, + pub(crate) meta_transaction: MetaTransaction, pub(crate) source: Source, pub(crate) maybe_responder: Option>>, pub(crate) verification_start_timestamp: Timestamp, @@ -22,14 +23,17 @@ pub(crate) struct EventMetadata { impl EventMetadata { pub(crate) fn new( transaction: Transaction, + meta_transaction: MetaTransaction, source: Source, maybe_responder: Option>>, + verification_start_timestamp: Timestamp, ) -> Self { EventMetadata { transaction, + meta_transaction, source, maybe_responder, - verification_start_timestamp: Timestamp::now(), + verification_start_timestamp, } } } diff --git a/node/src/components/transaction_acceptor/tests.rs b/node/src/components/transaction_acceptor/tests.rs index 75875bd10d..868501d381 100644 --- a/node/src/components/transaction_acceptor/tests.rs +++ b/node/src/components/transaction_acceptor/tests.rs @@ -1,7 +1,7 @@ #![cfg(test)] use std::{ - collections::VecDeque, + collections::{BTreeMap, VecDeque}, fmt::{self, Debug, Display, Formatter}, iter, sync::Arc, @@ -37,8 +37,8 @@ use casper_types::{ Block, BlockV2, CLValue, Chainspec, ChainspecRawBytes, Contract, Deploy, EntryPointValue, EraId, HashAddr, InvalidDeploy, InvalidTransaction, InvalidTransactionV1, Package, PricingMode, ProtocolVersion, PublicKey, SecretKey, StoredValue, TestBlockBuilder, TimeDiff, Timestamp, - Transaction, TransactionConfig, TransactionLane, TransactionRuntime, TransactionV1, - TransactionV1Builder, URef, U512, + Transaction, TransactionConfig, TransactionRuntime, TransactionV1, TransactionV1Builder, URef, + U512, }; use super::*; @@ -214,6 +214,8 @@ enum TestScenario { InvalidPricingModeForTransactionV1, TooLowGasPriceToleranceForTransactionV1, TooLowGasPriceToleranceForDeploy, + InvalidFields, + InvalidFieldsFromPeer, } impl TestScenario { @@ -230,7 +232,8 @@ impl TestScenario { | TestScenario::FromPeerCustomPaymentContract(_) | TestScenario::FromPeerCustomPaymentContractPackage(_) | TestScenario::FromPeerSessionContract(..) - | TestScenario::FromPeerSessionContractPackage(..) => Source::Peer(NodeId::random(rng)), + | TestScenario::FromPeerSessionContractPackage(..) + | TestScenario::InvalidFieldsFromPeer => Source::Peer(NodeId::random(rng)), TestScenario::FromClientInvalidTransaction(_) | TestScenario::FromClientSlightlyFutureDatedTransaction(_) | TestScenario::FromClientFutureDatedTransaction(_) @@ -256,7 +259,8 @@ impl TestScenario { | TestScenario::DeployWithNativeTransferInPayment | TestScenario::InvalidPricingModeForTransactionV1 | TestScenario::TooLowGasPriceToleranceForTransactionV1 - | TestScenario::TooLowGasPriceToleranceForDeploy => Source::Client, + | TestScenario::TooLowGasPriceToleranceForDeploy + | TestScenario::InvalidFields => Source::Client, } } @@ -282,9 +286,11 @@ impl TestScenario { TestScenario::FromPeerExpired(TxnType::V1) | TestScenario::FromClientExpired(TxnType::V1) => { let txn = TransactionV1Builder::new_session( - TransactionLane::Large, + false, Bytes::from(vec![1]), TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::zero()) @@ -307,9 +313,11 @@ impl TestScenario { TxnType::Deploy => Transaction::from(Deploy::random_valid_native_transfer(rng)), TxnType::V1 => { let txn = TransactionV1Builder::new_session( - TransactionLane::Large, + false, Bytes::from(vec![1]), TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::now()) @@ -326,9 +334,11 @@ impl TestScenario { } TestScenario::FromClientSignedByAdmin(TxnType::V1) => { let txn = TransactionV1Builder::new_session( - TransactionLane::Large, + false, Bytes::from(vec![1]), TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::now()) @@ -410,6 +420,7 @@ impl TestScenario { "Test", "call", TransactionRuntime::VmCasperV1, + 0, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::now()) @@ -423,6 +434,7 @@ impl TestScenario { AddressableEntityHash::new(HashAddr::default()), "call", TransactionRuntime::VmCasperV1, + 0, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::now()) @@ -436,6 +448,7 @@ impl TestScenario { AddressableEntityHash::new(HashAddr::default()), "non-existent-entry-point", TransactionRuntime::VmCasperV1, + 0, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::now()) @@ -478,6 +491,7 @@ impl TestScenario { None, "call", TransactionRuntime::VmCasperV1, + 0, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::now()) @@ -492,6 +506,7 @@ impl TestScenario { None, "call", TransactionRuntime::VmCasperV1, + 0, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::now()) @@ -506,6 +521,7 @@ impl TestScenario { Some(6), "call", TransactionRuntime::VmCasperV1, + 0, ) .with_chain_name("casper-example") .with_timestamp(Timestamp::now()) @@ -532,9 +548,11 @@ impl TestScenario { ), TxnType::V1 => { let txn = TransactionV1Builder::new_session( - TransactionLane::Large, + false, Bytes::from(vec![1]), TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name("casper-example") .with_timestamp(timestamp) @@ -559,9 +577,11 @@ impl TestScenario { ), TxnType::V1 => { let txn = TransactionV1Builder::new_session( - TransactionLane::Large, + false, Bytes::from(vec![1]), TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name("casper-example") .with_timestamp(timestamp) @@ -591,6 +611,7 @@ impl TestScenario { let fixed_mode_transaction = TransactionV1Builder::new_random(rng) .with_pricing_mode(PricingMode::Fixed { gas_price_tolerance: TOO_LOW_GAS_PRICE_TOLERANCE, + additional_computation_factor: 0, }) .with_chain_name("casper-example") .build() @@ -603,6 +624,24 @@ impl TestScenario { let deploy = Deploy::random_with_gas_price(rng, TOO_LOW_GAS_PRICE_TOLERANCE); Transaction::from(deploy) } + TestScenario::InvalidFields | TestScenario::InvalidFieldsFromPeer => { + let mut additional_fields = BTreeMap::new(); + additional_fields.insert(5, Bytes::from(vec![1])); + let txn = TransactionV1Builder::new_session( + false, + Bytes::from(vec![1]), + TransactionRuntime::VmCasperV1, + 0, + None, + ) + .with_chain_name("casper-example") + .with_ttl(TimeDiff::from_seconds(300)) + .with_secret_key(&secret_key) + .with_additional_fields(additional_fields) + .build() + .unwrap(); + Transaction::from(txn) + } } } @@ -659,6 +698,8 @@ impl TestScenario { TestScenario::InvalidPricingModeForTransactionV1 => false, TestScenario::TooLowGasPriceToleranceForTransactionV1 => false, TestScenario::TooLowGasPriceToleranceForDeploy => false, + TestScenario::InvalidFields => false, + TestScenario::InvalidFieldsFromPeer => false, } } @@ -819,7 +860,7 @@ impl reactor::Reactor for Reactor { .ignore::(); return Effects::new(); } - BalanceIdentifier::Payment => { + BalanceIdentifier::Payment | BalanceIdentifier::PenalizedPayment => { responder .respond(BalanceResult::Failure( TrackingCopyError::NamedKeyNotFound("payment".to_string()), @@ -1073,12 +1114,20 @@ fn inject_balance_check_for_peer( source: Source, rng: &mut TestRng, responder: Responder>, + chainspec: &Chainspec, ) -> impl FnOnce(EffectBuilder) -> Effects { let txn = txn.clone(); let block = TestBlockBuilder::new().build(rng); let block_header = Box::new(block.header().clone().into()); + let meta_transaction = MetaTransaction::from(&txn, &chainspec.transaction_config).unwrap(); |effect_builder: EffectBuilder| { - let event_metadata = Box::new(EventMetadata::new(txn, source, Some(responder))); + let event_metadata = Box::new(EventMetadata::new( + txn, + meta_transaction, + source, + Some(responder), + Timestamp::now(), + )); effect_builder .into_inner() .schedule( @@ -1104,9 +1153,10 @@ async fn run_transaction_acceptor_without_timeout( <(Chainspec, ChainspecRawBytes)>::from_resources("local"); chainspec.core_config.administrators = iter::once(PublicKey::from(&admin)).collect(); + let chainspec = Arc::new(chainspec); let mut runner: Runner> = Runner::new( test_scenario, - Arc::new(chainspec), + chainspec.clone(), Arc::new(chainspec_raw_bytes), rng, ) @@ -1156,12 +1206,14 @@ async fn run_transaction_acceptor_without_timeout( if test_scenario == TestScenario::BalanceCheckForDeploySentByPeer { let (txn_sender, _) = oneshot::channel(); let txn_responder = Responder::without_shutdown(txn_sender); + let chainspec = chainspec.as_ref().clone(); runner .process_injected_effects(inject_balance_check_for_peer( &txn, source.clone(), rng, txn_responder, + &chainspec, )) .await; while runner.try_crank(rng).await == TryCrankOutcome::NoEventsToProcess { @@ -1197,7 +1249,8 @@ async fn run_transaction_acceptor_without_timeout( | TestScenario::InvalidPricingModeForTransactionV1 | TestScenario::FromClientExpired(_) | TestScenario::TooLowGasPriceToleranceForTransactionV1 - | TestScenario::TooLowGasPriceToleranceForDeploy => { + | TestScenario::TooLowGasPriceToleranceForDeploy + | TestScenario::InvalidFields => { matches!( event, Event::TransactionAcceptorAnnouncement( @@ -1259,7 +1312,8 @@ async fn run_transaction_acceptor_without_timeout( // Check that invalid transactions sent by a peer raise the `InvalidTransaction` // announcement with the appropriate source. TestScenario::FromPeerInvalidTransaction(_) - | TestScenario::BalanceCheckForDeploySentByPeer => { + | TestScenario::BalanceCheckForDeploySentByPeer + | TestScenario::InvalidFieldsFromPeer => { matches!( event, Event::TransactionAcceptorAnnouncement( @@ -2445,3 +2499,25 @@ async fn should_reject_deploy_with_too_low_gas_price_tolerance() { )) )) } + +#[tokio::test] +async fn should_reject_transaction_with_unexpected_fields() { + let result = run_transaction_acceptor(TestScenario::InvalidFields).await; + assert!(matches!( + result, + Err(super::Error::InvalidTransaction(InvalidTransaction::V1( + InvalidTransactionV1::UnexpectedTransactionFieldEntries + ))) + )) +} + +#[tokio::test] +async fn should_reject_transaction_from_peer_with_unexpected_fields() { + let result = run_transaction_acceptor(TestScenario::InvalidFieldsFromPeer).await; + assert!(matches!( + result, + Err(super::Error::InvalidTransaction(InvalidTransaction::V1( + InvalidTransactionV1::UnexpectedTransactionFieldEntries + ))) + )) +} diff --git a/node/src/components/transaction_buffer.rs b/node/src/components/transaction_buffer.rs index 8ac942ec50..8cf1c63a78 100644 --- a/node/src/components/transaction_buffer.rs +++ b/node/src/components/transaction_buffer.rs @@ -122,7 +122,7 @@ impl TransactionBuffer { } }; debug!( - blocks = ?blocks.iter().map(|b| b.height()).collect_vec(), + blocks = ?blocks.iter().map(Block::height).collect_vec(), "TransactionBuffer: initialization" ); info!("initialized {}", >::name(self)); @@ -162,7 +162,7 @@ impl TransactionBuffer { // clear expired transaction from all holds, then clear any entries that have no items // remaining self.hold.iter_mut().for_each(|(_, held_transactions)| { - held_transactions.retain(|transaction_hash| !freed.contains_key(transaction_hash)) + held_transactions.retain(|transaction_hash| !freed.contains_key(transaction_hash)); }); self.hold.retain(|_, remaining| !remaining.is_empty()); @@ -208,7 +208,6 @@ impl TransactionBuffer { } fn register_transaction_gossiped( - &mut self, transaction_id: TransactionId, effect_builder: EffectBuilder, ) -> Effects @@ -234,7 +233,7 @@ impl TransactionBuffer { where REv: From + Send, { - if self.prices.get(&era_id).is_none() { + if !self.prices.contains_key(&era_id) { info!("Empty prices field, requesting gas price from contract runtime"); return effect_builder .get_current_gas_price(era_id) @@ -261,6 +260,7 @@ impl TransactionBuffer { error!(%transaction_hash, "TransactionBuffer: invalid transaction must not be buffered"); return; } + if self .hold .values() @@ -269,6 +269,7 @@ impl TransactionBuffer { info!(%transaction_hash, "TransactionBuffer: attempt to register already held transaction"); return; } + let footprint = match TransactionFootprint::new(&self.chainspec, &transaction) { Ok(footprint) => footprint, Err(invalid_transaction_error) => { @@ -411,7 +412,7 @@ impl TransactionBuffer { let mut buckets: HashMap<_, Vec<_>> = HashMap::new(); for (transaction_hash, footprint) in proposable { buckets - .entry(&footprint.body_hash) + .entry(&footprint.payload_hash) .and_modify(|vec| vec.push((*transaction_hash, footprint))) .or_insert(vec![(*transaction_hash, footprint)]); } @@ -460,9 +461,9 @@ impl TransactionBuffer { let iter_limit = self.buffer.len() * 4; let mut buckets = self.buckets(current_era_gas_price); - let mut body_hashes_queue: VecDeque<_> = buckets.keys().cloned().collect(); + let mut payload_hashes_queue: VecDeque<_> = buckets.keys().cloned().collect(); - while let Some(body_hash) = body_hashes_queue.pop_front() { + while let Some(payload_hash) = payload_hashes_queue.pop_front() { if Timestamp::now() > request_expiry { break; } @@ -476,14 +477,14 @@ impl TransactionBuffer { } let Some((transaction_hash, footprint)) = - buckets.get_mut(body_hash).and_then(Vec::<_>::pop) + buckets.get_mut(payload_hash).and_then(Vec::<_>::pop) else { continue; }; // bucket wasn't empty - push the hash back into the queue to be processed again on the // next pass - body_hashes_queue.push_back(body_hash); + payload_hashes_queue.push_back(payload_hash); if footprint.is_mint() && have_hit_mint_limit { continue; @@ -523,15 +524,15 @@ impl TransactionBuffer { ); dead.insert(transaction_hash); } - AddError::Count(category) => { - match category { - category if category == MINT_LANE_ID => { + AddError::Count(lane_id) => { + match lane_id { + lane_id if lane_id == MINT_LANE_ID => { have_hit_mint_limit = true; } - category if category == AUCTION_LANE_ID => { + lane_id if lane_id == AUCTION_LANE_ID => { have_hit_auction_limit = true; } - category if category == INSTALL_UPGRADE_LANE_ID => { + lane_id if lane_id == INSTALL_UPGRADE_LANE_ID => { have_hit_install_upgrade_limit = true; } _ => { @@ -788,7 +789,7 @@ where Effects::new() } Event::ReceiveTransactionGossiped(transaction_id) => { - self.register_transaction_gossiped(transaction_id, effect_builder) + Self::register_transaction_gossiped(transaction_id, effect_builder) } Event::StoredTransaction(transaction_id, maybe_transaction) => { match maybe_transaction { diff --git a/node/src/components/transaction_buffer/tests.rs b/node/src/components/transaction_buffer/tests.rs index d373fb2fc4..44dfcca544 100644 --- a/node/src/components/transaction_buffer/tests.rs +++ b/node/src/components/transaction_buffer/tests.rs @@ -5,7 +5,8 @@ use rand::{seq::SliceRandom, Rng}; use casper_types::{ testing::TestRng, Deploy, EraId, SecretKey, TestBlockBuilder, TimeDiff, Transaction, - TransactionConfig, TransactionV1, TransactionV1Config, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, + TransactionConfig, TransactionLimitsDefinition, TransactionV1, TransactionV1Config, + DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, LARGE_WASM_LANE_ID, }; use super::*; @@ -19,7 +20,6 @@ use crate::{ const ERA_ONE: EraId = EraId::new(1u64); const GAS_PRICE_TOLERANCE: u8 = 1; const DEFAULT_MINIMUM_GAS_PRICE: u8 = 1; -const LARGE_LANE_ID: u8 = 3; fn get_appendable_block( rng: &mut TestRng, @@ -163,17 +163,17 @@ const fn all_categories() -> [u8; 4] { MINT_LANE_ID, INSTALL_UPGRADE_LANE_ID, AUCTION_LANE_ID, - LARGE_LANE_ID, + LARGE_WASM_LANE_ID, ] } #[test] fn register_transaction_and_check_size() { let mut rng = TestRng::new(); - + let chainspec = Chainspec::default(); for category in all_categories() { let mut transaction_buffer = TransactionBuffer::new( - Arc::new(Chainspec::default()), + Arc::new(chainspec.clone()), Config::default(), &Registry::new(), ) @@ -194,7 +194,7 @@ fn register_transaction_and_check_size() { .get(rng.gen_range(0..num_valid_transactions)) .unwrap() .clone(); - transaction_buffer.register_transaction(duplicate_transaction); + transaction_buffer.register_transaction(duplicate_transaction.clone()); assert_container_sizes(&transaction_buffer, valid_transactions.len(), 0, 0); // Insert transaction without footprint @@ -558,8 +558,9 @@ fn block_fully_saturated() { // Ensure that only 'total_allowed' transactions are proposed. let timestamp = Timestamp::now(); - let expiry = timestamp.saturating_add(TimeDiff::from_seconds(1)); + let expiry = timestamp.saturating_add(TimeDiff::from_seconds(60)); let appendable_block = transaction_buffer.appendable_block(Timestamp::now(), ERA_ONE, expiry); + assert_eq!( appendable_block.transaction_hashes().len(), total_allowed as usize @@ -584,7 +585,6 @@ fn block_fully_saturated() { proposed_standards += 1; } }); - let mut has_hit_any_limit = false; if proposed_transfers == max_transfers { has_hit_any_limit = true; @@ -592,7 +592,7 @@ fn block_fully_saturated() { if proposed_stakings == max_staking { has_hit_any_limit = true; } - if proposed_install_upgrades as u64 == max_install_upgrade { + if proposed_install_upgrades == max_install_upgrade { has_hit_any_limit = true; } if proposed_standards == max_standard { @@ -836,7 +836,8 @@ fn register_transactions_and_blocks() { .cloned() .peekable(); assert!(held_transactions.peek().is_some()); - held_transactions.for_each(|transaction| transaction_buffer.register_transaction(transaction)); + held_transactions + .for_each(|transaction| transaction_buffer.register_transaction(transaction.clone())); assert_container_sizes( &transaction_buffer, block_transaction.len() + valid_transactions.len(), @@ -1090,11 +1091,13 @@ fn make_test_chainspec(max_standard_count: u64, max_mint_count: u64) -> Arc AutoClosingResponder { impl AutoClosingResponder { /// Send `Some(data)` to the origin of the request. pub(crate) async fn respond(self, data: T) { - self.into_inner().respond(Some(data)).await + self.into_inner().respond(Some(data)).await; } /// Send `None` to the origin of the request. pub(crate) async fn respond_none(self) { - self.into_inner().respond(None).await + self.into_inner().respond(None).await; } } @@ -273,7 +273,7 @@ impl Drop for AutoClosingResponder { debug!( unsent_value = %self.0, "failed to auto-close responder, ignoring" - ) + ); } } } @@ -379,17 +379,6 @@ pub(crate) trait EffectExt: Future + Send { U: 'static, Self: Sized; - /// Finalizes a future into an effect that returns an iterator of events. - /// - /// The function `f` is used to translate the returned value from an effect into an iterator of - /// events. - fn events(self, f: F) -> Effects - where - F: FnOnce(Self::Output) -> I + 'static + Send, - U: 'static, - I: Iterator, - Self: Sized; - /// Finalizes a future into an effect that runs but drops the result. fn ignore(self) -> Effects; } @@ -413,32 +402,6 @@ pub(crate) trait EffectResultExt { U: 'static; } -/// Effect extension for futures, used to convert futures returning an `Option` into two different -/// effects. -pub(crate) trait EffectOptionExt { - /// The type the future will return if `Some`. - type Value; - - /// Finalizes a future returning an `Option` into two different effects. - /// - /// The function `f_some` is used to translate the returned value from an effect into an event, - /// while the function `f_none` does the same for a returned `None`. - fn map_or_else(self, f_some: F, f_none: G) -> Effects - where - F: FnOnce(Self::Value) -> U + 'static + Send, - G: FnOnce() -> U + 'static + Send, - U: 'static; - - /// Finalizes a future returning an `Option` into two different effects. - /// - /// The function `f` is used to translate the returned value from an effect into an event, - /// In the case of `None`, empty vector of effects is returned. - fn map_some(self, f: F) -> Effects - where - F: FnOnce(Self::Value) -> U + 'static + Send, - U: 'static; -} - impl EffectExt for T where T: Future + Send + 'static + Sized, @@ -451,15 +414,6 @@ where smallvec![self.map(f).map(|item| smallvec![item]).boxed()] } - fn events(self, f: F) -> Effects - where - F: FnOnce(Self::Output) -> I + 'static + Send, - U: 'static, - I: Iterator, - { - smallvec![self.map(f).map(|iter| iter.collect()).boxed()] - } - fn ignore(self) -> Effects { smallvec![self.map(|_| Multiple::new()).boxed()] } @@ -486,42 +440,6 @@ where } } -impl EffectOptionExt for T -where - T: Future> + Send + 'static + Sized, - T: ?Sized, -{ - type Value = V; - - fn map_or_else(self, f_some: F, f_none: G) -> Effects - where - F: FnOnce(V) -> U + 'static + Send, - G: FnOnce() -> U + 'static + Send, - U: 'static, - { - smallvec![self - .map(|option| option.map_or_else(f_none, f_some)) - .map(|item| smallvec![item]) - .boxed()] - } - - /// Finalizes a future returning an `Option`. - /// - /// The function `f` is used to translate the returned value from an effect into an event, - /// In the case of `None`, empty vector is returned. - fn map_some(self, f: F) -> Effects - where - F: FnOnce(Self::Value) -> U + 'static + Send, - U: 'static, - { - smallvec![self - .map(|option| option - .map(|el| smallvec![f(el)]) - .unwrap_or_else(|| smallvec![])) - .boxed()] - } -} - /// A builder for [`Effect`](type.Effect.html)s. /// /// Provides methods allowing the creation of effects which need to be scheduled on the reactor's @@ -615,7 +533,7 @@ impl EffectBuilder { // // If it does happen, we pretend nothing happened instead of crashing. if self.event_queue.shutdown_flag().is_set() { - debug!(%err, channel=?type_name::(), "ignoring closed channel due to shutdown") + debug!(%err, channel=?type_name::(), "ignoring closed channel due to shutdown"); } else { error!(%err, channel=?type_name::(), "request for channel closed, this may be a bug? \ check if a component is stuck from now on"); @@ -653,14 +571,14 @@ impl EffectBuilder { { self.event_queue .schedule(FatalAnnouncement { file, line, msg }, QueueKind::Control) - .await + .await; } /// Sets a timeout. pub(crate) async fn set_timeout(self, timeout: Duration) -> Duration { let then = Instant::now(); time::sleep(timeout).await; - Instant::now() - then + then.elapsed() } /// Retrieve a snapshot of the nodes current metrics formatted as string. @@ -826,7 +744,7 @@ impl EffectBuilder { >::from_incoming(sender, payload), QueueKind::NetworkIncoming, ) - .await + .await; } /// Announces that a gossiper has received a new item, where the item's ID is the complete item. @@ -1013,7 +931,7 @@ impl EffectBuilder { UpgradeWatcherAnnouncement(maybe_next_upgrade), QueueKind::Control, ) - .await + .await; } /// Announces a committed Step success. @@ -1026,7 +944,7 @@ impl EffectBuilder { ContractRuntimeAnnouncement::CommitStepSuccess { era_id, effects }, QueueKind::ContractRuntime, ) - .await + .await; } pub(crate) async fn update_contract_runtime_state(self, new_pre_state: ExecutionPreState) @@ -1038,7 +956,7 @@ impl EffectBuilder { ContractRuntimeRequest::UpdatePreState { new_pre_state }, QueueKind::ContractRuntime, ) - .await + .await; } /// Announces validators for upcoming era. @@ -1057,7 +975,7 @@ impl EffectBuilder { }, QueueKind::ContractRuntime, ) - .await + .await; } pub(crate) async fn announce_new_era_gas_price(self, era_id: EraId, next_era_gas_price: u8) @@ -1072,7 +990,7 @@ impl EffectBuilder { }, QueueKind::ContractRuntime, ) - .await + .await; } /// Begins gossiping an item. @@ -1090,7 +1008,7 @@ impl EffectBuilder { }, QueueKind::Gossip, ) - .await + .await; } /// Puts the given block into the linear block store. @@ -1532,17 +1450,6 @@ impl EffectBuilder { .await } - pub(crate) async fn get_protocol_version(self) -> ProtocolVersion - where - REv: From, - { - self.make_request( - |responder| ReactorInfoRequest::ProtocolVersion { responder }, - QueueKind::Regular, - ) - .await - } - #[allow(unused)] pub(crate) async fn get_balance_holds_interval(self) -> TimeDiff where @@ -1726,7 +1633,7 @@ impl EffectBuilder { }, QueueKind::ToStorage, ) - .await + .await; } /// Gets the requested block and its finality signatures. @@ -1888,7 +1795,7 @@ impl EffectBuilder { }, QueueKind::ContractRuntime, ) - .await + .await; } pub(crate) async fn enqueue_protocol_upgrade( @@ -1910,7 +1817,7 @@ impl EffectBuilder { }, QueueKind::Control, ) - .await + .await; } /// Checks whether the transactions included in the block exist on the network and the block is @@ -1946,7 +1853,7 @@ impl EffectBuilder { ConsensusAnnouncement::Proposed(Box::new(proposed_block)), QueueKind::Consensus, ) - .await + .await; } /// Announces that a block has been finalized. @@ -1959,7 +1866,7 @@ impl EffectBuilder { ConsensusAnnouncement::Finalized(Box::new(finalized_block)), QueueKind::Consensus, ) - .await + .await; } /// Announces that a meta block has been created or its state has changed. @@ -1969,7 +1876,7 @@ impl EffectBuilder { { self.event_queue .schedule(MetaBlockAnnouncement(meta_block), QueueKind::Regular) - .await + .await; } /// Announces that a finalized block has been created, but it was not @@ -1983,7 +1890,7 @@ impl EffectBuilder { UnexecutedBlockAnnouncement(block_height), QueueKind::Regular, ) - .await + .await; } /// An equivocation has been detected. @@ -2004,7 +1911,7 @@ impl EffectBuilder { }, QueueKind::Consensus, ) - .await + .await; } /// Blocks a specific peer due to a transgression. @@ -2026,7 +1933,7 @@ impl EffectBuilder { }, QueueKind::NetworkInfo, ) - .await + .await; } /// Gets the next scheduled upgrade, if any. @@ -2244,7 +2151,7 @@ impl EffectBuilder { }, QueueKind::Control, ) - .await + .await; } /// Activates/deactivates a failpoint from a given activation. diff --git a/node/src/effect/requests.rs b/node/src/effect/requests.rs index c5f89403b2..5b8dfadf2d 100644 --- a/node/src/effect/requests.rs +++ b/node/src/effect/requests.rs @@ -34,8 +34,8 @@ use casper_types::{ execution::ExecutionResult, Approval, AvailableBlockRange, Block, BlockHash, BlockHeader, BlockSignatures, BlockSynchronizerStatus, BlockV2, ChainspecRawBytes, DeployHash, Digest, DisplayIter, EntityAddr, EraId, ExecutionInfo, FinalitySignature, FinalitySignatureId, Key, - NextUpgrade, ProtocolUpgradeConfig, ProtocolVersion, PublicKey, TimeDiff, Timestamp, - Transaction, TransactionHash, TransactionHeader, TransactionId, Transfer, + NextUpgrade, ProtocolUpgradeConfig, PublicKey, TimeDiff, Timestamp, Transaction, + TransactionHash, TransactionId, Transfer, }; use super::{AutoClosingResponder, GossipTarget, Responder}; @@ -58,7 +58,7 @@ use crate::{ types::{ appendable_block::AppendableBlock, BlockExecutionResultsOrChunk, BlockExecutionResultsOrChunkId, BlockWithMetadata, ExecutableBlock, LegacyDeploy, - MetaBlockState, NodeId, StatusFeed, + MetaBlockState, NodeId, StatusFeed, TransactionHeader, }, utils::Source, }; @@ -1112,24 +1112,11 @@ impl Display for UpgradeWatcherRequest { #[derive(Debug, Serialize)] pub(crate) enum ReactorInfoRequest { - ReactorState { - responder: Responder, - }, - LastProgress { - responder: Responder, - }, - Uptime { - responder: Responder, - }, - NetworkName { - responder: Responder, - }, - ProtocolVersion { - responder: Responder, - }, - BalanceHoldsInterval { - responder: Responder, - }, + ReactorState { responder: Responder }, + LastProgress { responder: Responder }, + Uptime { responder: Responder }, + NetworkName { responder: Responder }, + BalanceHoldsInterval { responder: Responder }, } impl Display for ReactorInfoRequest { @@ -1142,7 +1129,6 @@ impl Display for ReactorInfoRequest { ReactorInfoRequest::LastProgress { .. } => "LastProgress", ReactorInfoRequest::Uptime { .. } => "Uptime", ReactorInfoRequest::NetworkName { .. } => "NetworkName", - ReactorInfoRequest::ProtocolVersion { .. } => "ProtocolVersion", ReactorInfoRequest::BalanceHoldsInterval { .. } => "BalanceHoldsInterval", } ) diff --git a/node/src/logging.rs b/node/src/logging.rs index 40162c5f4e..06e83611a8 100644 --- a/node/src/logging.rs +++ b/node/src/logging.rs @@ -1,6 +1,6 @@ //! Logging via the tracing crate. -use std::{env, fmt, io}; +use std::{env, fmt, io, string::ToString}; use ansi_term::{Color, Style}; use anyhow::anyhow; @@ -284,8 +284,8 @@ impl ReloadHandle { /// Returns a string representation of the current [`EnvFilter`], if set. fn display_log_filter(&self) -> Result { match self { - ReloadHandle::Text(handle) => handle.with_current(|env_filter| env_filter.to_string()), - ReloadHandle::Json(handle) => handle.with_current(|env_filter| env_filter.to_string()), + ReloadHandle::Text(handle) => handle.with_current(ToString::to_string), + ReloadHandle::Json(handle) => handle.with_current(ToString::to_string), } } } diff --git a/node/src/reactor.rs b/node/src/reactor.rs index a4a94cfa93..dbca754211 100644 --- a/node/src/reactor.rs +++ b/node/src/reactor.rs @@ -49,7 +49,9 @@ use datasize::DataSize; use erased_serde::Serialize as ErasedSerialize; #[cfg(test)] use fake_instant::FakeClock; -use futures::{future::BoxFuture, FutureExt}; +#[cfg(test)] +use futures::future::BoxFuture; +use futures::FutureExt; use once_cell::sync::Lazy; use prometheus::{self, Histogram, HistogramOpts, IntCounter, IntGauge, Registry}; use quanta::{Clock, IntoNanoseconds}; @@ -222,7 +224,7 @@ impl EventQueueHandle { where REv: From, { - self.schedule_with_ancestor(None, event, queue_kind).await + self.schedule_with_ancestor(None, event, queue_kind).await; } /// Schedule an event on a specific queue. @@ -236,7 +238,7 @@ impl EventQueueHandle { { self.scheduler .push((ancestor, event.into()), queue_kind) - .await + .await; } /// Returns number of events in each of the scheduler's queues. @@ -309,6 +311,7 @@ pub(crate) trait Reactor: Sized { /// /// May return `None` if the component cannot be found, or if the reactor does not support /// querying component states. + #[allow(dead_code)] #[cfg(test)] fn get_component_state(&self, _name: &str) -> Option<&ComponentState> { None @@ -334,6 +337,7 @@ pub(crate) trait ReactorEvent: Send + Debug + From + 'stati /// A drop-like trait for `async` compatible drop-and-wait. /// /// Shuts down a type by explicitly freeing resources, but allowing to wait on cleanup to complete. +#[cfg(test)] pub(crate) trait Finalize: Sized { /// Runs cleanup code and waits for a shutdown to complete. /// @@ -538,8 +542,7 @@ where ); // Run all effects from component instantiation. process_effects(None, scheduler, initial_effects, QueueKind::Regular) - .instrument(debug_span!("process initial effects")) - .await; + .instrument(debug_span!("process initial effects")); info!("reactor main loop is ready"); @@ -658,7 +661,7 @@ where warn!( ?err, "failed to write/flush queue dump using debug format" - ) + ); }) .ok(); }) @@ -710,9 +713,7 @@ where self.scheduler, effects, queue_kind, - ) - .in_current_span() - .await; + ); self.current_event_id += 1; @@ -836,12 +837,10 @@ where let effects = create_effects(effect_builder); - process_effects(None, self.scheduler, effects, QueueKind::Regular) - .instrument(debug_span!( - "process injected effects", - ev = self.current_event_id - )) - .await; + process_effects(None, self.scheduler, effects, QueueKind::Regular).instrument(debug_span!( + "process injected effects", + ev = self.current_event_id + )); } /// Processes a single event if there is one and we haven't previously handled an exit code. @@ -937,7 +936,7 @@ where /// Spawns tasks that will process the given effects. /// /// Result events from processing the events will be scheduled with the given ancestor. -async fn process_effects( +fn process_effects( ancestor: Option, scheduler: &'static Scheduler, effects: Effects, @@ -948,7 +947,7 @@ async fn process_effects( for effect in effects { tokio::spawn(async move { for event in effect.await { - scheduler.push((ancestor, event), queue_kind).await + scheduler.push((ancestor, event), queue_kind).await; } }); } diff --git a/node/src/reactor/event_queue_metrics.rs b/node/src/reactor/event_queue_metrics.rs index a9971bff59..1b19d46839 100644 --- a/node/src/reactor/event_queue_metrics.rs +++ b/node/src/reactor/event_queue_metrics.rs @@ -90,7 +90,7 @@ impl Drop for EventQueueMetrics { .for_each(|(key, queue_gauge)| { self.registry .unregister(Box::new(queue_gauge.clone())) - .unwrap_or_else(|_| error!("unregistering {} failed: was not registered", key)) + .unwrap_or_else(|_| error!("unregistering {} failed: was not registered", key)); }); } } diff --git a/node/src/reactor/main_reactor.rs b/node/src/reactor/main_reactor.rs index b1893acbf9..0cd799f49f 100644 --- a/node/src/reactor/main_reactor.rs +++ b/node/src/reactor/main_reactor.rs @@ -261,9 +261,6 @@ impl reactor::Reactor for MainReactor { ReactorInfoRequest::NetworkName { responder } => responder .respond(NetworkName::new(self.chainspec.network_config.name.clone())) .ignore(), - ReactorInfoRequest::ProtocolVersion { responder } => responder - .respond(self.chainspec.protocol_version()) - .ignore(), ReactorInfoRequest::BalanceHoldsInterval { responder } => responder .respond(self.chainspec.core_config.gas_hold_interval) .ignore(), @@ -1553,21 +1550,9 @@ impl MainReactor { ), )); } - MetaBlock::Historical(historical_meta_block) => { - // Header type is the same for now so we can use the same `BlockAdded` event; - // When the header will be versioned, a new event will be needed for the - // consensus component. - effects.extend(reactor::wrap_effects( - MainEvent::Consensus, - self.consensus.handle_event( - effect_builder, - rng, - consensus::Event::BlockAdded { - header: Box::new(historical_meta_block.block.clone_header()), - header_hash: *historical_meta_block.block.hash(), - }, - ), - )); + MetaBlock::Historical(_historical_meta_block) => { + // Historical meta blocks aren't of interest to consensus - consensus only + // cares about new blocks. Hence, we can just do nothing here. } } } diff --git a/node/src/reactor/main_reactor/control.rs b/node/src/reactor/main_reactor/control.rs index 7cb01e868d..d59ef1e2cc 100644 --- a/node/src/reactor/main_reactor/control.rs +++ b/node/src/reactor/main_reactor/control.rs @@ -36,7 +36,7 @@ impl MainReactor { effects.extend( async move { if !delay.is_zero() { - tokio::time::sleep(delay).await + tokio::time::sleep(delay).await; } } .event(|_| MainEvent::ReactorCrank), @@ -482,7 +482,7 @@ impl MainReactor { Ok(Some(block_header)) if block_header.is_switch_block() => { block_header.is_last_block_before_activation(&self.chainspec.protocol_config) } - Ok(Some(_)) | Ok(None) => false, + Ok(Some(_) | None) => false, Err(msg) => { error!("{:?}: {}", self.state, msg); false diff --git a/node/src/reactor/main_reactor/error.rs b/node/src/reactor/main_reactor/error.rs index 449cc7a65f..86a58a1a5b 100644 --- a/node/src/reactor/main_reactor/error.rs +++ b/node/src/reactor/main_reactor/error.rs @@ -5,8 +5,9 @@ use casper_types::{bytesrepr, crypto::ErrorExt as CryptoError}; use crate::{ components::{ - contract_runtime, contract_runtime::BlockExecutionError, diagnostics_port, network, - storage, upgrade_watcher, + binary_port::BinaryPortInitializationError, + contract_runtime::{self, BlockExecutionError}, + diagnostics_port, network, storage, upgrade_watcher, }, utils::{ListeningError, LoadError}, }; @@ -61,6 +62,10 @@ pub(crate) enum Error { /// Error while loading the signing key pair. #[error("signing key pair load error: {0}")] LoadSigningKeyPair(#[from] LoadError), + + /// `BinaryPort` component error. + #[error("binary port: {0}")] + BinaryPort(#[from] BinaryPortInitializationError), } impl From for Error { diff --git a/node/src/reactor/main_reactor/tests.rs b/node/src/reactor/main_reactor/tests.rs index b91066c7c3..c6731167da 100644 --- a/node/src/reactor/main_reactor/tests.rs +++ b/node/src/reactor/main_reactor/tests.rs @@ -2920,7 +2920,7 @@ async fn run_gas_price_scenario(gas_price_scenario: GasPriceScenario) { let max_gas_price: u8 = 3; let mut transaction_config = TransactionV1Config::default(); - transaction_config.native_mint_lane[4] = 1; + transaction_config.native_mint_lane.max_transaction_count = 1; let spec_override = match gas_price_scenario { GasPriceScenario::SlotUtilization => { @@ -2964,6 +2964,7 @@ async fn run_gas_price_scenario(gas_price_scenario: GasPriceScenario) { .with_ttl(TimeDiff::from_seconds(120 * 10)) .with_pricing_mode(PricingMode::Fixed { gas_price_tolerance: max_gas_price, + additional_computation_factor: 0, }) .build() .expect("must get transaction"); @@ -2996,6 +2997,7 @@ async fn run_gas_price_scenario(gas_price_scenario: GasPriceScenario) { .with_secret_key(&alice_secret_key) .with_pricing_mode(PricingMode::Fixed { gas_price_tolerance: max_gas_price, + additional_computation_factor: 0, }) .build() .expect("must get transaction"); diff --git a/node/src/reactor/main_reactor/tests/binary_port.rs b/node/src/reactor/main_reactor/tests/binary_port.rs index 5826ec738e..07f37d63c2 100644 --- a/node/src/reactor/main_reactor/tests/binary_port.rs +++ b/node/src/reactor/main_reactor/tests/binary_port.rs @@ -11,10 +11,10 @@ use casper_binary_port::{ BinaryMessageCodec, BinaryRequest, BinaryRequestHeader, BinaryResponse, BinaryResponseAndRequest, ConsensusStatus, ConsensusValidatorChanges, ContractInformation, DictionaryItemIdentifier, DictionaryQueryResult, EntityIdentifier, EraIdentifier, ErrorCode, - GetRequest, GetTrieFullResult, GlobalStateQueryResult, GlobalStateRequest, InformationRequest, - InformationRequestTag, KeyPrefix, LastProgress, NetworkName, NodeStatus, PackageIdentifier, - PurseIdentifier, ReactorStateName, RecordId, ResponseType, RewardResponse, Uptime, - ValueWithProof, + GetRequest, GetTrieFullResult, GlobalStateEntityQualifier, GlobalStateQueryResult, + GlobalStateRequest, InformationRequest, InformationRequestTag, KeyPrefix, LastProgress, + NetworkName, NodeStatus, PackageIdentifier, PurseIdentifier, ReactorStateName, RecordId, + ResponseType, RewardResponse, Uptime, ValueWithProof, }; use casper_storage::global_state::state::CommitProvider; use casper_types::{ @@ -837,11 +837,13 @@ fn get_block_transfers(expected: BlockHeader) -> TestCase { fn get_era_summary(state_root_hash: Digest) -> TestCase { TestCase { name: "get_era_summary", - request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::Item { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), - base_key: Key::EraSummary, - path: vec![], - }))), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::Item { + base_key: Key::EraSummary, + path: vec![], + }, + )))), asserter: Box::new(|response| { assert_response::( response, @@ -858,10 +860,12 @@ fn get_era_summary(state_root_hash: Digest) -> TestCase { fn get_all_bids(state_root_hash: Digest) -> TestCase { TestCase { name: "get_all_bids", - request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::AllItems { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), - key_tag: KeyTag::Bid, - }))), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::AllItems { + key_tag: KeyTag::Bid, + }, + )))), asserter: Box::new(|response| { assert_response::, _>( response, @@ -875,9 +879,7 @@ fn get_all_bids(state_root_hash: Digest) -> TestCase { fn get_trie(digest: Digest) -> TestCase { TestCase { name: "get_trie", - request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::Trie { - trie_key: digest, - }))), + request: BinaryRequest::Get(GetRequest::Trie { trie_key: digest }), asserter: Box::new(|response| { assert_response::( response, @@ -891,12 +893,12 @@ fn get_trie(digest: Digest) -> TestCase { fn get_dictionary_item_by_addr(state_root_hash: Digest, addr: DictionaryAddr) -> TestCase { TestCase { name: "get_dictionary_item_by_addr", - request: BinaryRequest::Get(GetRequest::State(Box::new( - GlobalStateRequest::DictionaryItem { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::DictionaryItem { identifier: DictionaryItemIdentifier::DictionaryItem(addr), }, - ))), + )))), asserter: Box::new(move |response| { assert_response::( response, @@ -919,15 +921,15 @@ fn get_dictionary_item_by_seed_uref( ) -> TestCase { TestCase { name: "get_dictionary_item_by_seed_uref", - request: BinaryRequest::Get(GetRequest::State(Box::new( - GlobalStateRequest::DictionaryItem { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::DictionaryItem { identifier: DictionaryItemIdentifier::URef { seed_uref, dictionary_item_key: dictionary_item_key.clone(), }, }, - ))), + )))), asserter: Box::new(move |response| { assert_response::( response, @@ -952,16 +954,16 @@ fn get_dictionary_item_by_legacy_named_key( ) -> TestCase { TestCase { name: "get_dictionary_item_by_legacy_named_key", - request: BinaryRequest::Get(GetRequest::State(Box::new( - GlobalStateRequest::DictionaryItem { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::DictionaryItem { identifier: DictionaryItemIdentifier::AccountNamedKey { hash, dictionary_name, dictionary_item_key, }, }, - ))), + )))), asserter: Box::new(|response| { assert_response::( response, @@ -980,16 +982,16 @@ fn get_dictionary_item_by_named_key( ) -> TestCase { TestCase { name: "get_dictionary_item_by_named_key", - request: BinaryRequest::Get(GetRequest::State(Box::new( - GlobalStateRequest::DictionaryItem { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::DictionaryItem { identifier: DictionaryItemIdentifier::EntityNamedKey { addr, dictionary_name, dictionary_item_key, }, }, - ))), + )))), asserter: Box::new(|response| { assert_response::( response, @@ -1003,10 +1005,12 @@ fn get_dictionary_item_by_named_key( fn get_balance(state_root_hash: Digest, account_hash: AccountHash) -> TestCase { TestCase { name: "get_balance", - request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::Balance { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), - purse_identifier: PurseIdentifier::Account(account_hash), - }))), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::Balance { + purse_identifier: PurseIdentifier::Account(account_hash), + }, + )))), asserter: Box::new(|response| { assert_response::( response, @@ -1020,10 +1024,12 @@ fn get_balance(state_root_hash: Digest, account_hash: AccountHash) -> TestCase { fn get_balance_account_not_found(state_root_hash: Digest) -> TestCase { TestCase { name: "get_balance_account_not_found", - request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::Balance { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), - purse_identifier: PurseIdentifier::Account(AccountHash([9; 32])), - }))), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::Balance { + purse_identifier: PurseIdentifier::Account(AccountHash([9; 32])), + }, + )))), asserter: Box::new(|response| response.error_code() == ErrorCode::PurseNotFound as u16), } } @@ -1031,10 +1037,12 @@ fn get_balance_account_not_found(state_root_hash: Digest) -> TestCase { fn get_balance_purse_uref_not_found(state_root_hash: Digest) -> TestCase { TestCase { name: "get_balance_purse_uref_not_found", - request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::Balance { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), - purse_identifier: PurseIdentifier::Purse(URef::new([9; 32], Default::default())), - }))), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::Balance { + purse_identifier: PurseIdentifier::Purse(URef::new([9; 32], Default::default())), + }, + )))), asserter: Box::new(|response| response.error_code() == ErrorCode::PurseNotFound as u16), } } @@ -1042,12 +1050,12 @@ fn get_balance_purse_uref_not_found(state_root_hash: Digest) -> TestCase { fn get_named_keys_by_prefix(state_root_hash: Digest, entity_addr: EntityAddr) -> TestCase { TestCase { name: "get_named_keys_by_prefix", - request: BinaryRequest::Get(GetRequest::State(Box::new( - GlobalStateRequest::ItemsByPrefix { - state_identifier: Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + request: BinaryRequest::Get(GetRequest::State(Box::new(GlobalStateRequest::new( + Some(GlobalStateIdentifier::StateRootHash(state_root_hash)), + GlobalStateEntityQualifier::ItemsByPrefix { key_prefix: KeyPrefix::NamedKeysByEntity(entity_addr), }, - ))), + )))), asserter: Box::new(|response| { assert_response::, _>( response, @@ -1331,6 +1339,7 @@ fn try_accept_transaction(key: &SecretKey) -> TestCase { "Test", "call", TransactionRuntime::VmCasperV1, + 0, ) .with_secret_key(key) .with_chain_name("casper-example") diff --git a/node/src/reactor/main_reactor/tests/transactions.rs b/node/src/reactor/main_reactor/tests/transactions.rs index 4378a6f22d..bf6069539a 100644 --- a/node/src/reactor/main_reactor/tests/transactions.rs +++ b/node/src/reactor/main_reactor/tests/transactions.rs @@ -1,4 +1,5 @@ use super::*; +use crate::types::MetaTransaction; use casper_execution_engine::engine_state::MAX_PAYMENT_AMOUNT; use casper_storage::data_access_layer::{ AddressableEntityRequest, BalanceIdentifier, ProofHandling, QueryRequest, QueryResult, @@ -8,8 +9,7 @@ use casper_types::{ addressable_entity::NamedKeyAddr, runtime_args, system::mint::{ARG_AMOUNT, ARG_TARGET}, - AddressableEntity, Digest, EntityAddr, ExecutionInfo, GasLimited, TransactionLane, - TransactionRuntime, + AddressableEntity, Digest, EntityAddr, ExecutionInfo, TransactionRuntime, LARGE_WASM_LANE_ID, }; use once_cell::sync::Lazy; @@ -34,7 +34,6 @@ static CHARLIE_PUBLIC_KEY: Lazy = const MIN_GAS_PRICE: u8 = 5; const CHAIN_NAME: &str = "single-transaction-test-net"; -const LARGE_LANE_ID: u8 = 3; async fn transfer_to_account>( fixture: &mut TestFixture, @@ -90,11 +89,18 @@ async fn send_wasm_transaction( ) -> (TransactionHash, u64, ExecutionResult) { let chain_name = fixture.chainspec.network_config.name.clone(); + //These bytes are intentionally so large - this way they fall into "WASM_LARGE" category in the + // local chainspec Alternatively we could change the chainspec to have a different limits + // for the wasm categories, but that would require aligning all tests that use local + // chainspec + let module_bytes = Bytes::from(vec![1; 172_033]); let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, - Bytes::from(vec![1]), + false, + module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name(chain_name) .with_pricing_mode(pricing) @@ -396,6 +402,7 @@ async fn transfer_cost_fixed_price_no_fee_no_refund() { PublicKey::from(&*charlie_secret_key), PricingMode::Fixed { gas_price_tolerance: 1, + additional_computation_factor: 0, }, Some(0xDEADBEEF), ) @@ -501,6 +508,7 @@ async fn should_accept_transfer_without_id() { PublicKey::from(&*charlie_secret_key), PricingMode::Fixed { gas_price_tolerance: 1, + additional_computation_factor: 0, }, None, ) @@ -541,6 +549,7 @@ async fn failed_transfer_cost_fixed_price_no_fee_no_refund() { PublicKey::from(&*charlie_secret_key), PricingMode::Fixed { gas_price_tolerance: 1, + additional_computation_factor: 0, }, None, ) @@ -556,6 +565,7 @@ async fn failed_transfer_cost_fixed_price_no_fee_no_refund() { PublicKey::from(&*bob_secret_key), PricingMode::Fixed { gas_price_tolerance: 1, + additional_computation_factor: 0, }, None, ) @@ -777,6 +787,7 @@ async fn native_operations_fees_are_not_refunded() { PublicKey::from(&*charlie_secret_key), PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, None, ) @@ -874,13 +885,16 @@ async fn wasm_transaction_fees_are_refunded() { &bob_secret_key, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, ) .await; assert!(!exec_result_is_success(&exec_result)); // transaction should not succeed because the wasm bytes are invalid. - let expected_transaction_gas: u64 = fixture.chainspec.get_max_gas_limit_by_lane(LARGE_LANE_ID); + let expected_transaction_gas: u64 = fixture + .chainspec + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID); let expected_transaction_cost = expected_transaction_gas * MIN_GAS_PRICE as u64; assert_exec_result_cost( exec_result, @@ -1193,8 +1207,10 @@ async fn wasm_transaction_refunds_are_burnt(txn_pricing_mode: PricingMode) { let (_txn_hash, block_height, exec_result) = test.send_transaction(txn).await; - let expected_transaction_gas: u64 = - gas_limit.unwrap_or(test.chainspec().get_max_gas_limit_by_lane(LARGE_LANE_ID)); + let expected_transaction_gas: u64 = gas_limit.unwrap_or( + test.chainspec() + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), + ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; assert!(!exec_result_is_success(&exec_result)); // transaction should not succeed because the wasm bytes are invalid. @@ -1250,6 +1266,7 @@ async fn wasm_transaction_refunds_are_burnt(txn_pricing_mode: PricingMode) { async fn wasm_transaction_refunds_are_burnt_fixed_pricing() { wasm_transaction_refunds_are_burnt(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1292,8 +1309,10 @@ async fn only_refunds_are_burnt_no_fee(txn_pricing_mode: PricingMode) { let (_txn_hash, block_height, exec_result) = test.send_transaction(txn).await; // Fixed transaction pricing. - let expected_transaction_gas: u64 = - gas_limit.unwrap_or(test.chainspec().get_max_gas_limit_by_lane(LARGE_LANE_ID)); + let expected_transaction_gas: u64 = gas_limit.unwrap_or( + test.chainspec() + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), + ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; assert!(!exec_result_is_success(&exec_result)); // transaction should not succeed because the wasm bytes are invalid. @@ -1349,6 +1368,7 @@ async fn only_refunds_are_burnt_no_fee(txn_pricing_mode: PricingMode) { async fn only_refunds_are_burnt_no_fee_fixed_pricing() { only_refunds_are_burnt_no_fee(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1382,8 +1402,10 @@ async fn fees_and_refunds_are_burnt_separately(txn_pricing_mode: PricingMode) { let txn = invalid_wasm_txn(BOB_SECRET_KEY.clone(), txn_pricing_mode); // Fixed transaction pricing. - let expected_transaction_gas: u64 = - gas_limit.unwrap_or(test.chainspec().get_max_gas_limit_by_lane(LARGE_LANE_ID)); + let expected_transaction_gas: u64 = gas_limit.unwrap_or( + test.chainspec() + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), + ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; test.fixture @@ -1441,6 +1463,7 @@ async fn fees_and_refunds_are_burnt_separately(txn_pricing_mode: PricingMode) { async fn fees_and_refunds_are_burnt_separately_fixed_pricing() { fees_and_refunds_are_burnt_separately(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1475,8 +1498,10 @@ async fn refunds_are_payed_and_fees_are_burnt(txn_pricing_mode: PricingMode) { let txn = invalid_wasm_txn(BOB_SECRET_KEY.clone(), txn_pricing_mode); // Fixed transaction pricing. - let expected_transaction_gas: u64 = - gas_limit.unwrap_or(test.chainspec().get_max_gas_limit_by_lane(LARGE_LANE_ID)); + let expected_transaction_gas: u64 = gas_limit.unwrap_or( + test.chainspec() + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), + ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; test.fixture @@ -1540,6 +1565,7 @@ async fn refunds_are_payed_and_fees_are_burnt(txn_pricing_mode: PricingMode) { async fn refunds_are_payed_and_fees_are_burnt_fixed_pricing() { refunds_are_payed_and_fees_are_burnt(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1572,10 +1598,15 @@ async fn refunds_are_payed_and_fees_are_on_hold(txn_pricing_mode: PricingMode) { .await; let txn = invalid_wasm_txn(BOB_SECRET_KEY.clone(), txn_pricing_mode); + let meta_transaction = + MetaTransaction::from(&txn, &test.chainspec().transaction_config).unwrap(); // Fixed transaction pricing. let expected_consumed_gas = Gas::new(0); // expect that this transaction doesn't consume any gas since it has invalid wasm. - let expected_transaction_cost = - txn.gas_limit(test.chainspec()).unwrap().value() * min_gas_price; + let expected_transaction_cost = meta_transaction + .gas_limit(test.chainspec()) + .unwrap() + .value() + * min_gas_price; test.fixture .run_until_consensus_in_era(ERA_ONE, ONE_MIN) @@ -1640,6 +1671,7 @@ async fn refunds_are_payed_and_fees_are_on_hold(txn_pricing_mode: PricingMode) { async fn refunds_are_payed_and_fees_are_on_hold_fixed_pricing() { refunds_are_payed_and_fees_are_on_hold(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1684,9 +1716,11 @@ async fn only_refunds_are_burnt_no_fee_custom_payment() { let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, + false, module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name(CHAIN_NAME) .with_pricing_mode(PricingMode::PaymentLimited { @@ -1708,8 +1742,7 @@ async fn only_refunds_are_burnt_no_fee_custom_payment() { test.get_balances(None); let initial_total_supply = test.get_total_supply(None); let (_txn_hash, block_height, exec_result) = test.send_transaction(txn).await; - assert!(!exec_result_is_success(&exec_result)); // transaction should not succeed because we didn't request enough gas for this transaction - // to succeed. + match exec_result { ExecutionResult::V2(exec_result_v2) => { assert_eq!(exec_result_v2.cost, expected_transaction_cost.into()); @@ -1786,9 +1819,11 @@ async fn no_refund_no_fee_custom_payment() { let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, + false, module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name(CHAIN_NAME) .with_pricing_mode(PricingMode::PaymentLimited { @@ -1810,8 +1845,7 @@ async fn no_refund_no_fee_custom_payment() { test.get_balances(None); let initial_total_supply = test.get_total_supply(None); let (_txn_hash, block_height, exec_result) = test.send_transaction(txn).await; - // expected to fail due to insufficient funding - assert!(!exec_result_is_success(&exec_result), "should have failed"); + match exec_result { ExecutionResult::V2(exec_result_v2) => { assert_eq!(exec_result_v2.cost, expected_transaction_cost.into()); @@ -1957,6 +1991,7 @@ async fn transfer_fee_is_burnt_no_refund(txn_pricing_mode: PricingMode) { async fn transfer_fee_is_burnt_no_refund_fixed_pricing() { transfer_fee_is_burnt_no_refund(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2067,6 +2102,7 @@ async fn fee_is_payed_to_proposer_no_refund(txn_pricing_mode: PricingMode) { async fn fee_is_payed_to_proposer_no_refund_fixed_pricing() { fee_is_payed_to_proposer_no_refund(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2110,8 +2146,10 @@ async fn wasm_transaction_fees_are_refunded_to_proposer(txn_pricing_mode: Pricin let (_txn_hash, block_height, exec_result) = test.send_transaction(txn).await; - let expected_transaction_gas: u64 = - gas_limit.unwrap_or(test.chainspec().get_max_gas_limit_by_lane(LARGE_LANE_ID)); + let expected_transaction_gas: u64 = gas_limit.unwrap_or( + test.chainspec() + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), + ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; assert!(!exec_result_is_success(&exec_result)); // transaction should not succeed because the wasm bytes are invalid. @@ -2166,6 +2204,7 @@ async fn wasm_transaction_fees_are_refunded_to_proposer(txn_pricing_mode: Pricin async fn wasm_transaction_fees_are_refunded_to_proposer_fixed_pricing() { wasm_transaction_fees_are_refunded_to_proposer(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2304,6 +2343,7 @@ async fn fee_is_accumulated_and_distributed_no_refund(txn_pricing_mode: PricingM async fn fee_is_accumulated_and_distributed_no_refund_fixed_pricing() { fee_is_accumulated_and_distributed_no_refund(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2338,11 +2378,18 @@ fn transfer_txn>( } fn invalid_wasm_txn(initiator: Arc, pricing_mode: PricingMode) -> Transaction { + //These bytes are intentionally so large - this way they fall into "WASM_LARGE" category in the + // local chainspec Alternatively we could change the chainspec to have a different limits + // for the wasm categories, but that would require aligning all tests that use local + // chainspec + let module_bytes = Bytes::from(vec![1; 172_033]); let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, - Bytes::from(vec![1]), + false, + module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name(CHAIN_NAME) .with_pricing_mode(pricing_mode) @@ -2367,16 +2414,9 @@ fn match_pricing_mode(txn_pricing_mode: &PricingMode) -> (PricingHandling, u8, O ), PricingMode::Fixed { gas_price_tolerance, + .. } => (PricingHandling::Fixed, *gas_price_tolerance, None), PricingMode::Reserved { .. } => unimplemented!(), - PricingMode::GasLimited { - gas_limit, - gas_price_tolerance, - } => ( - PricingHandling::GasLimited, - *gas_price_tolerance, - Some(*gas_limit), - ), } } @@ -2384,6 +2424,7 @@ fn match_pricing_mode(txn_pricing_mode: &PricingMode) -> (PricingHandling, u8, O async fn holds_should_be_added_and_cleared_fixed_pricing() { holds_should_be_added_and_cleared(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2513,6 +2554,7 @@ async fn fee_holds_are_amortized() { BOB_SECRET_KEY.clone(), PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, ); @@ -2526,7 +2568,9 @@ async fn fee_holds_are_amortized() { let (_txn_hash, block_height, exec_result) = test.send_transaction(txn).await; // Fixed transaction pricing. - let expected_transaction_gas: u64 = test.chainspec().get_max_gas_limit_by_lane(LARGE_LANE_ID); + let expected_transaction_gas: u64 = test + .chainspec() + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID); let expected_transaction_cost = expected_transaction_gas * MIN_GAS_PRICE as u64; @@ -2644,6 +2688,7 @@ async fn sufficient_balance_is_available_after_amortization() { &CHARLIE_PUBLIC_KEY, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, transfer_amount, ); @@ -2675,6 +2720,7 @@ async fn sufficient_balance_is_available_after_amortization() { &BOB_PUBLIC_KEY, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, min_transfer_amount, ); @@ -2715,6 +2761,7 @@ async fn sufficient_balance_is_available_after_amortization() { &BOB_PUBLIC_KEY, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, min_transfer_amount, ); @@ -2760,6 +2807,7 @@ async fn validator_credit_is_written_and_cleared_after_auction() { &CHARLIE_PUBLIC_KEY, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, transfer_amount, ); @@ -2821,22 +2869,13 @@ async fn add_and_withdraw_bid_transaction() { ) .await; - let transfer_cost: U512 = - U512::from(test.chainspec().system_costs_config.mint_costs().transfer) * MIN_GAS_PRICE; - let min_transfer_amount = U512::from( - test.chainspec() - .transaction_config - .native_transfer_minimum_motes, - ); - let half_transfer_cost = - (Ratio::new(U512::from(1), U512::from(2)) * transfer_cost).to_integer(); - let transfer_amount = min_transfer_amount * 2 + transfer_cost + half_transfer_cost; + let bid_amount = test.chainspec().core_config.minimum_bid_amount + 10; let mut txn = Transaction::from( TransactionV1Builder::new_add_bid( PublicKey::from(&**BOB_SECRET_KEY), 0, - transfer_amount, + bid_amount, test.chainspec().core_config.minimum_delegation_amount, test.chainspec().core_config.maximum_delegation_amount, ) @@ -2854,6 +2893,7 @@ async fn add_and_withdraw_bid_transaction() { let (_, _bob_initial_balance, _) = test.get_balances(None); let (_txn_hash, _block_height, exec_result) = test.send_transaction(txn).await; + println!("{:?}", exec_result); assert!(exec_result_is_success(&exec_result)); test.fixture @@ -2861,7 +2901,7 @@ async fn add_and_withdraw_bid_transaction() { .await; let mut txn = Transaction::from( - TransactionV1Builder::new_withdraw_bid(PublicKey::from(&**BOB_SECRET_KEY), transfer_amount) + TransactionV1Builder::new_withdraw_bid(PublicKey::from(&**BOB_SECRET_KEY), bid_amount) .unwrap() .with_chain_name(CHAIN_NAME) .with_initiator_addr(PublicKey::from(&**BOB_SECRET_KEY)) @@ -3058,9 +3098,11 @@ async fn insufficient_funds_transfer_from_purse() { let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, + false, module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_runtime_args(runtime_args! { "destination" => purse_name, "amount" => U512::zero() }) .with_chain_name(CHAIN_NAME) @@ -3185,9 +3227,11 @@ async fn charge_when_session_code_succeeds() { let transferred_amount = 1; let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, + false, module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_runtime_args(runtime_args! { ARG_TARGET => CHARLIE_PUBLIC_KEY.to_account_hash(), @@ -3195,6 +3239,12 @@ async fn charge_when_session_code_succeeds() { }) .with_chain_name(CHAIN_NAME) .with_initiator_addr(BOB_PUBLIC_KEY.clone()) + .with_pricing_mode(PricingMode::Fixed { + gas_price_tolerance: 5, + additional_computation_factor: 2, /*Makes the transaction + * "Large" despite the fact that the actual + * WASM bytes categorize it as "Small" */ + }) .build() .unwrap(), ); @@ -3250,9 +3300,11 @@ async fn charge_when_session_code_fails_with_user_error() { let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, + false, module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name(CHAIN_NAME) .with_initiator_addr(BOB_PUBLIC_KEY.clone()) @@ -3267,7 +3319,7 @@ async fn charge_when_session_code_fails_with_user_error() { ExecutionResult::V2(res) if res.error_message.as_deref() == Some("User error: 100") ), "{:?}", - exec_result + exec_result.error_message() ); let (alice_current_balance, bob_current_balance, _) = test.get_balances(Some(block_height)); @@ -3279,11 +3331,11 @@ async fn charge_when_session_code_fails_with_user_error() { "fee is {}, expected to be greater than 0", fee ); - assert_eq!( - bob_current_balance.total, - bob_initial_balance.total - fee, - "bob should pay the fee" - ); + let init = bob_initial_balance.total; + let curr = bob_current_balance.total; + let actual = curr; + let expected = init - fee; + assert_eq!(actual, expected, "init {} curr {} fee {}", init, curr, fee,); } #[tokio::test] @@ -3318,9 +3370,11 @@ async fn charge_when_session_code_runs_out_of_gas() { let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, + false, module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name(CHAIN_NAME) .with_initiator_addr(BOB_PUBLIC_KEY.clone()) @@ -3389,7 +3443,7 @@ async fn successful_purse_to_purse_transfer() { Bytes::from(std::fs::read(purse_create_contract).expect("cannot read module bytes")); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionLane::Large, module_bytes, TransactionRuntime::VmCasperV1) + TransactionV1Builder::new_session(false, module_bytes,TransactionRuntime::VmCasperV1, 0, None) .with_runtime_args( runtime_args! { "destination" => purse_name, "amount" => U512::from(MAX_PAYMENT_AMOUNT) + U512::one() }, ) @@ -3482,7 +3536,7 @@ async fn successful_purse_to_account_transfer() { Bytes::from(std::fs::read(purse_create_contract).expect("cannot read module bytes")); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionLane::Large, module_bytes, TransactionRuntime::VmCasperV1) + TransactionV1Builder::new_session(false, module_bytes, TransactionRuntime::VmCasperV1, 0, None) .with_runtime_args( runtime_args! { "destination" => purse_name, "amount" => U512::from(MAX_PAYMENT_AMOUNT) + U512::one() }, ) @@ -3644,9 +3698,11 @@ async fn out_of_gas_txn_does_not_produce_effects() { let mut txn = Transaction::from( TransactionV1Builder::new_session( - TransactionLane::Large, + false, module_bytes, TransactionRuntime::VmCasperV1, + 0, + None, ) .with_chain_name(CHAIN_NAME) .with_initiator_addr(BOB_PUBLIC_KEY.clone()) @@ -3707,6 +3763,7 @@ async fn gas_holds_accumulate_for_multiple_transactions_in_the_same_block() { let chain_name = test.fixture.chainspec.network_config.name.clone(); let txn_pricing_mode = PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }; let expected_transfer_gas = test.chainspec().system_costs_config.mint_costs().transfer; let expected_transfer_cost: U512 = U512::from(expected_transfer_gas) * MIN_GAS_PRICE; diff --git a/node/src/testing/fake_transaction_acceptor.rs b/node/src/testing/fake_transaction_acceptor.rs index 3e8e21eee7..e275477984 100644 --- a/node/src/testing/fake_transaction_acceptor.rs +++ b/node/src/testing/fake_transaction_acceptor.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use tracing::debug; -use casper_types::Transaction; +use casper_types::{Chainspec, Timestamp, Transaction}; pub(crate) use crate::components::transaction_acceptor::{Error, Event}; use crate::{ @@ -20,6 +20,7 @@ use crate::{ announcements::TransactionAcceptorAnnouncement, requests::StorageRequest, EffectBuilder, EffectExt, Effects, Responder, }, + types::MetaTransaction, utils::Source, NodeRng, }; @@ -39,11 +40,15 @@ impl ReactorEventT for REv where #[derive(Debug)] pub struct FakeTransactionAcceptor { is_active: bool, + chainspec: Chainspec, } impl FakeTransactionAcceptor { pub(crate) fn new() -> Self { - FakeTransactionAcceptor { is_active: true } + FakeTransactionAcceptor { + is_active: true, + chainspec: Chainspec::default(), + } } pub(crate) fn set_active(&mut self, new_setting: bool) { @@ -57,10 +62,14 @@ impl FakeTransactionAcceptor { source: Source, maybe_responder: Option>>, ) -> Effects { + let meta_transaction = + MetaTransaction::from(&transaction, &self.chainspec.transaction_config).unwrap(); let event_metadata = Box::new(EventMetadata::new( transaction.clone(), + meta_transaction, source, maybe_responder, + Timestamp::now(), )); effect_builder .put_transaction_to_storage(transaction) @@ -77,6 +86,7 @@ impl FakeTransactionAcceptor { is_new: bool, ) -> Effects { let EventMetadata { + meta_transaction: _, transaction, source, maybe_responder, diff --git a/node/src/tls.rs b/node/src/tls.rs index 195dd1befd..b5bcaf0003 100644 --- a/node/src/tls.rs +++ b/node/src/tls.rs @@ -63,7 +63,7 @@ mod big_array { big_array! { BigArray; } } -/// The chosen signature algorithm (**ECDSA with SHA512**). +/// The chosen signature algorithm (**ECDSA with SHA512**). const SIGNATURE_ALGORITHM: Nid = Nid::ECDSA_WITH_SHA512; /// The underlying elliptic curve (**P-521**). @@ -556,7 +556,7 @@ fn tls_cert_from_x509_and_key( ec_key: EcKey, ) -> Result { let cert_fingerprint = cert_fingerprint(&cert)?; - let key_fingerprint = key_fingerprint(ec_key)?; + let key_fingerprint = key_fingerprint(&ec_key)?; Ok(TlsCert { x509: cert, cert_fingerprint, @@ -575,7 +575,7 @@ pub(crate) fn cert_fingerprint(cert: &X509) -> Result) -> Result { +pub(crate) fn key_fingerprint(ec_key: &EcKey) -> Result { let mut big_num_context = BigNumContext::new().map_err(ValidationError::BigNumContextNotAvailable)?; let buf = ec_key @@ -836,7 +836,7 @@ impl Hash for Sha512 { // TODO: Benchmark if this is really worthwhile over the automatic derivation. chunk.copy_from_slice(&self.bytes()[0..8]); - state.write_u64(u64::from_le_bytes(chunk)) + state.write_u64(u64::from_le_bytes(chunk)); } } diff --git a/node/src/types.rs b/node/src/types.rs index 95a836b2c0..af88a7076a 100644 --- a/node/src/types.rs +++ b/node/src/types.rs @@ -36,7 +36,9 @@ pub use node_config::{NodeConfig, SyncHandling}; pub(crate) use node_id::NodeId; pub use status_feed::{ChainspecInfo, GetStatusResult, StatusFeed}; pub(crate) use sync_leap::{GlobalStatesMetadata, SyncLeap, SyncLeapIdentifier}; -pub(crate) use transaction::{LegacyDeploy, TransactionFootprint}; +pub(crate) use transaction::{ + LegacyDeploy, MetaTransaction, TransactionFootprint, TransactionHeader, +}; pub(crate) use validator_matrix::{EraValidatorWeights, SignatureWeight, ValidatorMatrix}; pub use value_or_chunk::{ ChunkingError, TrieOrChunk, TrieOrChunkId, TrieOrChunkIdDisplay, ValueOrChunk, diff --git a/node/src/types/appendable_block.rs b/node/src/types/appendable_block.rs index d90bce01bd..1d38824cf9 100644 --- a/node/src/types/appendable_block.rs +++ b/node/src/types/appendable_block.rs @@ -83,19 +83,19 @@ impl AppendableBlock { if expires < self.timestamp { return Err(AddError::Expired); } - let category = footprint.lane; + let lane_id = footprint.lane_id; let limit = self .transaction_config .transaction_v1_config - .get_max_transaction_count(category); + .get_max_transaction_count(lane_id); // check total count by category let count = self .transactions .iter() - .filter(|(_, item)| item.lane == category) + .filter(|(_, item)| item.lane_id == lane_id) .count(); - if count.checked_add(1).ok_or(AddError::Count(category))? > limit as usize { - return Err(AddError::Count(category)); + if count.checked_add(1).ok_or(AddError::Count(lane_id))? > limit as usize { + return Err(AddError::Count(lane_id)); } // check total gas let gas_limit: U512 = self @@ -161,7 +161,7 @@ impl AppendableBlock { items: &BTreeMap, ) { let mut ret = vec![]; - for (x, y) in items.iter().filter(|(_, y)| y.lane == lane) { + for (x, y) in items.iter().filter(|(_, y)| y.lane_id == lane) { ret.push((*x, y.approvals.clone())); } if !ret.is_empty() { @@ -177,9 +177,9 @@ impl AppendableBlock { .transaction_v1_config .wasm_lanes .iter() - .map(|lane| lane[0]) + .map(|lane| lane.id()) { - collate(lane_id as u8, &mut transactions, &footprints); + collate(lane_id, &mut transactions, &footprints); } BlockPayload::new( @@ -198,7 +198,7 @@ impl AppendableBlock { fn category_lane(&self, lane: u8) -> usize { self.transactions .iter() - .filter(|(_, f)| f.lane == lane) + .filter(|(_, f)| f.lane_id == lane) .count() } @@ -215,7 +215,12 @@ impl Display for AppendableBlock { let auction_count = self.category_lane(AUCTION_LANE_ID); let install_upgrade_count = self.category_lane(INSTALL_UPGRADE_LANE_ID); let wasm_count = total_count - mint_count - auction_count - install_upgrade_count; - let total_gas_limit: Gas = self.transactions.values().map(|f| f.gas_limit).sum(); + let total_gas_limit: Gas = self + .transactions + .values() + .map(|f| f.gas_limit) + .try_fold(Gas::new(0), |acc, gas| acc.checked_add(gas)) + .unwrap_or(Gas::MAX); let total_approvals_count: usize = self .transactions .values() diff --git a/node/src/types/block/block_payload.rs b/node/src/types/block/block_payload.rs index 1142a4f390..cfcf479c12 100644 --- a/node/src/types/block/block_payload.rs +++ b/node/src/types/block/block_payload.rs @@ -62,7 +62,7 @@ impl BlockPayload { let mut ret = vec![]; if let Some(transactions) = self.transactions.get(&MINT_LANE_ID) { for transaction in transactions { - ret.push(transaction) + ret.push(transaction); } } ret.into_iter() @@ -73,7 +73,7 @@ impl BlockPayload { let mut ret = vec![]; if let Some(transactions) = self.transactions.get(&AUCTION_LANE_ID) { for transaction in transactions { - ret.push(transaction) + ret.push(transaction); } } ret.into_iter() @@ -84,7 +84,7 @@ impl BlockPayload { let mut ret = vec![]; if let Some(transactions) = self.transactions.get(&INSTALL_UPGRADE_LANE_ID) { for transaction in transactions { - ret.push(transaction) + ret.push(transaction); } } ret.into_iter() @@ -98,7 +98,7 @@ impl BlockPayload { let mut ret = vec![]; if let Some(transactions) = self.transactions.get(&lane) { for transaction in transactions { - ret.push(transaction) + ret.push(transaction); } } ret.into_iter() @@ -106,7 +106,7 @@ impl BlockPayload { pub(crate) fn finalized_payload(&self) -> BTreeMap> { let mut ret = BTreeMap::new(); - for (category, transactions) in self.transactions.iter() { + for (category, transactions) in &self.transactions { let transactions = transactions.iter().map(|(tx, _)| *tx).collect(); ret.insert(*category, transactions); } @@ -117,12 +117,8 @@ impl BlockPayload { /// Returns count of transactions by category. pub fn count(&self, lane: Option) -> usize { match lane { - None => self - .transactions - .values() - .map(|transactions| transactions.len()) - .sum(), - Some(category) => match self.transactions.get(&category) { + None => self.transactions.values().map(Vec::len).sum(), + Some(lane) => match self.transactions.get(&lane) { Some(values) => values.len(), None => 0, }, diff --git a/node/src/types/block/meta_block.rs b/node/src/types/block/meta_block.rs index 93b38957bd..e555d1266e 100644 --- a/node/src/types/block/meta_block.rs +++ b/node/src/types/block/meta_block.rs @@ -3,12 +3,12 @@ mod state; use std::{convert::TryFrom, sync::Arc}; +use crate::types::TransactionHeader; use datasize::DataSize; use serde::Serialize; use casper_types::{ execution::ExecutionResult, ActivationPoint, Block, BlockHash, BlockV2, EraId, TransactionHash, - TransactionHeader, }; pub(crate) use merge_mismatch_error::MergeMismatchError; @@ -185,7 +185,7 @@ mod tests { let txn = TransactionV1::random(rng); let execution_results = vec![ExecutionArtifact::new( TransactionHash::V1(*txn.hash()), - TransactionHeader::V1(txn.take_header()), + (&txn).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; @@ -240,7 +240,7 @@ mod tests { let txn = TransactionV1::random(rng); let execution_results = vec![ExecutionArtifact::new( TransactionHash::V1(*txn.hash()), - TransactionHeader::V1(txn.take_header()), + (&txn).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; @@ -278,7 +278,7 @@ mod tests { let txn = TransactionV1::random(rng); let execution_results = vec![ExecutionArtifact::new( TransactionHash::V1(*txn.hash()), - TransactionHeader::V1(txn.take_header()), + (&txn).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; @@ -311,14 +311,14 @@ mod tests { let txn1 = TransactionV1::random(rng); let execution_results1 = vec![ExecutionArtifact::new( TransactionHash::V1(*txn1.hash()), - TransactionHeader::V1(txn1.take_header()), + (&txn1).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; let txn2 = TransactionV1::random(rng); let execution_results2 = vec![ExecutionArtifact::new( TransactionHash::V1(*txn2.hash()), - TransactionHeader::V1(txn2.take_header()), + (&txn2).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; diff --git a/node/src/types/transaction.rs b/node/src/types/transaction.rs index 3fa0faaa3b..b8d26e1e30 100644 --- a/node/src/types/transaction.rs +++ b/node/src/types/transaction.rs @@ -1,5 +1,6 @@ mod deploy; +mod meta_transaction; mod transaction_footprint; - pub(crate) use deploy::LegacyDeploy; +pub(crate) use meta_transaction::{MetaTransaction, TransactionHeader, TransactionLane}; pub(crate) use transaction_footprint::TransactionFootprint; diff --git a/node/src/types/transaction/meta_transaction.rs b/node/src/types/transaction/meta_transaction.rs new file mode 100644 index 0000000000..6d1c4bbe6a --- /dev/null +++ b/node/src/types/transaction/meta_transaction.rs @@ -0,0 +1,305 @@ +mod meta_transaction_v1; +mod transaction_header; +mod transaction_lane; +pub(crate) use transaction_header::*; + +use casper_execution_engine::engine_state::{SessionDataDeploy, SessionDataV1, SessionInputData}; +use casper_types::{ + account::AccountHash, bytesrepr::ToBytes, Approval, Chainspec, Deploy, Digest, Gas, GasLimited, + InitiatorAddr, InvalidTransaction, Phase, PricingMode, TimeDiff, Timestamp, Transaction, + TransactionArgs, TransactionConfig, TransactionEntryPoint, TransactionHash, TransactionTarget, + INSTALL_UPGRADE_LANE_ID, LARGE_WASM_LANE_ID, MINT_LANE_ID, +}; +use core::fmt::{self, Debug, Display, Formatter}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +pub(crate) use meta_transaction_v1::MetaTransactionV1; +use serde::Serialize; +use std::{borrow::Cow, collections::BTreeSet}; +pub(crate) use transaction_lane::TransactionLane; + +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[derive(Clone, Debug, Serialize)] +pub(crate) enum MetaTransaction { + Deploy(Deploy), + V1(MetaTransactionV1), +} + +impl MetaTransaction { + /// Returns the `TransactionHash` identifying this transaction. + pub fn hash(&self) -> TransactionHash { + match self { + MetaTransaction::Deploy(deploy) => TransactionHash::from(*deploy.hash()), + MetaTransaction::V1(txn) => TransactionHash::from(*txn.hash()), + } + } + + /// Timestamp. + pub fn timestamp(&self) -> Timestamp { + match self { + MetaTransaction::Deploy(deploy) => deploy.header().timestamp(), + MetaTransaction::V1(v1) => v1.timestamp(), + } + } + + /// Time to live. + pub fn ttl(&self) -> TimeDiff { + match self { + MetaTransaction::Deploy(deploy) => deploy.header().ttl(), + MetaTransaction::V1(v1) => v1.ttl(), + } + } + + /// Returns the `Approval`s for this transaction. + pub fn approvals(&self) -> BTreeSet { + match self { + MetaTransaction::Deploy(deploy) => deploy.approvals().clone(), + MetaTransaction::V1(v1) => v1.approvals().clone(), + } + } + + /// Returns the address of the initiator of the transaction. + pub fn initiator_addr(&self) -> InitiatorAddr { + match self { + MetaTransaction::Deploy(deploy) => InitiatorAddr::PublicKey(deploy.account().clone()), + MetaTransaction::V1(txn) => txn.initiator_addr().clone(), + } + } + + /// Returns the set of account hashes corresponding to the public keys of the approvals. + pub fn signers(&self) -> BTreeSet { + match self { + MetaTransaction::Deploy(deploy) => deploy + .approvals() + .iter() + .map(|approval| approval.signer().to_account_hash()) + .collect(), + MetaTransaction::V1(txn) => txn + .approvals() + .iter() + .map(|approval| approval.signer().to_account_hash()) + .collect(), + } + } + + /// Returns `true` if `self` represents a native transfer deploy or a native V1 transaction. + pub fn is_native(&self) -> bool { + match self { + MetaTransaction::Deploy(deploy) => deploy.is_transfer(), + MetaTransaction::V1(v1_txn) => *v1_txn.target() == TransactionTarget::Native, + } + } + + /// Should this transaction use standard payment processing? + pub fn is_standard_payment(&self) -> bool { + match self { + MetaTransaction::Deploy(deploy) => deploy.payment().is_standard_payment(Phase::Payment), + MetaTransaction::V1(v1) => { + if let PricingMode::PaymentLimited { + standard_payment, .. + } = v1.pricing_mode() + { + *standard_payment + } else { + true + } + } + } + } + + /// Authorization keys. + pub fn authorization_keys(&self) -> BTreeSet { + match self { + MetaTransaction::Deploy(deploy) => deploy + .approvals() + .iter() + .map(|approval| approval.signer().to_account_hash()) + .collect(), + MetaTransaction::V1(transaction_v1) => transaction_v1 + .approvals() + .iter() + .map(|approval| approval.signer().to_account_hash()) + .collect(), + } + } + + /// The session args. + pub fn session_args(&self) -> Cow { + match self { + MetaTransaction::Deploy(deploy) => { + Cow::Owned(TransactionArgs::Named(deploy.session().args().clone())) + } + MetaTransaction::V1(transaction_v1) => Cow::Borrowed(transaction_v1.args()), + } + } + + /// The entry point. + pub fn entry_point(&self) -> TransactionEntryPoint { + match self { + MetaTransaction::Deploy(deploy) => deploy.session().entry_point_name().into(), + MetaTransaction::V1(transaction_v1) => transaction_v1.entry_point().clone(), + } + } + + /// The transaction lane. + pub fn transaction_lane(&self) -> u8 { + match self { + MetaTransaction::Deploy(deploy) => { + if deploy.is_transfer() { + MINT_LANE_ID + } else { + LARGE_WASM_LANE_ID + } + } + MetaTransaction::V1(v1) => v1.transaction_lane(), + } + } + + /// Returns the gas price tolerance. + pub fn gas_price_tolerance(&self) -> Result { + match self { + MetaTransaction::Deploy(deploy) => deploy + .gas_price_tolerance() + .map_err(InvalidTransaction::from), + MetaTransaction::V1(v1) => Ok(v1.gas_price_tolerance()), + } + } + + pub fn gas_limit(&self, chainspec: &Chainspec) -> Result { + match self { + MetaTransaction::Deploy(deploy) => deploy + .gas_limit(chainspec) + .map_err(InvalidTransaction::from), + MetaTransaction::V1(v1) => v1.gas_limit(chainspec), + } + } + + /// Is the transaction the legacy deploy variant. + pub fn is_legacy_transaction(&self) -> bool { + match self { + MetaTransaction::Deploy(_) => true, + MetaTransaction::V1(_) => false, + } + } + + pub fn from( + transaction: &Transaction, + transaction_config: &TransactionConfig, + ) -> Result { + match transaction { + Transaction::Deploy(deploy) => Ok(MetaTransaction::Deploy(deploy.clone())), + Transaction::V1(v1) => { + MetaTransactionV1::from(v1, transaction_config).map(MetaTransaction::V1) + } + } + } + + pub fn is_config_compliant( + &self, + chainspec: &Chainspec, + timestamp_leeway: TimeDiff, + at: Timestamp, + ) -> Result<(), InvalidTransaction> { + match self { + MetaTransaction::Deploy(deploy) => deploy + .is_config_compliant(chainspec, timestamp_leeway, at) + .map_err(InvalidTransaction::from), + MetaTransaction::V1(v1) => v1 + .is_config_compliant(chainspec, timestamp_leeway, at) + .map_err(InvalidTransaction::from), + } + } + + pub fn payload_hash(&self) -> Digest { + match self { + MetaTransaction::Deploy(deploy) => *deploy.body_hash(), + MetaTransaction::V1(v1) => *v1.payload_hash(), + } + } + + pub fn to_session_input_data(&self) -> SessionInputData { + let initiator_addr = self.initiator_addr(); + let is_standard_payment = self.is_standard_payment(); + match self { + MetaTransaction::Deploy(deploy) => { + let data = SessionDataDeploy::new( + deploy.hash(), + deploy.session(), + initiator_addr, + self.signers().clone(), + is_standard_payment, + ); + SessionInputData::DeploySessionData { data } + } + MetaTransaction::V1(v1) => { + let data = SessionDataV1::new( + v1.args().as_named().expect("V1 wasm args should be named and validated at the transaction acceptor level"), + v1.target(), + v1.entry_point(), + v1.transaction_lane() == INSTALL_UPGRADE_LANE_ID, + v1.hash(), + v1.pricing_mode(), + initiator_addr, + self.signers().clone(), + is_standard_payment, + ); + SessionInputData::SessionDataV1 { data } + } + } + } + + /// Size estimate. + pub fn size_estimate(&self) -> usize { + match self { + MetaTransaction::Deploy(deploy) => deploy.serialized_length(), + MetaTransaction::V1(v1) => v1.serialized_length(), + } + } + + pub fn is_v2_wasm(&self) -> bool { + match self { + MetaTransaction::Deploy(deploy) => false, + MetaTransaction::V1(v1) => v1.is_v2_wasm(), + } + } + + pub(crate) fn seed(&self) -> Option<[u8; 32]> { + match self { + MetaTransaction::Deploy(deploy) => None, + MetaTransaction::V1(v1) => v1.seed(), + } + } + + pub(crate) fn is_install_or_upgrade(&self) -> bool { + match self { + MetaTransaction::Deploy(deploy) => false, + MetaTransaction::V1(meta_transaction_v1) => { + meta_transaction_v1.transaction_lane() == INSTALL_UPGRADE_LANE_ID + } + } + } +} + +impl Display for MetaTransaction { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + match self { + MetaTransaction::Deploy(deploy) => Display::fmt(deploy, formatter), + MetaTransaction::V1(txn) => Display::fmt(txn, formatter), + } + } +} + +#[cfg(test)] +mod proptests { + use super::*; + use casper_types::gens::legal_transaction_arb; + use proptest::prelude::*; + + proptest! { + #[test] + fn construction_roundtrip(transaction in legal_transaction_arb()) { + let maybe_transaction = MetaTransaction::from(&transaction, &TransactionConfig::default()); + assert!(maybe_transaction.is_ok()); + } + } +} diff --git a/node/src/types/transaction/meta_transaction/meta_transaction_v1.rs b/node/src/types/transaction/meta_transaction/meta_transaction_v1.rs new file mode 100644 index 0000000000..2e5ddcd97f --- /dev/null +++ b/node/src/types/transaction/meta_transaction/meta_transaction_v1.rs @@ -0,0 +1,636 @@ +use super::transaction_lane::{calculate_transaction_lane, TransactionLane}; +use casper_types::{ + arg_handling, bytesrepr::ToBytes, crypto, Approval, Chainspec, Digest, DisplayIter, Gas, + InitiatorAddr, InvalidTransaction, InvalidTransactionV1, PricingHandling, PricingMode, + RuntimeArgs, TimeDiff, Timestamp, TransactionArgs, TransactionConfig, TransactionEntryPoint, + TransactionRuntime, TransactionScheduling, TransactionTarget, TransactionV1, + TransactionV1ExcessiveSizeError, TransactionV1Hash, U512, +}; +use core::fmt::{self, Debug, Display, Formatter}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(any(feature = "once_cell", test))] +use once_cell::sync::OnceCell; +use serde::Serialize; +use std::collections::BTreeSet; +use tracing::debug; + +const ARGS_MAP_KEY: u16 = 0; +const TARGET_MAP_KEY: u16 = 1; +const ENTRY_POINT_MAP_KEY: u16 = 2; +const SCHEDULING_MAP_KEY: u16 = 3; +const TRANSFERRED_VALUE_MAP_KEY: u16 = 4; +const SEED_MAP_KEY: u16 = 5; +const EXPECTED_NUMBER_OF_FIELDS: usize = 6; + +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[derive(Clone, Debug, Serialize)] +pub struct MetaTransactionV1 { + hash: TransactionV1Hash, + chain_name: String, + timestamp: Timestamp, + ttl: TimeDiff, + pricing_mode: PricingMode, + initiator_addr: InitiatorAddr, + args: TransactionArgs, + target: TransactionTarget, + entry_point: TransactionEntryPoint, + transaction_lane: TransactionLane, + scheduling: TransactionScheduling, + approvals: BTreeSet, + serialized_length: usize, + payload_hash: Digest, + transferred_value: u64, + seed: Option<[u8; 32]>, + has_valid_hash: Result<(), InvalidTransactionV1>, + #[cfg_attr(any(all(feature = "std", feature = "once_cell"), test), serde(skip))] + #[cfg_attr( + all(any(feature = "once_cell", test), feature = "datasize"), + data_size(skip) + )] + #[cfg(any(feature = "once_cell", test))] + is_verified: OnceCell>, +} + +impl MetaTransactionV1 { + pub fn from( + v1: &TransactionV1, + transaction_config: &TransactionConfig, + ) -> Result { + let args: TransactionArgs = v1.deserialize_field(ARGS_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let target: TransactionTarget = v1.deserialize_field(TARGET_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let entry_point: TransactionEntryPoint = + v1.deserialize_field(ENTRY_POINT_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let scheduling: TransactionScheduling = + v1.deserialize_field(SCHEDULING_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let transferred_value = + v1.deserialize_field(TRANSFERRED_VALUE_MAP_KEY) + .map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let seed = v1.deserialize_field(SEED_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + + if v1.number_of_fields() != EXPECTED_NUMBER_OF_FIELDS { + return Err(InvalidTransaction::V1( + InvalidTransactionV1::UnexpectedTransactionFieldEntries, + )); + } + + let payload_hash = v1.payload_hash()?; + let serialized_length = v1.serialized_length(); + + let lane_id = calculate_transaction_lane( + &entry_point, + &target, + v1.pricing_mode().additional_computation_factor(), + transaction_config, + serialized_length as u64, + )?; + let transaction_lane = + TransactionLane::try_from(lane_id).map_err(Into::::into)?; + let has_valid_hash = v1.has_valid_hash(); + let approvals = v1.approvals().clone(); + Ok(MetaTransactionV1::new( + *v1.hash(), + v1.chain_name().to_string(), + v1.timestamp(), + v1.ttl(), + v1.pricing_mode().clone(), + v1.initiator_addr().clone(), + args, + target, + entry_point, + transaction_lane, + scheduling, + serialized_length, + payload_hash, + approvals, + transferred_value, + seed, + has_valid_hash, + )) + } + + fn is_native_mint(&self) -> bool { + self.transaction_lane == TransactionLane::Mint + } + + fn is_native_auction(&self) -> bool { + self.transaction_lane == TransactionLane::Auction + } + + pub(crate) fn is_v2_wasm(&self) -> bool { + match self.target { + TransactionTarget::Native => false, + TransactionTarget::Stored { runtime, .. } + | TransactionTarget::Session { runtime, .. } => { + matches!(runtime, TransactionRuntime::VmCasperV2) + && (!self.is_native_mint() && !self.is_native_auction()) + } + } + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + hash: TransactionV1Hash, + chain_name: String, + timestamp: Timestamp, + ttl: TimeDiff, + pricing_mode: PricingMode, + initiator_addr: InitiatorAddr, + args: TransactionArgs, + target: TransactionTarget, + entry_point: TransactionEntryPoint, + transaction_lane: TransactionLane, + scheduling: TransactionScheduling, + serialized_length: usize, + payload_hash: Digest, + approvals: BTreeSet, + transferred_value: u64, + seed: Option<[u8; 32]>, + has_valid_hash: Result<(), InvalidTransactionV1>, + ) -> Self { + Self { + hash, + chain_name, + timestamp, + ttl, + pricing_mode, + initiator_addr, + args, + target, + entry_point, + transaction_lane, + scheduling, + approvals, + serialized_length, + payload_hash, + has_valid_hash, + transferred_value, + seed, + #[cfg(any(feature = "once_cell", test))] + is_verified: OnceCell::new(), + } + } + + /// Returns the runtime args of the transaction. + pub fn args(&self) -> &TransactionArgs { + &self.args + } + + /// Returns the `DeployHash` identifying this `Deploy`. + pub fn hash(&self) -> &TransactionV1Hash { + &self.hash + } + + /// Returns the `Approvals`. + pub fn approvals(&self) -> &BTreeSet { + &self.approvals + } + + /// Returns `Ok` if and only if: + /// * the transaction hash is correct (see [`TransactionV1::has_valid_hash`] for details) + /// * approvals are non empty, and + /// * all approvals are valid signatures of the signed hash + pub fn verify(&self) -> Result<(), InvalidTransactionV1> { + #[cfg(any(feature = "once_cell", test))] + return self.is_verified.get_or_init(|| self.do_verify()).clone(); + + #[cfg(not(any(feature = "once_cell", test)))] + self.do_verify() + } + + /// Returns `Ok` if and only if this transaction's body hashes to the value of `body_hash()`, + /// and if this transaction's header hashes to the value claimed as the transaction hash. + pub fn has_valid_hash(&self) -> &Result<(), InvalidTransactionV1> { + &self.has_valid_hash + } + + fn do_verify(&self) -> Result<(), InvalidTransactionV1> { + if self.approvals.is_empty() { + debug!(?self, "transaction has no approvals"); + return Err(InvalidTransactionV1::EmptyApprovals); + } + + self.has_valid_hash().clone()?; + + for (index, approval) in self.approvals.iter().enumerate() { + if let Err(error) = crypto::verify(self.hash, approval.signature(), approval.signer()) { + debug!( + ?self, + "failed to verify transaction approval {}: {}", index, error + ); + return Err(InvalidTransactionV1::InvalidApproval { index, error }); + } + } + + Ok(()) + } + + /// Returns the entry point of the transaction. + pub fn entry_point(&self) -> &TransactionEntryPoint { + &self.entry_point + } + + /// Returns the transaction lane. + pub fn transaction_lane(&self) -> u8 { + self.transaction_lane as u8 + } + + /// Returns payload hash of the transaction. + pub fn payload_hash(&self) -> &Digest { + &self.payload_hash + } + + /// Returns the pricing mode for the transaction. + pub fn pricing_mode(&self) -> &PricingMode { + &self.pricing_mode + } + + /// Returns the initiator_addr of the transaction. + pub fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + /// Returns the target of the transaction. + pub fn target(&self) -> &TransactionTarget { + &self.target + } + + /// Returns `true` if the serialized size of the transaction is not greater than + /// `max_transaction_size`. + fn is_valid_size( + &self, + max_transaction_size: u32, + ) -> Result<(), TransactionV1ExcessiveSizeError> { + let actual_transaction_size = self.serialized_length; + if actual_transaction_size > max_transaction_size as usize { + return Err(TransactionV1ExcessiveSizeError { + max_transaction_size, + actual_transaction_size, + }); + } + Ok(()) + } + + /// Returns the creation timestamp of the `Deploy`. + pub fn timestamp(&self) -> Timestamp { + self.timestamp + } + + /// Returns the duration after the creation timestamp for which the `Deploy` will stay valid. + /// + /// After this duration has ended, the `Deploy` will be considered expired. + pub fn ttl(&self) -> TimeDiff { + self.ttl + } + + /// Returns `Ok` if and only if: + /// * the chain_name is correct, + /// * the configured parameters are complied with at the given timestamp + pub fn is_config_compliant( + &self, + chainspec: &Chainspec, + timestamp_leeway: TimeDiff, + at: Timestamp, + ) -> Result<(), InvalidTransactionV1> { + let transaction_config = chainspec.transaction_config.clone(); + self.is_valid_size( + transaction_config + .transaction_v1_config + .get_max_serialized_length(self.transaction_lane as u8) as u32, + )?; + + let chain_name = chainspec.network_config.name.clone(); + + if self.chain_name != chain_name { + debug!( + transaction_hash = %self.hash(), + chain_name = %self.chain_name, + timestamp= %self.timestamp, + ttl= %self.ttl, + pricing_mode= %self.pricing_mode, + initiator_addr= %self.initiator_addr, + target= %self.target, + entry_point= %self.entry_point, + transaction_lane= %self.transaction_lane, + scheduling= %self.scheduling, + "invalid chain identifier" + ); + return Err(InvalidTransactionV1::InvalidChainName { + expected: chain_name, + got: self.chain_name.to_string(), + }); + } + + let price_handling = chainspec.core_config.pricing_handling; + let pricing_mode = &self.pricing_mode; + + match pricing_mode { + PricingMode::PaymentLimited { .. } => { + if let PricingHandling::Classic = price_handling { + } else { + return Err(InvalidTransactionV1::InvalidPricingMode { + price_mode: pricing_mode.clone(), + }); + } + } + PricingMode::Fixed { .. } => { + if let PricingHandling::Fixed = price_handling { + } else { + return Err(InvalidTransactionV1::InvalidPricingMode { + price_mode: pricing_mode.clone(), + }); + } + } + PricingMode::Reserved { .. } => { + if !chainspec.core_config.allow_reservations { + // Currently Reserved isn't implemented and we should + // not be accepting transactions with this mode. + return Err(InvalidTransactionV1::InvalidPricingMode { + price_mode: pricing_mode.clone(), + }); + } + } + } + + let min_gas_price = chainspec.vacancy_config.min_gas_price; + let gas_price_tolerance = self.gas_price_tolerance(); + if gas_price_tolerance < min_gas_price { + return Err(InvalidTransactionV1::GasPriceToleranceTooLow { + min_gas_price_tolerance: min_gas_price, + provided_gas_price_tolerance: gas_price_tolerance, + }); + } + + self.is_header_metadata_valid(&transaction_config, timestamp_leeway, at, &self.hash)?; + + let max_associated_keys = chainspec.core_config.max_associated_keys; + + if self.approvals.len() > max_associated_keys as usize { + debug!( + transaction_hash = %self.hash(), + number_of_approvals = %self.approvals.len(), + max_associated_keys = %max_associated_keys, + "number of transaction approvals exceeds the limit" + ); + return Err(InvalidTransactionV1::ExcessiveApprovals { + got: self.approvals.len() as u32, + max_associated_keys, + }); + } + + let gas_limit = self + .pricing_mode + .gas_limit(chainspec, &self.entry_point, self.transaction_lane as u8) + .map_err(Into::::into)?; + let block_gas_limit = Gas::new(U512::from(transaction_config.block_gas_limit)); + if gas_limit > block_gas_limit { + debug!( + amount = %gas_limit, + %block_gas_limit, + "transaction gas limit exceeds block gas limit" + ); + return Err(InvalidTransactionV1::ExceedsBlockGasLimit { + block_gas_limit: transaction_config.block_gas_limit, + got: Box::new(gas_limit.value()), + }); + } + + self.is_body_metadata_valid(&transaction_config) + } + + fn is_body_metadata_valid( + &self, + config: &TransactionConfig, + ) -> Result<(), InvalidTransactionV1> { + let lane_id = self.transaction_lane as u8; + if !config.transaction_v1_config.is_supported(lane_id) { + return Err(InvalidTransactionV1::InvalidTransactionLane(lane_id)); + } + + let max_serialized_length = config + .transaction_v1_config + .get_max_serialized_length(lane_id); + let actual_length = self.serialized_length; + if actual_length > max_serialized_length as usize { + return Err(InvalidTransactionV1::ExcessiveSize( + TransactionV1ExcessiveSizeError { + max_transaction_size: max_serialized_length as u32, + actual_transaction_size: actual_length, + }, + )); + } + + let max_args_length = config.transaction_v1_config.get_max_args_length(lane_id); + + let args_length = self.args.serialized_length(); + if args_length > max_args_length as usize { + debug!( + args_length, + max_args_length = max_args_length, + "transaction runtime args excessive size" + ); + return Err(InvalidTransactionV1::ExcessiveArgsLength { + max_length: max_args_length as usize, + got: args_length, + }); + } + + match &self.target { + TransactionTarget::Native => match self.entry_point { + TransactionEntryPoint::Call => { + debug!( + entry_point = %self.entry_point, + "native transaction cannot have call entry point" + ); + Err(InvalidTransactionV1::EntryPointCannotBeCall) + } + TransactionEntryPoint::Custom(_) => { + debug!( + entry_point = %self.entry_point, + "native transaction cannot have custom entry point" + ); + Err(InvalidTransactionV1::EntryPointCannotBeCustom { + entry_point: self.entry_point.clone(), + }) + } + TransactionEntryPoint::Transfer => arg_handling::has_valid_transfer_args( + &self.args, + config.native_transfer_minimum_motes, + ), + TransactionEntryPoint::AddBid => arg_handling::has_valid_add_bid_args(&self.args), + TransactionEntryPoint::WithdrawBid => { + arg_handling::has_valid_withdraw_bid_args(&self.args) + } + TransactionEntryPoint::Delegate => { + arg_handling::has_valid_delegate_args(&self.args) + } + TransactionEntryPoint::Undelegate => { + arg_handling::has_valid_undelegate_args(&self.args) + } + TransactionEntryPoint::Redelegate => { + arg_handling::has_valid_redelegate_args(&self.args) + } + TransactionEntryPoint::ActivateBid => { + arg_handling::has_valid_activate_bid_args(&self.args) + } + TransactionEntryPoint::ChangeBidPublicKey => { + arg_handling::has_valid_change_bid_public_key_args(&self.args) + } + TransactionEntryPoint::AddReservations => { + todo!() + } + TransactionEntryPoint::CancelReservations => { + todo!() + } + }, + TransactionTarget::Stored { .. } => match &self.entry_point { + TransactionEntryPoint::Custom(_) => Ok(()), + TransactionEntryPoint::Call + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + debug!( + entry_point = %self.entry_point, + "transaction targeting stored entity/package must have custom entry point" + ); + Err(InvalidTransactionV1::EntryPointMustBeCustom { + entry_point: self.entry_point.clone(), + }) + } + }, + TransactionTarget::Session { module_bytes, .. } => match &self.entry_point { + TransactionEntryPoint::Call | TransactionEntryPoint::Custom(_) => { + if module_bytes.is_empty() { + debug!("transaction with session code must not have empty module bytes"); + return Err(InvalidTransactionV1::EmptyModuleBytes); + } + Ok(()) + } + TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + debug!( + entry_point = %self.entry_point, + "transaction with session code must use custom or default 'call' entry point" + ); + Err(InvalidTransactionV1::EntryPointMustBeCustom { + entry_point: self.entry_point.clone(), + }) + } + }, + } + } + + fn is_header_metadata_valid( + &self, + config: &TransactionConfig, + timestamp_leeway: TimeDiff, + at: Timestamp, + transaction_hash: &TransactionV1Hash, + ) -> Result<(), InvalidTransactionV1> { + if self.ttl() > config.max_ttl { + debug!( + %transaction_hash, + transaction_header = %self, + max_ttl = %config.max_ttl, + "transaction ttl excessive" + ); + return Err(InvalidTransactionV1::ExcessiveTimeToLive { + max_ttl: config.max_ttl, + got: self.ttl(), + }); + } + + if self.timestamp() > at + timestamp_leeway { + debug!( + %transaction_hash, transaction_header = %self, %at, + "transaction timestamp in the future" + ); + return Err(InvalidTransactionV1::TimestampInFuture { + validation_timestamp: at, + timestamp_leeway, + got: self.timestamp(), + }); + } + + Ok(()) + } + + /// Returns the gas price tolerance for the given transaction. + pub fn gas_price_tolerance(&self) -> u8 { + match self.pricing_mode { + PricingMode::PaymentLimited { + gas_price_tolerance, + .. + } => gas_price_tolerance, + PricingMode::Fixed { + gas_price_tolerance, + .. + } => gas_price_tolerance, + PricingMode::Reserved { .. } => { + // TODO: Change this when reserve gets implemented. + 0u8 + } + } + } + + pub fn serialized_length(&self) -> usize { + self.serialized_length + } + + pub fn gas_limit(&self, chainspec: &Chainspec) -> Result { + self.pricing_mode() + .gas_limit(chainspec, self.entry_point(), self.transaction_lane as u8) + .map_err(Into::into) + } + + pub(crate) fn seed(&self) -> Option<[u8; 32]> { + self.seed + } +} + +impl Display for MetaTransactionV1 { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + write!( + formatter, + "meta-transaction-v1[hash: {}, chain_name: {}, timestamp: {}, ttl: {}, pricing_mode: {}, initiator_addr: {}, target: {}, entry_point: {}, transaction_lane: {}, scheduling: {}, approvals: {}]", + self.hash, + self.chain_name, + self.timestamp, + self.ttl, + self.pricing_mode, + self.initiator_addr, + self.target, + self.entry_point, + self.transaction_lane, + self.scheduling, + DisplayIter::new(self.approvals.iter()) + ) + } +} diff --git a/node/src/types/transaction/meta_transaction/transaction_header.rs b/node/src/types/transaction/meta_transaction/transaction_header.rs new file mode 100644 index 0000000000..fa0c6b0108 --- /dev/null +++ b/node/src/types/transaction/meta_transaction/transaction_header.rs @@ -0,0 +1,77 @@ +use casper_types::{DeployHeader, InitiatorAddr, TimeDiff, Timestamp, Transaction, TransactionV1}; +use core::fmt::{self, Display, Formatter}; +use datasize::DataSize; +use serde::Serialize; + +#[derive(Debug, Clone, DataSize, PartialEq, Eq, Serialize)] +pub(crate) struct TransactionV1Metadata { + initiator_addr: InitiatorAddr, + timestamp: Timestamp, + ttl: TimeDiff, +} + +impl TransactionV1Metadata { + pub(crate) fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + pub(crate) fn timestamp(&self) -> Timestamp { + self.timestamp + } + + pub(crate) fn ttl(&self) -> TimeDiff { + self.ttl + } +} + +impl Display for TransactionV1Metadata { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + write!( + formatter, + "transaction-v1-metadata[initiator_addr: {}]", + self.initiator_addr, + ) + } +} + +#[derive(Debug, Clone, DataSize, Serialize, PartialEq, Eq)] +/// A versioned wrapper for a transaction header or deploy header. +pub(crate) enum TransactionHeader { + Deploy(DeployHeader), + V1(TransactionV1Metadata), +} + +impl From for TransactionHeader { + fn from(header: DeployHeader) -> Self { + Self::Deploy(header) + } +} + +impl From<&TransactionV1> for TransactionHeader { + fn from(transaction_v1: &TransactionV1) -> Self { + let meta = TransactionV1Metadata { + initiator_addr: transaction_v1.initiator_addr().clone(), + timestamp: transaction_v1.timestamp(), + ttl: transaction_v1.ttl(), + }; + Self::V1(meta) + } +} + +impl From<&Transaction> for TransactionHeader { + fn from(transaction: &Transaction) -> Self { + match transaction { + Transaction::Deploy(deploy) => deploy.header().clone().into(), + Transaction::V1(v1) => v1.into(), + } + } +} + +impl Display for TransactionHeader { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + match self { + TransactionHeader::Deploy(header) => Display::fmt(header, formatter), + TransactionHeader::V1(meta) => Display::fmt(meta, formatter), + } + } +} diff --git a/node/src/types/transaction/meta_transaction/transaction_lane.rs b/node/src/types/transaction/meta_transaction/transaction_lane.rs new file mode 100644 index 0000000000..0d39ccf0f5 --- /dev/null +++ b/node/src/types/transaction/meta_transaction/transaction_lane.rs @@ -0,0 +1,161 @@ +use core::{ + convert::TryFrom, + fmt::{self, Formatter}, +}; + +use casper_types::{ + InvalidTransaction, InvalidTransactionV1, TransactionConfig, TransactionEntryPoint, + TransactionTarget, TransactionV1Config, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, +}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +use serde::Serialize; + +/// The category of a Transaction. +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Serialize)] +#[repr(u8)] +pub enum TransactionLane { + /// Native mint interaction (the default). + Mint = 0, + /// Native auction interaction. + Auction = 1, + /// InstallUpgradeWasm + InstallUpgradeWasm = 2, + /// A large Wasm based transaction. + Large = 3, + /// A medium Wasm based transaction. + Medium = 4, + /// A small Wasm based transaction. + Small = 5, +} + +#[derive(Debug)] +pub struct TransactionLaneConversionError(u8); + +impl From for InvalidTransaction { + fn from(value: TransactionLaneConversionError) -> InvalidTransaction { + InvalidTransaction::V1(InvalidTransactionV1::InvalidTransactionLane(value.0)) + } +} + +impl TryFrom for TransactionLane { + type Error = TransactionLaneConversionError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Mint), + 1 => Ok(Self::Auction), + 2 => Ok(Self::InstallUpgradeWasm), + 3 => Ok(Self::Large), + 4 => Ok(Self::Medium), + 5 => Ok(Self::Small), + _ => Err(TransactionLaneConversionError(value)), + } + } +} + +impl fmt::Display for TransactionLane { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + TransactionLane::Mint => write!(f, "Mint"), + TransactionLane::Auction => write!(f, "Auction"), + TransactionLane::Large => write!(f, "Large"), + TransactionLane::Medium => write!(f, "Medium"), + TransactionLane::Small => write!(f, "Small"), + TransactionLane::InstallUpgradeWasm => write!(f, "InstallUpgradeWASM"), + } + } +} + +/// Calculates the laned based on properties of the transaction +pub(crate) fn calculate_transaction_lane( + entry_point: &TransactionEntryPoint, + target: &TransactionTarget, + additional_computation_factor: u8, + transaction_config: &TransactionConfig, + transaction_size: u64, +) -> Result { + match target { + TransactionTarget::Native => match entry_point { + TransactionEntryPoint::Transfer => Ok(MINT_LANE_ID), + TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => Ok(AUCTION_LANE_ID), + TransactionEntryPoint::Call => Err(InvalidTransactionV1::EntryPointCannotBeCall), + TransactionEntryPoint::Custom(_) => { + Err(InvalidTransactionV1::EntryPointCannotBeCustom { + entry_point: entry_point.clone(), + }) + } + }, + TransactionTarget::Stored { .. } => match entry_point { + TransactionEntryPoint::Custom(_) => get_lane_for_non_install_wasm( + &transaction_config.transaction_v1_config, + transaction_size, + additional_computation_factor, + ), + TransactionEntryPoint::Call + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + Err(InvalidTransactionV1::EntryPointMustBeCustom { + entry_point: entry_point.clone(), + }) + } + }, + TransactionTarget::Session { + is_install_upgrade, .. + } => match entry_point { + TransactionEntryPoint::Call => { + if *is_install_upgrade { + Ok(INSTALL_UPGRADE_LANE_ID) + } else { + get_lane_for_non_install_wasm( + &transaction_config.transaction_v1_config, + transaction_size, + additional_computation_factor, + ) + } + } + TransactionEntryPoint::Custom(_) + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + Err(InvalidTransactionV1::EntryPointMustBeCall { + entry_point: entry_point.clone(), + }) + } + }, + } +} + +fn get_lane_for_non_install_wasm( + config: &TransactionV1Config, + transaction_size: u64, + additional_computation_factor: u8, +) -> Result { + config + .get_wasm_lane_id(transaction_size, additional_computation_factor) + .ok_or(InvalidTransactionV1::NoWasmLaneMatchesTransaction()) +} diff --git a/node/src/types/transaction/transaction_footprint.rs b/node/src/types/transaction/transaction_footprint.rs index 7d27602b56..825fa57589 100644 --- a/node/src/types/transaction/transaction_footprint.rs +++ b/node/src/types/transaction/transaction_footprint.rs @@ -1,6 +1,7 @@ +use crate::types::MetaTransaction; use casper_types::{ - Approval, Chainspec, Digest, Gas, GasLimited, InvalidTransaction, InvalidTransactionV1, - TimeDiff, Timestamp, Transaction, TransactionHash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, + Approval, Chainspec, Digest, Gas, InvalidTransaction, InvalidTransactionV1, TimeDiff, + Timestamp, Transaction, TransactionHash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, }; use datasize::DataSize; @@ -13,16 +14,16 @@ use std::collections::BTreeSet; pub(crate) struct TransactionFootprint { /// The identifying hash. pub(crate) transaction_hash: TransactionHash, - /// Transaction body hash. - pub(crate) body_hash: Digest, + /// Transaction payload hash. + pub(crate) payload_hash: Digest, /// The estimated gas consumption. pub(crate) gas_limit: Gas, /// The gas tolerance. pub(crate) gas_price_tolerance: u8, /// The bytesrepr serialized length. pub(crate) size_estimate: usize, - /// The transaction lane. - pub(crate) lane: u8, + /// The transaction lane_id. + pub(crate) lane_id: u8, /// Timestamp of the transaction. pub(crate) timestamp: Timestamp, /// Time to live for the transaction. @@ -35,32 +36,40 @@ impl TransactionFootprint { pub(crate) fn new( chainspec: &Chainspec, transaction: &Transaction, + ) -> Result { + let transaction = MetaTransaction::from(transaction, &chainspec.transaction_config)?; + Self::new_from_meta_transaction(chainspec, &transaction) + } + + fn new_from_meta_transaction( + chainspec: &Chainspec, + transaction: &MetaTransaction, ) -> Result { let gas_price_tolerance = transaction.gas_price_tolerance()?; let gas_limit = transaction.gas_limit(chainspec)?; - let category = transaction.transaction_lane(); + let lane_id = transaction.transaction_lane(); if !chainspec .transaction_config .transaction_v1_config - .is_supported(category) + .is_supported(lane_id) { return Err(InvalidTransaction::V1( - InvalidTransactionV1::InvalidTransactionKind(category), + InvalidTransactionV1::InvalidTransactionLane(lane_id), )); } let transaction_hash = transaction.hash(); - let body_hash = transaction.body_hash(); let size_estimate = transaction.size_estimate(); + let payload_hash = transaction.payload_hash(); let timestamp = transaction.timestamp(); let ttl = transaction.ttl(); let approvals = transaction.approvals(); Ok(TransactionFootprint { transaction_hash, - body_hash, + payload_hash, gas_limit, gas_price_tolerance, size_estimate, - lane: category, + lane_id, timestamp, ttl, approvals, @@ -80,7 +89,7 @@ impl TransactionFootprint { /// Is mint interaction. pub(crate) fn is_mint(&self) -> bool { - if self.lane == MINT_LANE_ID { + if self.lane_id == MINT_LANE_ID { return true; } @@ -89,7 +98,7 @@ impl TransactionFootprint { /// Is auction interaction. pub(crate) fn is_auction(&self) -> bool { - if self.lane == AUCTION_LANE_ID { + if self.lane_id == AUCTION_LANE_ID { return true; } @@ -97,7 +106,7 @@ impl TransactionFootprint { } pub(crate) fn is_install_upgrade(&self) -> bool { - if self.lane == INSTALL_UPGRADE_LANE_ID { + if self.lane_id == INSTALL_UPGRADE_LANE_ID { return true; } diff --git a/node/src/types/value_or_chunk.rs b/node/src/types/value_or_chunk.rs index b6381fba29..d8d071ef0f 100644 --- a/node/src/types/value_or_chunk.rs +++ b/node/src/types/value_or_chunk.rs @@ -107,11 +107,6 @@ impl Display for ValueOrChunk> { } } -/// Error type simply conveying that chunk validation failed. -#[derive(Debug, Error)] -#[error("Chunk validation failed")] -pub(crate) struct ChunkValidationError; - #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, DataSize)] pub struct HashingTrieRaw { inner: TrieRaw, diff --git a/node/src/utils.rs b/node/src/utils.rs index 42b124cefd..93294fb097 100644 --- a/node/src/utils.rs +++ b/node/src/utils.rs @@ -16,19 +16,18 @@ pub(crate) mod umask; pub mod work_queue; use std::{ - any, fmt::{self, Debug, Display, Formatter}, io, net::{SocketAddr, ToSocketAddrs}, ops::{Add, BitXorAssign, Div}, path::{Path, PathBuf}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::{Duration, Instant, SystemTime}, + sync::atomic::{AtomicBool, Ordering}, + time::{Instant, SystemTime}, }; +#[cfg(test)] +use std::{any, sync::Arc, time::Duration}; + use datasize::DataSize; use hyper::server::{conn::AddrIncoming, Builder, Server}; #[cfg(test)] @@ -175,7 +174,7 @@ impl SharedFlag { /// Set the flag. pub(crate) fn set(self) { - self.0.store(true, Ordering::SeqCst) + self.0.store(true, Ordering::SeqCst); } /// Returns a shared instance of the flag for testing. @@ -287,8 +286,9 @@ impl Source { impl Display for Source { fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { match self { - Source::PeerGossiped(node_id) => Display::fmt(node_id, formatter), - Source::Peer(node_id) => Display::fmt(node_id, formatter), + Source::PeerGossiped(node_id) | Source::Peer(node_id) => { + Display::fmt(node_id, formatter) + } Source::Client => write!(formatter, "client"), Source::SpeculativeExec => write!(formatter, "client (speculative exec)"), Source::Ourself => write!(formatter, "ourself"), @@ -359,6 +359,7 @@ pub(crate) fn xor(lhs: &mut [u8], rhs: &[u8]) { /// /// Using this function is usually a potential architectural issue and it should be used very /// sparingly. Consider introducing a different access pattern for the value under `Arc`. +#[cfg(test)] pub(crate) async fn wait_for_arc_drop( arc: Arc, attempts: usize, diff --git a/node/src/utils/chain_specification.rs b/node/src/utils/chain_specification.rs index 26e860192f..b980684d37 100644 --- a/node/src/utils/chain_specification.rs +++ b/node/src/utils/chain_specification.rs @@ -153,9 +153,9 @@ mod tests { use casper_types::{ bytesrepr::FromBytes, ActivationPoint, BrTableCost, ChainspecRawBytes, ControlFlowCosts, CoreConfig, EraId, GlobalStateUpdate, HighwayConfig, HostFunction, HostFunctionCosts, - MessageLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, StorageCosts, - StoredValue, TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, TransactionV1Config, - WasmConfig, MINT_LANE_ID, + MessageLimits, Motes, OpcodeCosts, ProtocolConfig, ProtocolVersion, StoredValue, + TestBlockBuilder, TimeDiff, Timestamp, TransactionConfig, TransactionV1Config, WasmConfig, + WasmV1Config, MINT_LANE_ID, }; use super::*; @@ -164,7 +164,6 @@ mod tests { utils::{Loadable, RESOURCES_PATH}, }; - const EXPECTED_GENESIS_STORAGE_COSTS: StorageCosts = StorageCosts::new(101); const EXPECTED_GENESIS_COSTS: OpcodeCosts = OpcodeCosts { bit: 13, add: 14, @@ -199,6 +198,7 @@ mod tests { nop: 25, current_memory: 26, grow_memory: 27, + sign: 28, }; static EXPECTED_GENESIS_HOST_FUNCTION_COSTS: Lazy = Lazy::new(|| HostFunctionCosts { @@ -250,16 +250,16 @@ mod tests { manage_message_topic: HostFunction::new(100, [0, 1, 2, 4]), emit_message: HostFunction::new(100, [0, 1, 2, 3]), cost_increase_per_message: 50, + get_block_info: HostFunction::new(330, [0, 0]), }); static EXPECTED_GENESIS_WASM_COSTS: Lazy = Lazy::new(|| { - WasmConfig::new( + let wasm_v1_config = WasmV1Config::new( 17, // initial_memory 19, // max_stack_height EXPECTED_GENESIS_COSTS, - EXPECTED_GENESIS_STORAGE_COSTS, *EXPECTED_GENESIS_HOST_FUNCTION_COSTS, - MessageLimits::default(), - ) + ); + WasmConfig::new(MessageLimits::default(), wasm_v1_config) }); #[test] @@ -627,7 +627,6 @@ mod tests { spec.transaction_config.max_ttl, TimeDiff::from_seconds(26_300_160) ); - assert_eq!(spec.transaction_config.deploy_config.max_dependencies, 11); assert_eq!(spec.transaction_config.max_block_size, 12); assert_eq!( spec.transaction_config diff --git a/node/src/utils/chain_specification/parse_toml.rs b/node/src/utils/chain_specification/parse_toml.rs index f0026844ea..1a3ce947a3 100644 --- a/node/src/utils/chain_specification/parse_toml.rs +++ b/node/src/utils/chain_specification/parse_toml.rs @@ -34,7 +34,8 @@ use serde::{Deserialize, Serialize}; use casper_types::{ bytesrepr::Bytes, file_utils, AccountsConfig, ActivationPoint, Chainspec, ChainspecRawBytes, CoreConfig, GlobalStateUpdate, GlobalStateUpdateConfig, HighwayConfig, NetworkConfig, - ProtocolConfig, ProtocolVersion, SystemConfig, TransactionConfig, VacancyConfig, WasmConfig, + ProtocolConfig, ProtocolVersion, StorageCosts, SystemConfig, TransactionConfig, VacancyConfig, + WasmConfig, }; use crate::utils::{ @@ -80,6 +81,7 @@ pub(super) struct TomlChainspec { wasm: WasmConfig, system_costs: SystemConfig, vacancy: VacancyConfig, + storage_costs: StorageCosts, } impl From<&Chainspec> for TomlChainspec { @@ -99,6 +101,7 @@ impl From<&Chainspec> for TomlChainspec { let wasm = chainspec.wasm_config; let system_costs = chainspec.system_costs_config; let vacancy = chainspec.vacancy_config; + let storage_costs = chainspec.storage_costs; TomlChainspec { protocol, @@ -109,6 +112,7 @@ impl From<&Chainspec> for TomlChainspec { wasm, system_costs, vacancy, + storage_costs, } } } @@ -163,6 +167,7 @@ pub(super) fn parse_toml>( wasm_config: toml_chainspec.wasm, system_costs_config: toml_chainspec.system_costs, vacancy_config: toml_chainspec.vacancy, + storage_costs: toml_chainspec.storage_costs, }; let chainspec_raw_bytes = ChainspecRawBytes::new( Bytes::from(chainspec_bytes), diff --git a/node/src/utils/ds.rs b/node/src/utils/ds.rs index f4f257c278..e412381afa 100644 --- a/node/src/utils/ds.rs +++ b/node/src/utils/ds.rs @@ -53,7 +53,7 @@ where let sampled = vec .as_slice() .choose_multiple(&mut rng, SAMPLE_SIZE) - .map(|v| v.estimate_heap_size()) + .map(DataSize::estimate_heap_size) .sum(); base_size + scale_sample(vec.len(), sampled) } diff --git a/node/src/utils/specimen.rs b/node/src/utils/specimen.rs index 468b02840c..700a63de7e 100644 --- a/node/src/utils/specimen.rs +++ b/node/src/utils/specimen.rs @@ -28,7 +28,8 @@ use casper_types::{ ProtocolVersion, RewardedSignatures, RuntimeArgs, SecretKey, SemVer, SignedBlockHeader, SingleBlockRewardedSignatures, TimeDiff, Timestamp, Transaction, TransactionHash, TransactionId, TransactionRuntime, TransactionV1, TransactionV1Builder, TransactionV1Hash, - URef, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, KEY_HASH_LENGTH, MINT_LANE_ID, U512, + URef, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, KEY_HASH_LENGTH, LARGE_WASM_LANE_ID, + MINT_LANE_ID, U512, }; use crate::{ @@ -47,8 +48,6 @@ use casper_storage::block_store::types::ApprovalsHashes; /// The largest valid unicode codepoint that can be encoded to UTF-8. pub(crate) const HIGHEST_UNICODE_CODEPOINT: char = '\u{10FFFF}'; -const LARGE_LANE_ID: u8 = 3; - /// A cache used for memoization, typically on a single estimator. #[derive(Debug, Default)] pub(crate) struct Cache { @@ -860,7 +859,7 @@ impl LargestSpecimen for BlockPayload { ], ); transactions.insert( - LARGE_LANE_ID, + LARGE_WASM_LANE_ID, vec![ large_txn_hash_with_approvals.clone(); estimator.parameter::("max_standard_transactions_per_block") @@ -1014,15 +1013,16 @@ impl LargestSpecimen for TransactionV1 { // See comment in `impl LargestSpecimen for ExecutableDeployItem` below for rationale here. let max_size_with_margin = estimator.parameter::("max_transaction_size").max(0) as usize + 10 * 4; - TransactionV1Builder::new_session( - casper_types::TransactionLane::InstallUpgrade, + true, Bytes::from(vec_of_largest_specimen( estimator, max_size_with_margin, cache, )), TransactionRuntime::VmCasperV1, + 0, + None, ) .with_secret_key(&LargestSpecimen::largest_specimen(estimator, cache)) .with_timestamp(LargestSpecimen::largest_specimen(estimator, cache)) diff --git a/node/src/utils/work_queue.rs b/node/src/utils/work_queue.rs index 76c5d23c9b..a04bc9b7c7 100644 --- a/node/src/utils/work_queue.rs +++ b/node/src/utils/work_queue.rs @@ -217,7 +217,7 @@ impl JobHandle { impl Drop for JobHandle { fn drop(&mut self) { - self.queue.complete_job() + self.queue.complete_job(); } } diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 54ea36e341..d016f750b4 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -76,6 +76,9 @@ max_runtime_call_stack_height = 12 minimum_delegation_amount = 500_000_000_000 # Maximum allowed delegation amount in motes maximum_delegation_amount = 1_000_000_000_000_000_000 +# Minimum bid amount allowed in motes. Withdrawing one's bid to an amount strictly less than +# the value specified will be treated as a full unbond of a validator and their associated delegators +minimum_bid_amount = 10_000_000_000_000 # Global state prune batch size (0 = this feature is off) prune_batch_size = 0 # Enables strict arguments checking when calling a contract; i.e. that all non-optional args are provided and of the correct `CLType`. @@ -96,7 +99,10 @@ signature_rewards_max_delay = 3 # # Changing this option makes sense only for private chains which dont need auctioning new validator slots. allow_auction_bids = true -# Allow peer to peer transfers between users. Setting this to false makes sense only on private chains. +# Allows transfers between accounts in the blockchain network. +# +# Setting this to false restricts normal accounts from sending tokens to other accounts, allowing transfers only to administrators. +# Changing this option makes sense only on private chains. allow_unrestricted_transfers = true # If set to false, then consensus doesn't compute rewards and always uses 0. compute_rewards = true @@ -198,29 +204,28 @@ vm_casper_v2 = false # [4] -> The maximum number of transactions the lane can contain native_mint_lane = [0, 1024, 1024, 65_000_000_000, 650] native_auction_lane = [1, 2048, 2048, 362_500_000_000, 145] -wasm_lanes = [[2, 1_048_576, 2048, 1_000_000_000_000, 1], [3, 344_064, 1024, 500_000_000_000, 3], [4, 172_032, 1024, 50_000_000_000, 7], [5, 12_288, 512, 1_500_000_000, 15]] +install_upgrade_lane = [2, 1_048_576, 2048, 1_000_000_000_000, 1] +wasm_lanes = [[3, 344_064, 1024, 500_000_000_000, 3], [4, 172_032, 1024, 50_000_000_000, 7], [5, 12_288, 512, 1_500_000_000, 15]] [transactions.deploy] # The maximum number of Motes allowed to be spent during payment. 0 means unlimited. max_payment_cost = '0' -# The maximum number of other deploys a deploy can depend on (require to have been executed before it can execute). -max_dependencies = 10 # The limit of length of serialized payment code arguments. payment_args_max_length = 1024 # The limit of length of serialized session code arguments. session_args_max_length = 1024 -[wasm] +[wasm.v1] # Amount of free memory (in 64kB pages) each contract can use for stack. max_memory = 64 # Max stack height (native WebAssembly stack limiter). max_stack_height = 500 -[wasm.storage_costs] +[storage_costs] # Gas charged per byte stored in the global state. gas_per_byte = 1_117_587 -[wasm.opcode_costs] +[wasm.v1.opcode_costs] # Bit operations multiplier. bit = 300 # Arithmetic add operations multiplier. @@ -251,9 +256,11 @@ nop = 200 current_memory = 290 # Grow memory cost, per page (64kb). grow_memory = 240_000 +# Sign extension operations cost +sign = 300 # Control flow operations multiplier. -[wasm.opcode_costs.control_flow] +[wasm.v1.opcode_costs.control_flow] block = 440 loop = 440 if = 440 @@ -267,14 +274,14 @@ call = 68_000 call_indirect = 68_000 drop = 440 -[wasm.opcode_costs.control_flow.br_table] +[wasm.v1.opcode_costs.control_flow.br_table] # Fixed cost per `br_table` opcode cost = 35_000 # Size of target labels in the `br_table` opcode will be multiplied by `size_multiplier` size_multiplier = 100 # Host function declarations are located in smart_contracts/contract/src/ext_ffi.rs -[wasm.host_function_costs] +[wasm.v1.host_function_costs] add = { cost = 5_800, arguments = [0, 0, 0, 0] } add_associated_key = { cost = 9_000, arguments = [0, 0, 0] } add_contract_version = { cost = 200, arguments = [0, 0, 0, 0, 120_000, 0, 0, 0, 0, 0] } @@ -323,6 +330,7 @@ enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } manage_message_topic = { cost = 200, arguments = [0, 30_000, 0, 0] } emit_message = { cost = 200, arguments = [0, 30_000, 0, 120_000] } cost_increase_per_message = 50 +get_block_info = { cost = 330, arguments = [0, 0] } [wasm.messages_limits] max_topic_name_size = 256 diff --git a/resources/local/config.toml b/resources/local/config.toml index 7e6d933350..f18151b387 100644 --- a/resources/local/config.toml +++ b/resources/local/config.toml @@ -320,17 +320,13 @@ allow_request_speculative_exec = false # Maximum size of a message in bytes. max_message_size_bytes = 4_194_304 -# Maximum number of in-flight requests. -# This is based on measurements captured on a local instance of the sidecar, -# it might make sense to increase this value if the sidecar is running on a separate machine. -client_request_limit = 10 - -# Number of requests that can be buffered. -client_request_buffer_size = 20 - # Maximum number of connections to the server. max_connections = 16 +# The global max rate of requests (per second) before they are limited. +# The implementation uses a sliding window algorithm. +qps_limit = 100 + # ============================================== # Configuration options for the REST HTTP server # ============================================== diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index b06eadfb9e..2d5e35cd84 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -83,6 +83,9 @@ max_runtime_call_stack_height = 12 minimum_delegation_amount = 500_000_000_000 # Maximum allowed delegation amount in motes maximum_delegation_amount = 1_000_000_000_000_000_000 +# Minimum bid amount allowed in motes. Withdrawing one's bid to an amount strictly less than +# the value specified will be treated as a full unbond of a validator and their associated delegators +minimum_bid_amount = 10_000_000_000_000 # Global state prune batch size (0 = this feature is off) prune_batch_size = 0 # Enables strict arguments checking when calling a contract; i.e. that all non-optional args are provided and of the correct `CLType`. @@ -99,9 +102,10 @@ finders_fee = [1, 5] finality_signature_proportion = [1, 2] # Lookback interval indicating which past block we are looking at to reward. signature_rewards_max_delay = 3 -# Allows peer to peer transfers between users. +# Allows transfers between accounts in the blockchain network. # -# Setting this to false makes sense only for private chains. +# Setting this to false restricts normal accounts from sending tokens to other accounts, allowing transfers only to administrators. +# Changing this option makes sense only on private chains. allow_unrestricted_transfers = true # Enables the auction entry points 'delegate' and 'add_bid'. # @@ -208,33 +212,28 @@ vm_casper_v2 = false # [4] -> The maximum number of transactions the lane can contain native_mint_lane = [0, 1024, 1024, 65_000_000_000, 650] native_auction_lane = [1, 2048, 2048, 362_500_000_000, 145] -wasm_lanes = [ - [2, 1_048_576, 2048, 1_000_000_000_000, 1], - [3, 344_064, 1024, 500_000_000_000, 3], - [4, 172_032, 1024, 50_000_000_000, 7], - [5, 12_288, 512, 1_500_000_000, 15]] +install_upgrade_lane = [2, 1_048_576, 2048, 1_000_000_000_000, 1] +wasm_lanes = [[3, 344_064, 1024, 500_000_000_000, 3], [4, 172_032, 1024, 50_000_000_000, 7], [5, 12_288, 512, 1_500_000_000, 15]] [transactions.deploy] # The maximum number of Motes allowed to be spent during payment. 0 means unlimited. max_payment_cost = '0' -# The maximum number of other deploys a deploy can depend on (require to have been executed before it can execute). -max_dependencies = 10 # The limit of length of serialized payment code arguments. payment_args_max_length = 1024 # The limit of length of serialized session code arguments. session_args_max_length = 1024 -[wasm] +[wasm.v1] # Amount of free memory (in 64kB pages) each contract can use for stack. max_memory = 64 # Max stack height (native WebAssembly stack limiter). max_stack_height = 500 -[wasm.storage_costs] +[storage_costs] # Gas charged per byte stored in the global state. gas_per_byte = 1_117_587 -[wasm.opcode_costs] +[wasm.v1.opcode_costs] # Bit operations multiplier. bit = 300 # Arithmetic add operations multiplier. @@ -265,9 +264,11 @@ nop = 200 current_memory = 290 # Grow memory cost, per page (64kb). grow_memory = 240_000 +# Sign extension operations cost +sign = 300 # Control flow operations multiplier. -[wasm.opcode_costs.control_flow] +[wasm.v1.opcode_costs.control_flow] block = 440 loop = 440 if = 440 @@ -281,14 +282,14 @@ call = 68_000 call_indirect = 68_000 drop = 440 -[wasm.opcode_costs.control_flow.br_table] +[wasm.v1.opcode_costs.control_flow.br_table] # Fixed cost per `br_table` opcode cost = 35_000 # Size of target labels in the `br_table` opcode will be multiplied by `size_multiplier` size_multiplier = 100 # Host function declarations are located in smart_contracts/contract/src/ext_ffi.rs -[wasm.host_function_costs] +[wasm.v1.host_function_costs] add = { cost = 5_800, arguments = [0, 0, 0, 0] } add_associated_key = { cost = 1_200_000, arguments = [0, 0, 0] } add_contract_version = { cost = 200, arguments = [0, 0, 0, 0, 120_000, 0, 0, 0, 0, 0] } @@ -337,6 +338,7 @@ enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } manage_message_topic = { cost = 200, arguments = [0, 30_000, 0, 0] } emit_message = { cost = 200, arguments = [0, 30_000, 0, 120_000] } cost_increase_per_message = 50 +get_block_info = { cost = 330, arguments = [0, 0] } [wasm.messages_limits] max_topic_name_size = 256 @@ -348,7 +350,7 @@ max_message_size = 1_024 [system_costs.auction_costs] get_era_validators = 10_000 read_seigniorage_recipients = 10_000 -add_bid = 2_500_000_000 +add_bid = 5_000_000_000_000 withdraw_bid = 2_500_000_000 delegate = 2_500_000_000 undelegate = 2_500_000_000 diff --git a/resources/production/config-example.toml b/resources/production/config-example.toml index d18a6d43d4..630216330b 100644 --- a/resources/production/config-example.toml +++ b/resources/production/config-example.toml @@ -320,15 +320,13 @@ allow_request_speculative_exec = false # Maximum size of a message in bytes. max_message_size_bytes = 4_194_304 -# Maximum number of in-flight requests per client. -client_request_limit = 3 - -# Number of requests that can be buffered per client. -client_request_buffer_size = 16 - # Maximum number of connections to the server. max_connections = 16 +# The global max rate of requests (per second) before they are limited. +# The implementation uses a sliding window algorithm. +qps_limit = 10 + # ============================================== # Configuration options for the REST HTTP server # ============================================== diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 96fc3c0646..8115a4739d 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -1560,25 +1560,15 @@ "type": "object", "required": [ "approvals", - "body", "hash", - "header", - "serialization_version" + "payload" ], "properties": { - "serialization_version": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, "hash": { "$ref": "#/definitions/TransactionV1Hash" }, - "header": { - "$ref": "#/definitions/TransactionV1Header" - }, - "body": { - "$ref": "#/definitions/TransactionV1Body" + "payload": { + "$ref": "#/definitions/TransactionV1Payload" }, "approvals": { "type": "array", @@ -1590,20 +1580,20 @@ }, "additionalProperties": false }, - "TransactionV1Header": { - "description": "The header portion of a TransactionV1.", + "TransactionV1Payload": { + "description": "A unit of work sent by a client to the network, which when executed can cause global state to be altered.", "type": "object", "required": [ - "body_hash", "chain_name", + "fields", "initiator_addr", "pricing_mode", "timestamp", "ttl" ], "properties": { - "chain_name": { - "type": "string" + "initiator_addr": { + "$ref": "#/definitions/InitiatorAddr" }, "timestamp": { "$ref": "#/definitions/Timestamp" @@ -1611,18 +1601,56 @@ "ttl": { "$ref": "#/definitions/TimeDiff" }, - "body_hash": { - "$ref": "#/definitions/Digest" + "chain_name": { + "type": "string" }, "pricing_mode": { "$ref": "#/definitions/PricingMode" }, - "initiator_addr": { - "$ref": "#/definitions/InitiatorAddr" + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Bytes" + } } }, "additionalProperties": false }, + "InitiatorAddr": { + "description": "The address of the initiator of a TransactionV1.", + "oneOf": [ + { + "description": "The public key of the initiator.", + "type": "object", + "required": [ + "PublicKey" + ], + "properties": { + "PublicKey": { + "$ref": "#/definitions/PublicKey" + } + }, + "additionalProperties": false + }, + { + "description": "The account hash derived from the public key of the initiator.", + "type": "object", + "required": [ + "AccountHash" + ], + "properties": { + "AccountHash": { + "$ref": "#/definitions/AccountHash" + } + }, + "additionalProperties": false + } + ] + }, + "AccountHash": { + "description": "Account hash as a formatted string.", + "type": "string" + }, "PricingMode": { "description": "Pricing mode of a Transaction.", "oneOf": [ @@ -1673,9 +1701,16 @@ "Fixed": { "type": "object", "required": [ + "additional_computation_factor", "gas_price_tolerance" ], "properties": { + "additional_computation_factor": { + "description": "User-specified additional computation factor (minimum 0). If \"0\" is provided, no additional logic is applied to the computation limit. Each value above \"0\" tells the node that it needs to treat the transaction as if it uses more gas than it's serialized size indicates. Each \"1\" will increase the \"wasm lane\" size bucket for this transaction by 1. So if the size of the transaction indicates bucket \"0\" and \"additional_computation_factor = 2\", the transaction will be treated as a \"2\".", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "gas_price_tolerance": { "description": "User-specified gas_price tolerance (minimum 1). This is interpreted to mean \"do not include this transaction in a block if the current gas price is greater than this number\"", "type": "integer", @@ -1749,440 +1784,6 @@ } ] }, - "InitiatorAddr": { - "description": "The address of the initiator of a TransactionV1.", - "oneOf": [ - { - "description": "The public key of the initiator.", - "type": "object", - "required": [ - "PublicKey" - ], - "properties": { - "PublicKey": { - "$ref": "#/definitions/PublicKey" - } - }, - "additionalProperties": false - }, - { - "description": "The account hash derived from the public key of the initiator.", - "type": "object", - "required": [ - "AccountHash" - ], - "properties": { - "AccountHash": { - "$ref": "#/definitions/AccountHash" - } - }, - "additionalProperties": false - } - ] - }, - "AccountHash": { - "description": "Account hash as a formatted string.", - "type": "string" - }, - "TransactionV1Body": { - "description": "Body of a `TransactionV1`.", - "type": "object", - "required": [ - "args", - "entry_point", - "scheduling", - "target", - "transaction_lane", - "transferred_value" - ], - "properties": { - "args": { - "$ref": "#/definitions/TransactionArgs" - }, - "target": { - "$ref": "#/definitions/TransactionTarget" - }, - "entry_point": { - "$ref": "#/definitions/TransactionEntryPoint" - }, - "transaction_lane": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, - "scheduling": { - "$ref": "#/definitions/TransactionScheduling" - }, - "transferred_value": { - "type": "integer", - "format": "uint128", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "TransactionArgs": { - "description": "Body of a `TransactionArgs`.", - "oneOf": [ - { - "description": "Named runtime arguments.", - "type": "object", - "required": [ - "Named" - ], - "properties": { - "Named": { - "$ref": "#/definitions/RuntimeArgs" - } - }, - "additionalProperties": false - }, - { - "description": "Chunked bytes.", - "type": "object", - "required": [ - "Chunked" - ], - "properties": { - "Chunked": { - "$ref": "#/definitions/Bytes" - } - }, - "additionalProperties": false - } - ] - }, - "TransactionTarget": { - "description": "Execution target of a Transaction.", - "oneOf": [ - { - "description": "The execution target is a native operation (e.g. a transfer).", - "type": "string", - "enum": [ - "Native" - ] - }, - { - "description": "The execution target is a stored entity or package.", - "type": "object", - "required": [ - "Stored" - ], - "properties": { - "Stored": { - "type": "object", - "required": [ - "id", - "runtime" - ], - "properties": { - "id": { - "description": "The identifier of the stored execution target.", - "allOf": [ - { - "$ref": "#/definitions/TransactionInvocationTarget" - } - ] - }, - "runtime": { - "description": "The execution runtime to use.", - "allOf": [ - { - "$ref": "#/definitions/TransactionRuntime" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The execution target is the included module bytes, i.e. compiled Wasm.", - "type": "object", - "required": [ - "Session" - ], - "properties": { - "Session": { - "type": "object", - "required": [ - "module_bytes", - "runtime" - ], - "properties": { - "module_bytes": { - "description": "The compiled Wasm.", - "allOf": [ - { - "$ref": "#/definitions/Bytes" - } - ] - }, - "runtime": { - "description": "The execution runtime to use.", - "allOf": [ - { - "$ref": "#/definitions/TransactionRuntime" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "TransactionInvocationTarget": { - "description": "Identifier of a `Stored` transaction target.", - "oneOf": [ - { - "description": "Hex-encoded entity address identifying the invocable entity.", - "type": "object", - "required": [ - "ByHash" - ], - "properties": { - "ByHash": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The alias identifying the invocable entity.", - "type": "object", - "required": [ - "ByName" - ], - "properties": { - "ByName": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The address and optional version identifying the package.", - "type": "object", - "required": [ - "ByPackageHash" - ], - "properties": { - "ByPackageHash": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "description": "Hex-encoded address of the package.", - "type": "string" - }, - "version": { - "description": "The package version.\n\nIf `None`, the latest enabled version is implied.", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The alias and optional version identifying the package.", - "type": "object", - "required": [ - "ByPackageName" - ], - "properties": { - "ByPackageName": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "description": "The package name.", - "type": "string" - }, - "version": { - "description": "The package version.\n\nIf `None`, the latest enabled version is implied.", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "TransactionRuntime": { - "description": "Runtime used to execute a Transaction.", - "oneOf": [ - { - "description": "The Casper Version 1 Virtual Machine.", - "type": "string", - "enum": [ - "VmCasperV1" - ] - }, - { - "description": "The Casper Version 2 Virtual Machine.", - "type": "string", - "enum": [ - "VmCasperV2" - ] - } - ] - }, - "TransactionEntryPoint": { - "description": "Entry point of a Transaction.", - "oneOf": [ - { - "description": "The default entry point name.", - "type": "string", - "enum": [ - "Call" - ] - }, - { - "description": "A non-native, arbitrary entry point.", - "type": "object", - "required": [ - "Custom" - ], - "properties": { - "Custom": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The `transfer` native entry point, used to transfer `Motes` from a source purse to a target purse.", - "type": "string", - "enum": [ - "Transfer" - ] - }, - { - "description": "The `add_bid` native entry point, used to create or top off a bid purse.", - "type": "string", - "enum": [ - "AddBid" - ] - }, - { - "description": "The `withdraw_bid` native entry point, used to decrease a stake.", - "type": "string", - "enum": [ - "WithdrawBid" - ] - }, - { - "description": "The `delegate` native entry point, used to add a new delegator or increase an existing delegator's stake.", - "type": "string", - "enum": [ - "Delegate" - ] - }, - { - "description": "The `undelegate` native entry point, used to reduce a delegator's stake or remove the delegator if the remaining stake is 0.", - "type": "string", - "enum": [ - "Undelegate" - ] - }, - { - "description": "The `redelegate` native entry point, used to reduce a delegator's stake or remove the delegator if the remaining stake is 0, and after the unbonding delay, automatically delegate to a new validator.", - "type": "string", - "enum": [ - "Redelegate" - ] - }, - { - "description": "The `activate_bid` native entry point, used to used to reactivate an inactive bid.", - "type": "string", - "enum": [ - "ActivateBid" - ] - }, - { - "description": "The `change_bid_public_key` native entry point, used to change a bid's public key.", - "type": "string", - "enum": [ - "ChangeBidPublicKey" - ] - }, - { - "description": "The `add_reservations` native entry point, used to add delegator to validator's reserve list", - "type": "string", - "enum": [ - "AddReservations" - ] - }, - { - "description": "The `cancel_reservations` native entry point, used to remove delegator from validator's reserve list", - "type": "string", - "enum": [ - "CancelReservations" - ] - } - ] - }, - "TransactionScheduling": { - "description": "Scheduling mode of a Transaction.", - "oneOf": [ - { - "description": "No special scheduling applied.", - "type": "string", - "enum": [ - "Standard" - ] - }, - { - "description": "Execution should be scheduled for the specified era.", - "type": "object", - "required": [ - "FutureEra" - ], - "properties": { - "FutureEra": { - "$ref": "#/definitions/EraId" - } - }, - "additionalProperties": false - }, - { - "description": "Execution should be scheduled for the specified timestamp or later.", - "type": "object", - "required": [ - "FutureTimestamp" - ], - "properties": { - "FutureTimestamp": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - } - ] - }, "ExecutionResult": { "description": "The versioned result of executing a single deploy.", "oneOf": [ @@ -4748,6 +4349,25 @@ } ] }, + "TransactionRuntime": { + "description": "Runtime used to execute a Transaction.", + "oneOf": [ + { + "description": "The Casper Version 1 Virtual Machine.", + "type": "string", + "enum": [ + "VmCasperV1" + ] + }, + { + "description": "The Casper Version 2 Virtual Machine.", + "type": "string", + "enum": [ + "VmCasperV2" + ] + } + ] + }, "ByteCodeHash": { "description": "The hash address of the contract wasm", "type": "string" @@ -5496,4 +5116,4 @@ ] } } -} \ No newline at end of file +} diff --git a/smart_contracts/contract/Cargo.toml b/smart_contracts/contract/Cargo.toml index 793607ebc2..80190cfe9f 100644 --- a/smart_contracts/contract/Cargo.toml +++ b/smart_contracts/contract/Cargo.toml @@ -7,7 +7,7 @@ description = "A library for developing Casper network smart contracts." readme = "README.md" documentation = "https://docs.rs/casper-contract" homepage = "https://casperlabs.io" -repository = "https://github.com/CasperLabs/casper-node/tree/master/smart_contracts/contract" +repository = "https://github.com/casper-network/casper-node/tree/master/smart_contracts/contract" license = "Apache-2.0" [dependencies] diff --git a/smart_contracts/contract/README.md b/smart_contracts/contract/README.md index fd842e4882..5542dcd661 100644 --- a/smart_contracts/contract/README.md +++ b/smart_contracts/contract/README.md @@ -4,7 +4,7 @@ [![Crates.io](https://img.shields.io/crates/v/casper-contract)](https://crates.io/crates/casper-contract) [![Documentation](https://docs.rs/casper-contract/badge.svg)](https://docs.rs/casper-contract) -[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/CasperLabs/casper-node/blob/master/LICENSE) +[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/casper-network/casper-node/blob/master/LICENSE) A library for developing Casper network smart contracts. diff --git a/smart_contracts/contract/src/contract_api/runtime.rs b/smart_contracts/contract/src/contract_api/runtime.rs index ad56d7b14a..62ea1afb7f 100644 --- a/smart_contracts/contract/src/contract_api/runtime.rs +++ b/smart_contracts/contract/src/contract_api/runtime.rs @@ -7,11 +7,11 @@ use casper_types::{ account::AccountHash, addressable_entity::NamedKeys, api_error, - bytesrepr::{self, FromBytes}, + bytesrepr::{self, FromBytes, U64_SERIALIZED_LENGTH}, contract_messages::{MessagePayload, MessageTopicOperation}, system::Caller, - AddressableEntityHash, ApiError, BlockTime, CLTyped, CLValue, EntityVersion, Key, PackageHash, - Phase, RuntimeArgs, URef, BLAKE2B_DIGEST_LENGTH, BLOCKTIME_SERIALIZED_LENGTH, + AddressableEntityHash, ApiError, BlockTime, CLTyped, CLValue, Digest, EntityVersion, Key, + PackageHash, Phase, RuntimeArgs, URef, BLAKE2B_DIGEST_LENGTH, BLOCKTIME_SERIALIZED_LENGTH, PHASE_SERIALIZED_LENGTH, }; @@ -252,6 +252,59 @@ pub fn get_blocktime() -> BlockTime { bytesrepr::deserialize(bytes).unwrap_or_revert() } +/// The default length of hashes such as account hash, state hash, hash addresses, etc. +pub const DEFAULT_HASH_LENGTH: u8 = 32; +/// Index for the block time field of block info. +pub const BLOCK_TIME_FIELD_IDX: u8 = 0; +/// Index for the block height field of block info. +pub const BLOCK_HEIGHT_FIELD_IDX: u8 = 1; +/// Index for the parent block hash field of block info. +pub const PARENT_BLOCK_HASH_FIELD_IDX: u8 = 2; +/// Index for the state hash field of block info. +pub const STATE_HASH_FIELD_IDX: u8 = 3; + +/// Returns the block height. +pub fn get_block_height() -> u64 { + let dest_non_null_ptr = contract_api::alloc_bytes(U64_SERIALIZED_LENGTH); + let bytes = unsafe { + ext_ffi::casper_get_block_info(BLOCK_HEIGHT_FIELD_IDX, dest_non_null_ptr.as_ptr()); + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + U64_SERIALIZED_LENGTH, + U64_SERIALIZED_LENGTH, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Returns the parent block hash. +pub fn get_parent_block_hash() -> Digest { + let dest_non_null_ptr = contract_api::alloc_bytes(DEFAULT_HASH_LENGTH as usize); + let bytes = unsafe { + ext_ffi::casper_get_block_info(PARENT_BLOCK_HASH_FIELD_IDX, dest_non_null_ptr.as_ptr()); + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + DEFAULT_HASH_LENGTH as usize, + DEFAULT_HASH_LENGTH as usize, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Returns the state root hash. +pub fn get_state_hash() -> Digest { + let dest_non_null_ptr = contract_api::alloc_bytes(DEFAULT_HASH_LENGTH as usize); + let bytes = unsafe { + ext_ffi::casper_get_block_info(STATE_HASH_FIELD_IDX, dest_non_null_ptr.as_ptr()); + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + DEFAULT_HASH_LENGTH as usize, + DEFAULT_HASH_LENGTH as usize, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + /// Returns the current [`Phase`]. pub fn get_phase() -> Phase { let dest_non_null_ptr = contract_api::alloc_bytes(PHASE_SERIALIZED_LENGTH); diff --git a/smart_contracts/contract/src/contract_api/storage.rs b/smart_contracts/contract/src/contract_api/storage.rs index 74d923b167..c14b8d17ef 100644 --- a/smart_contracts/contract/src/contract_api/storage.rs +++ b/smart_contracts/contract/src/contract_api/storage.rs @@ -368,7 +368,7 @@ pub fn add_contract_version( /// # Arguments /// /// * `contract_package_hash` - The hash of the contract package containing the version to be -/// disabled. +/// disabled. /// * `contract_hash` - The hash of the specific contract version to be disabled. /// /// # Errors diff --git a/smart_contracts/contract/src/ext_ffi.rs b/smart_contracts/contract/src/ext_ffi.rs index a8d5ec6df1..a294401306 100644 --- a/smart_contracts/contract/src/ext_ffi.rs +++ b/smart_contracts/contract/src/ext_ffi.rs @@ -76,7 +76,8 @@ extern "C" { /// * `total_keys`: number of authorization keys used to sign this deploy /// * `result_size`: size of the data loaded in the host pub fn casper_load_authorization_keys(total_keys: *mut usize, result_size: *mut usize) -> i32; - /// + /// This function loads a set of named keys from the host. The data will be available through + /// the host buffer and can be copied to Wasm memory through [`casper_read_host_buffer`]. pub fn casper_load_named_keys(total_keys: *mut usize, result_size: *mut usize) -> i32; /// This function causes a `Trap`, terminating the currently running module, /// but first copies the bytes from `value_ptr` to `value_ptr + value_size` to @@ -94,7 +95,7 @@ extern "C" { /// * `value_ptr`: pointer to bytes representing the value to return to the caller /// * `value_size`: size of the value (in bytes) pub fn casper_ret(value_ptr: *const u8, value_size: usize) -> !; - /// + /// Retrieves a key from the named keys by name and writes it to the output buffer. pub fn casper_get_key( name_ptr: *const u8, name_size: usize, @@ -102,16 +103,16 @@ extern "C" { output_size: usize, bytes_written_ptr: *mut usize, ) -> i32; - /// + /// This function checks if the key with the given name is present in the named keys. pub fn casper_has_key(name_ptr: *const u8, name_size: usize) -> i32; - /// + /// This function stores a key under the given name in the named keys. pub fn casper_put_key( name_ptr: *const u8, name_size: usize, key_ptr: *const u8, key_size: usize, ); - /// + /// This function removes a key with the given name from the named keys. pub fn casper_remove_key(name_ptr: *const u8, name_size: usize); /// This function causes a `Trap` which terminates the currently running /// module. Additionally, it signals that the current entire phase of @@ -198,8 +199,8 @@ extern "C" { /// /// # Arguments /// - /// * `public_key` - pointer to the bytes in wasm memory representing the - /// public key to update, presently only 32-byte public keys are supported + /// * `public_key` - pointer to the bytes in wasm memory representing the public key to update, + /// presently only 32-byte public keys are supported. /// * `weight` - the weight to assign to this public key pub fn casper_update_associated_key( account_hash_ptr: *const u8, @@ -404,13 +405,13 @@ extern "C" { /// /// * `dest_ptr` - pointer to position in wasm memory to write the result pub fn casper_get_phase(dest_ptr: *mut u8); - /// + /// Retrieves a system contract by index and writes it to the destination pointer. pub fn casper_get_system_contract( system_contract_index: u32, dest_ptr: *mut u8, dest_size: usize, ) -> i32; - /// + /// Retrieves the main purse and writes it to the destination pointer. pub fn casper_get_main_purse(dest_ptr: *mut u8); /// This function copies the contents of the current runtime buffer into the /// wasm memory, beginning at the provided offset. It is intended that this @@ -473,8 +474,8 @@ extern "C" { /// * `version_ptr` - output parameter where new version assigned by host is set /// * `entry_points_ptr` - pointer to serialized [`casper_types::EntryPoints`] /// * `entry_points_size` - size of serialized [`casper_types::EntryPoints`] - /// * `named_keys_ptr` - pointer to serialized [`casper_types::contracts::NamedKeys`] - /// * `named_keys_size` - size of serialized [`casper_types::contracts::NamedKeys`] + /// * `named_keys_ptr` - pointer to serialized [`casper_types::addressable_entity::NamedKeys`] + /// * `named_keys_size` - size of serialized [`casper_types::addressable_entity::NamedKeys`] /// * `output_ptr` - pointer to a memory where host assigned contract hash is set to /// * `output_size` - size of memory area that host can write to /// * `bytes_written_ptr` - pointer to a value where host will set a number of bytes written to @@ -843,4 +844,18 @@ extern "C" { call_stack_len_ptr: *mut usize, result_size_ptr: *mut usize, ) -> i32; + + /// This function gets the requested field at `field_idx`. It is up to + /// the caller to ensure that the correct number of bytes for the field data + /// are allocated at `dest_ptr`, otherwise data corruption in the wasm memory may occur. + /// + /// # Arguments + /// + /// * `field_idx` - what info field is requested? + /// * 0 => block time (functionally equivalent to earlier get_blocktime ffi) + /// * 1 => block height + /// * 2 => parent block hash + /// * 3 => state hash + /// * `dest_ptr` => pointer in wasm memory where to write the result + pub fn casper_get_block_info(field_idx: u8, dest_ptr: *const u8); } diff --git a/smart_contracts/contract/src/lib.rs b/smart_contracts/contract/src/lib.rs index 4389a99f12..550b8df76e 100644 --- a/smart_contracts/contract/src/lib.rs +++ b/smart_contracts/contract/src/lib.rs @@ -46,6 +46,7 @@ //! submodules. #![cfg_attr(not(test), no_std)] +#![cfg_attr(all(not(test), feature = "no-std-helpers"), allow(internal_features))] #![cfg_attr( all(not(test), feature = "no-std-helpers"), feature(alloc_error_handler, core_intrinsics, lang_items) diff --git a/smart_contracts/contracts/explorer/faucet/README.md b/smart_contracts/contracts/explorer/faucet/README.md index bea61d656c..4829df7ff3 100644 --- a/smart_contracts/contracts/explorer/faucet/README.md +++ b/smart_contracts/contracts/explorer/faucet/README.md @@ -35,7 +35,7 @@ If you try to invoke the contract before these variables are set, then you'll ge | feature | cost | |--------------------------|-------------------| -| faucet install | `160_749_054_412` | -| faucet set variables | `135_343_030` | -| faucet call by installer | `2_884_513_067` | -| faucet call by user | `2_623_210_206` | +| faucet install | `142_640_262_074` | +| faucet set variables | `134_259_210` | +| faucet call by installer | `2_879_594_967` | +| faucet call by user | `2_615_492_876` | \ No newline at end of file diff --git a/smart_contracts/contracts/test/deserialize-error/src/main.rs b/smart_contracts/contracts/test/deserialize-error/src/main.rs index d10b7cb0d0..aceb9f4a79 100644 --- a/smart_contracts/contracts/test/deserialize-error/src/main.rs +++ b/smart_contracts/contracts/test/deserialize-error/src/main.rs @@ -3,7 +3,7 @@ extern crate alloc; -use alloc::{vec, vec::Vec}; +use alloc::vec::Vec; use casper_contract::{self, contract_api::storage, unwrap_or_revert::UnwrapOrRevert}; use casper_types::{ @@ -46,7 +46,7 @@ pub fn my_call_contract(contract_hash: AddressableEntityHash, entry_point_name: let (contract_hash_ptr, contract_hash_size, _bytes1) = to_ptr(contract_hash); let entry_point_name = ToBytes::to_bytes(entry_point_name).unwrap(); - let malicious_args = vec![255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let malicious_args = [255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; { let mut bytes_written = 0usize; diff --git a/smart_contracts/contracts/test/ee-966-regression/src/main.rs b/smart_contracts/contracts/test/ee-966-regression/src/main.rs index 5c0889f714..efffe3a0da 100644 --- a/smart_contracts/contracts/test/ee-966-regression/src/main.rs +++ b/smart_contracts/contracts/test/ee-966-regression/src/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![allow(internal_features)] #![feature(lang_items)] extern crate core; @@ -45,7 +46,7 @@ pub fn memory_size() -> usize { pub fn memory_grow(new_pages: usize) { let ptr = wasm32::memory_grow(DEFAULT_MEMORY_INDEX, new_pages); - if ptr == usize::max_value() { + if ptr == usize::MAX { revert(ApiError::OutOfMemory); } } diff --git a/smart_contracts/contracts/test/get-blockinfo/Cargo.toml b/smart_contracts/contracts/test/get-blockinfo/Cargo.toml new file mode 100644 index 0000000000..1882282d98 --- /dev/null +++ b/smart_contracts/contracts/test/get-blockinfo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "get-blockinfo" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2021" + +[[bin]] +name = "get_blockinfo" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } diff --git a/smart_contracts/contracts/test/get-blockinfo/src/main.rs b/smart_contracts/contracts/test/get-blockinfo/src/main.rs new file mode 100644 index 0000000000..65d555c3b2 --- /dev/null +++ b/smart_contracts/contracts/test/get-blockinfo/src/main.rs @@ -0,0 +1,63 @@ +#![no_std] +#![no_main] + +use casper_contract::{ + contract_api::{runtime, runtime::revert}, + unwrap_or_revert::UnwrapOrRevert, +}; +use casper_types::{ + bytesrepr::{Bytes, FromBytes}, + ApiError, BlockTime, Digest, +}; + +const ARG_FIELD_IDX: &str = "field_idx"; +const FIELD_IDX_BLOCK_TIME: u8 = 0; +const FIELD_IDX_BLOCK_HEIGHT: u8 = 1; +const FIELD_IDX_PARENT_BLOCK_HASH: u8 = 2; +const FIELD_IDX_STATE_HASH: u8 = 3; + +const CURRENT_UBOUND: u8 = FIELD_IDX_STATE_HASH; +const ARG_KNOWN_BLOCK_TIME: &str = "known_block_time"; +const ARG_KNOWN_BLOCK_HEIGHT: &str = "known_block_height"; +const ARG_KNOWN_BLOCK_PARENT_HASH: &str = "known_block_parent_hash"; +const ARG_KNOWN_STATE_HASH: &str = "known_state_hash"; + +#[no_mangle] +pub extern "C" fn call() { + let field_idx: u8 = runtime::get_named_arg(ARG_FIELD_IDX); + if field_idx > CURRENT_UBOUND { + revert(ApiError::Unhandled); + } + if field_idx == FIELD_IDX_BLOCK_TIME { + let expected = BlockTime::new(runtime::get_named_arg(ARG_KNOWN_BLOCK_TIME)); + let actual: BlockTime = runtime::get_blocktime(); + if expected != actual { + revert(ApiError::User(field_idx as u16)); + } + } + if field_idx == FIELD_IDX_BLOCK_HEIGHT { + let expected: u64 = runtime::get_named_arg(ARG_KNOWN_BLOCK_HEIGHT); + let actual = runtime::get_block_height(); + if expected != actual { + revert(ApiError::User(field_idx as u16)); + } + } + if field_idx == FIELD_IDX_PARENT_BLOCK_HASH { + let bytes: Bytes = runtime::get_named_arg(ARG_KNOWN_BLOCK_PARENT_HASH); + let (expected, _rem) = Digest::from_bytes(bytes.inner_bytes()) + .unwrap_or_revert_with(ApiError::User(CURRENT_UBOUND as u16 + 1)); + let actual = runtime::get_parent_block_hash(); + if expected != actual { + revert(ApiError::User(field_idx as u16)); + } + } + if field_idx == FIELD_IDX_STATE_HASH { + let bytes: Bytes = runtime::get_named_arg(ARG_KNOWN_STATE_HASH); + let (expected, _rem) = Digest::from_bytes(bytes.inner_bytes()) + .unwrap_or_revert_with(ApiError::User(CURRENT_UBOUND as u16 + 2)); + let actual = runtime::get_state_hash(); + if expected != actual { + revert(ApiError::User(field_idx as u16)); + } + } +} diff --git a/smart_contracts/contracts/test/gh-4898-regression/Cargo.toml b/smart_contracts/contracts/test/gh-4898-regression/Cargo.toml new file mode 100644 index 0000000000..1489d13119 --- /dev/null +++ b/smart_contracts/contracts/test/gh-4898-regression/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "gh-4898-regression" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "gh_4898_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } \ No newline at end of file diff --git a/smart_contracts/contracts/test/gh-4898-regression/src/main.rs b/smart_contracts/contracts/test/gh-4898-regression/src/main.rs new file mode 100644 index 0000000000..a3610e6e64 --- /dev/null +++ b/smart_contracts/contracts/test/gh-4898-regression/src/main.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use alloc::string::String; + +use casper_contract::contract_api::runtime; +use casper_types::Key; + +const ARG_DATA: &str = "data"; + +#[no_mangle] +fn is_key(key_str: &str) -> bool { + Key::from_formatted_str(key_str).is_ok() +} + +#[no_mangle] +pub extern "C" fn call() { + let data: String = runtime::get_named_arg(ARG_DATA); + + assert!(is_key(&data), "Data should be a key"); +} diff --git a/smart_contracts/contracts/test/storage-costs/src/main.rs b/smart_contracts/contracts/test/storage-costs/src/main.rs index 7622190180..03a4b8dbd3 100644 --- a/smart_contracts/contracts/test/storage-costs/src/main.rs +++ b/smart_contracts/contracts/test/storage-costs/src/main.rs @@ -25,7 +25,7 @@ const WRITE_LARGE_VALUE: &[u8] = b"111111111111111111111111111111111111111111111 const HASH_KEY_NAME: &str = "contract_package"; const CONTRACT_KEY_NAME: &str = "contract"; const ADD_SMALL_VALUE: u64 = 1; -const ADD_LARGE_VALUE: u64 = u64::max_value(); +const ADD_LARGE_VALUE: u64 = u64::MAX; const NEW_UREF_FUNCTION: &str = "new_uref_function"; const PUT_KEY_FUNCTION: &str = "put_key_function"; const REMOVE_KEY_FUNCTION: &str = "remove_key_function"; diff --git a/smart_contracts/contracts/vm2/vm2-harness/src/contracts/harness.rs b/smart_contracts/contracts/vm2/vm2-harness/src/contracts/harness.rs index 29b56dc27c..59185f541e 100644 --- a/smart_contracts/contracts/vm2/vm2-harness/src/contracts/harness.rs +++ b/smart_contracts/contracts/vm2/vm2-harness/src/contracts/harness.rs @@ -12,7 +12,12 @@ use casper_sdk::{ }, error::Error, keyspace::Keyspace, - }, collections::Map, host::{self, Entity}, log, revert, types::CallError, ContractHandle + }, + collections::Map, + host::{self, Entity}, + log, revert, + types::CallError, + ContractHandle, }; use crate::traits::{DepositExt, DepositRef}; diff --git a/smart_contracts/contracts/vm2/vm2-harness/src/contracts/token_owner.rs b/smart_contracts/contracts/vm2/vm2-harness/src/contracts/token_owner.rs index 1ca107aca0..c383ff65a2 100644 --- a/smart_contracts/contracts/vm2/vm2-harness/src/contracts/token_owner.rs +++ b/smart_contracts/contracts/vm2/vm2-harness/src/contracts/token_owner.rs @@ -138,7 +138,11 @@ impl TokenOwnerContract { #[casper(path = crate::traits)] impl Deposit for TokenOwnerContract { fn deposit(&mut self) { - log!("Received deposit with value = {} current handler is {:?}", host::get_value(), self.fallback_handler); + log!( + "Received deposit with value = {} current handler is {:?}", + host::get_value(), + self.fallback_handler + ); match std::mem::replace(&mut self.fallback_handler, FallbackHandler::AcceptTokens) { FallbackHandler::AcceptTokens => { let value = host::get_value(); diff --git a/smart_contracts/rust-toolchain b/smart_contracts/rust-toolchain index f9e5e5e397..63e221c849 100644 --- a/smart_contracts/rust-toolchain +++ b/smart_contracts/rust-toolchain @@ -1 +1 @@ -nightly-2023-03-25 +nightly-2024-07-31 diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 2402d43a5b..9c35247556 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -6,7 +6,7 @@ description = "Storage for a node on the Casper network." readme = "README.md" documentation = "https://docs.rs/casper-storage" homepage = "https://casperlabs.io" -repository = "https://github.com/CasperLabs/casper-node/tree/master/storage" +repository = "https://github.com/casper-network/casper-node/tree/master/storage" license = "Apache-2.0" [dependencies] diff --git a/storage/README.md b/storage/README.md index cbcd18a26a..0b7acf4c96 100644 --- a/storage/README.md +++ b/storage/README.md @@ -5,7 +5,7 @@ [![Build Status](https://drone-auto-casper-network.casperlabs.io/api/badges/casper-network/casper-node/status.svg?branch=dev)](http://drone-auto-casper-network.casperlabs.io/casper-network/casper-node) [![Crates.io](https://img.shields.io/crates/v/casper-hashing)](https://crates.io/crates/casper-storage) [![Documentation](https://docs.rs/casper-hashing/badge.svg)](https://docs.rs/casper-storage) -[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/CasperLabs/casper-node/blob/master/LICENSE) +[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/casper-network/casper-node/blob/master/LICENSE) A library providing storage functionality for Casper nodes. diff --git a/storage/src/block_store/block_provider.rs b/storage/src/block_store/block_provider.rs index 9625a8c216..d3290360c0 100644 --- a/storage/src/block_store/block_provider.rs +++ b/storage/src/block_store/block_provider.rs @@ -2,17 +2,22 @@ use super::error::BlockStoreError; /// A block store that supports read/write operations consistently. pub trait BlockStoreProvider { + /// Reader alias. type Reader<'a>: BlockStoreTransaction where Self: 'a; + /// ReaderWriter alias. type ReaderWriter<'a>: BlockStoreTransaction where Self: 'a; + /// Check out read only handle. fn checkout_ro(&self) -> Result, BlockStoreError>; + /// Check out read write handle. fn checkout_rw(&mut self) -> Result, BlockStoreError>; } +/// Block store transaction. pub trait BlockStoreTransaction { /// Commit changes to the block store. fn commit(self) -> Result<(), BlockStoreError>; @@ -21,12 +26,18 @@ pub trait BlockStoreTransaction { fn rollback(self); } +/// Data reader definition. pub trait DataReader { + /// Read item at key. fn read(&self, key: K) -> Result, BlockStoreError>; + /// Returns true if item exists at key, else false. fn exists(&self, key: K) -> Result; } +/// Data write definition. pub trait DataWriter { + /// Write item to store and return key. fn write(&mut self, data: &T) -> Result; + /// Delete item at key from store. fn delete(&mut self, key: K) -> Result<(), BlockStoreError>; } diff --git a/storage/src/block_store/error.rs b/storage/src/block_store/error.rs index 0e7ff4b330..976357e6d5 100644 --- a/storage/src/block_store/error.rs +++ b/storage/src/block_store/error.rs @@ -2,6 +2,7 @@ use casper_types::{BlockHash, EraId, TransactionHash}; use std::fmt::Debug; use thiserror::Error; +/// Block store error. #[derive(Debug, Error)] pub enum BlockStoreError { /// Found a duplicate block entry of the specified height. diff --git a/storage/src/block_store/lmdb/indexed_lmdb_block_store.rs b/storage/src/block_store/lmdb/indexed_lmdb_block_store.rs index dae84469dd..464e74b634 100644 --- a/storage/src/block_store/lmdb/indexed_lmdb_block_store.rs +++ b/storage/src/block_store/lmdb/indexed_lmdb_block_store.rs @@ -27,6 +27,7 @@ use casper_types::{ BlockSignatures, Digest, EraId, ProtocolVersion, Transaction, TransactionHash, Transfer, }; +/// Indexed lmdb block store. #[derive(DataSize, Debug)] pub struct IndexedLmdbBlockStore { /// Block store @@ -124,6 +125,7 @@ impl IndexedLmdbBlockStore { Ok(()) } + /// Ctor. pub fn new( block_store: LmdbBlockStore, hard_reset_to_start_of_era: Option, diff --git a/storage/src/block_store/lmdb/lmdb_block_store.rs b/storage/src/block_store/lmdb/lmdb_block_store.rs index baacf426e0..5abbb96e4f 100644 --- a/storage/src/block_store/lmdb/lmdb_block_store.rs +++ b/storage/src/block_store/lmdb/lmdb_block_store.rs @@ -51,6 +51,7 @@ const OS_FLAGS: EnvironmentFlags = EnvironmentFlags::WRITE_MAP; #[cfg(target_os = "macos")] const OS_FLAGS: EnvironmentFlags = EnvironmentFlags::empty(); +/// Lmdb block store. #[derive(DataSize, Debug)] pub struct LmdbBlockStore { /// Storage location. @@ -82,6 +83,7 @@ pub struct LmdbBlockStore { } impl LmdbBlockStore { + /// Ctor. pub fn new(root_path: &Path, total_size: usize) -> Result { // Create the environment and databases. let env = new_environment(total_size, root_path)?; @@ -127,6 +129,7 @@ impl LmdbBlockStore { }) } + /// Write finality signatures. pub fn write_finality_signatures( &self, txn: &mut RwTransaction, diff --git a/storage/src/block_store/mod.rs b/storage/src/block_store/mod.rs index 657f924aff..baa9ae7d50 100644 --- a/storage/src/block_store/mod.rs +++ b/storage/src/block_store/mod.rs @@ -1,6 +1,8 @@ mod block_provider; mod error; +/// Block store lmdb logic. pub mod lmdb; +/// Block store types. pub mod types; pub use block_provider::{BlockStoreProvider, BlockStoreTransaction, DataReader, DataWriter}; diff --git a/storage/src/block_store/types/approvals_hashes.rs b/storage/src/block_store/types/approvals_hashes.rs index ccbf0543a1..3398dfcf7f 100644 --- a/storage/src/block_store/types/approvals_hashes.rs +++ b/storage/src/block_store/types/approvals_hashes.rs @@ -39,6 +39,7 @@ pub struct ApprovalsHashes { } impl ApprovalsHashes { + /// Ctor. pub fn new( block_hash: BlockHash, approvals_hashes: Vec, @@ -51,6 +52,7 @@ impl ApprovalsHashes { } } + /// Verify block. pub fn verify(&self, block: &Block) -> Result<(), ApprovalsHashesValidationError> { if *self.merkle_proof_approvals.key() != Key::ChecksumRegistry { return Err(ApprovalsHashesValidationError::InvalidKeyType); @@ -92,6 +94,7 @@ impl ApprovalsHashes { Ok(()) } + /// Deploy ids. pub(crate) fn deploy_ids( &self, v1_block: &BlockV1, @@ -106,6 +109,7 @@ impl ApprovalsHashes { .collect()) } + /// Transaction ids. pub fn transaction_ids( &self, v2_block: &BlockV2, @@ -119,10 +123,12 @@ impl ApprovalsHashes { .collect() } + /// Block hash. pub fn block_hash(&self) -> &BlockHash { &self.block_hash } + /// Approvals hashes. pub fn approvals_hashes(&self) -> Vec { self.approvals_hashes.clone() } @@ -197,7 +203,9 @@ pub enum ApprovalsHashesValidationError { /// The state root hash implied by the Merkle proof doesn't match that in the block. #[error("state root hash implied by the Merkle proof doesn't match that in the block")] StateRootHashMismatch { + /// Proof state root hash. proof_state_root_hash: Digest, + /// Block state root hash. block_state_root_hash: Digest, }, @@ -212,10 +220,13 @@ pub enum ApprovalsHashesValidationError { /// The approvals checksum provided doesn't match one calculated from the approvals. #[error("provided approvals checksum doesn't match one calculated from the approvals")] ApprovalsChecksumMismatch { + /// Computed approvals checksum. computed_approvals_checksum: Digest, + /// Value in proof. value_in_proof: Digest, }, + /// Variant mismatch. #[error("mismatch in variants: {0:?}")] #[data_size(skip)] VariantMismatch(Box), diff --git a/storage/src/block_store/types/block_hash_height_and_era.rs b/storage/src/block_store/types/block_hash_height_and_era.rs index 10a9591c42..4ef2ab7f10 100644 --- a/storage/src/block_store/types/block_hash_height_and_era.rs +++ b/storage/src/block_store/types/block_hash_height_and_era.rs @@ -6,10 +6,14 @@ use rand::Rng; use casper_types::testing::TestRng; use casper_types::{BlockHash, BlockHashAndHeight, EraId}; +/// Aggregates block identifying information. #[derive(Clone, Copy, Debug, DataSize)] pub struct BlockHashHeightAndEra { + /// Block hash. pub block_hash: BlockHash, + /// Block height. pub block_height: u64, + /// EraId pub era_id: EraId, } diff --git a/storage/src/block_store/types/mod.rs b/storage/src/block_store/types/mod.rs index ceb012c4cb..96fbc90ab6 100644 --- a/storage/src/block_store/types/mod.rs +++ b/storage/src/block_store/types/mod.rs @@ -18,39 +18,62 @@ pub(crate) use approvals_hashes::LegacyApprovalsHashes; pub(crate) use deploy_metadata_v1::DeployMetadataV1; pub(in crate::block_store) use transfers::Transfers; +/// Exeuction results. pub type ExecutionResults = HashMap; +/// Transaction finalized approvals. pub struct TransactionFinalizedApprovals { + /// Transaction hash. pub transaction_hash: TransactionHash, + /// Finalized approvals. pub finalized_approvals: BTreeSet, } +/// Block execution results. pub struct BlockExecutionResults { + /// Block info. pub block_info: BlockHashHeightAndEra, + /// Execution results. pub exec_results: ExecutionResults, } +/// Block transfers. pub struct BlockTransfers { + /// Block hash. pub block_hash: BlockHash, + /// Transfers. pub transfers: Vec, } +/// State store. pub struct StateStore { + /// Key. pub key: Cow<'static, [u8]>, + /// Value. pub value: Vec, } +/// State store key. pub struct StateStoreKey(pub(super) Cow<'static, [u8]>); impl StateStoreKey { + /// Ctor. pub fn new(key: Cow<'static, [u8]>) -> Self { StateStoreKey(key) } } +/// Block tip anchor. pub struct Tip; + +/// Latest switch block anchor. pub struct LatestSwitchBlock; +/// Block height. pub type BlockHeight = u64; + +/// Switch block header alias. pub type SwitchBlockHeader = BlockHeader; + +/// Switch block alias. pub type SwitchBlock = Block; diff --git a/storage/src/data_access_layer.rs b/storage/src/data_access_layer.rs index 9d3e82eeed..719b4393b5 100644 --- a/storage/src/data_access_layer.rs +++ b/storage/src/data_access_layer.rs @@ -2,36 +2,49 @@ use crate::global_state::{ error::Error as GlobalStateError, state::{CommitProvider, StateProvider}, }; -use casper_types::{execution::Effects, Digest, EraId}; +use casper_types::{execution::Effects, Digest}; use crate::tracking_copy::TrackingCopy; mod addressable_entity; +/// Auction provider. pub mod auction; +/// Balance provider. pub mod balance; mod balance_hold; +/// Bids provider. pub mod bids; mod block_global; +/// Block rewards provider. pub mod block_rewards; mod entry_points; +/// Era validators provider. pub mod era_validators; mod execution_results_checksum; mod fee; mod flush; +/// Forced undelegate provider. pub mod forced_undelegate; mod genesis; +/// Handle fee provider. pub mod handle_fee; mod handle_refund; mod key_prefix; +/// Mint provider. pub mod mint; +/// Prefixed values provider. pub mod prefixed_values; mod protocol_upgrade; +/// Prune provider. pub mod prune; +/// Query provider. pub mod query; mod round_seigniorage; mod seigniorage_recipients; +/// Step provider. pub mod step; mod system_entity_registry; +/// Tagged values provider. pub mod tagged_values; mod total_supply; mod trie; @@ -75,37 +88,30 @@ pub use system_entity_registry::{ pub use total_supply::{TotalSupplyRequest, TotalSupplyResult}; pub use trie::{PutTrieRequest, PutTrieResult, TrieElement, TrieRequest, TrieResult}; -pub struct Block { - _era_id: EraId, -} - -pub trait BlockProvider { - type Error; - - fn read_block_by_height(&self, _height: usize) -> Result, Self::Error> { - // TODO: We need to implement this - todo!() - } -} - +/// Anchor struct for block store functionality. #[derive(Default, Copy, Clone)] pub struct BlockStore(()); impl BlockStore { + /// Ctor. pub fn new() -> Self { BlockStore(()) } } -// We're currently putting it here, but in future it needs to move to its own crate. +/// Data access layer. #[derive(Copy, Clone)] pub struct DataAccessLayer { + /// Block store instance. pub block_store: BlockStore, + /// Memoized state. pub state: S, + /// Max query depth. pub max_query_depth: u64, } impl DataAccessLayer { + /// Returns reference to current state of the data access layer. pub fn state(&self) -> &S { &self.state } diff --git a/storage/src/data_access_layer/addressable_entity.rs b/storage/src/data_access_layer/addressable_entity.rs index 24147882f0..724251296c 100644 --- a/storage/src/data_access_layer/addressable_entity.rs +++ b/storage/src/data_access_layer/addressable_entity.rs @@ -37,6 +37,7 @@ pub enum AddressableEntityResult { /// An addressable entity. entity: AddressableEntity, }, + /// Failure. Failure(TrackingCopyError), } diff --git a/storage/src/data_access_layer/auction.rs b/storage/src/data_access_layer/auction.rs index 24818fa7bf..0d318794a0 100644 --- a/storage/src/data_access_layer/auction.rs +++ b/storage/src/data_access_layer/auction.rs @@ -36,46 +36,83 @@ pub enum AuctionMethodError { }, } +/// Auction method to interact with. #[derive(Debug, Clone, PartialEq, Eq)] pub enum AuctionMethod { + /// Activate bid. ActivateBid { + /// Validator public key (must match initiating address). validator: PublicKey, }, + /// Add bid. AddBid { + /// Validator public key (must match initiating address). public_key: PublicKey, + /// Delegation rate for this validator bid. delegation_rate: DelegationRate, + /// Bid amount. amount: U512, + /// Minimum delegation amount for this validator bid. minimum_delegation_amount: u64, + /// Maximum delegation amount for this validator bid. maximum_delegation_amount: u64, + /// The minimum bid amount a validator must submit to have + /// their bid considered as valid. + minimum_bid_amount: u64, }, + /// Withdraw bid. WithdrawBid { + /// Validator public key. public_key: PublicKey, + /// Bid amount. amount: U512, + /// The minimum bid amount a validator, if a validator reduces their stake + /// below this amount, then it is treated as a complete withdrawal. + minimum_bid_amount: u64, }, + /// Delegate to validator. Delegate { + /// Delegator public key. delegator: PublicKey, + /// Validator public key. validator: PublicKey, + /// Delegation amount. amount: U512, + /// Max delegators per validator. max_delegators_per_validator: u32, }, + /// Undelegate from validator. Undelegate { + /// Delegator public key. delegator: PublicKey, + /// Validator public key. validator: PublicKey, + /// Undelegation amount. amount: U512, }, + /// Undelegate from validator and attempt delegation to new validator after unbonding delay + /// elapses. Redelegate { + /// Delegator public key. delegator: PublicKey, + /// Validator public key. validator: PublicKey, + /// Redelegation amount. amount: U512, + /// New validator public key. new_validator: PublicKey, }, + /// Change the public key associated with a validator to a different public key. ChangeBidPublicKey { + /// Current public key. public_key: PublicKey, + /// New public key. new_public_key: PublicKey, }, } impl AuctionMethod { + /// Form auction method from parts. pub fn from_parts( entry_point: TransactionEntryPoint, runtime_args: &RuntimeArgs, @@ -92,8 +129,11 @@ impl AuctionMethod { runtime_args, chainspec.core_config.minimum_delegation_amount, chainspec.core_config.maximum_delegation_amount, + chainspec.core_config.minimum_bid_amount, ), - TransactionEntryPoint::WithdrawBid => Self::new_withdraw_bid(runtime_args), + TransactionEntryPoint::WithdrawBid => { + Self::new_withdraw_bid(runtime_args, chainspec.core_config.minimum_bid_amount) + } TransactionEntryPoint::Delegate => Self::new_delegate( runtime_args, chainspec.core_config.max_delegators_per_validator, @@ -118,6 +158,7 @@ impl AuctionMethod { runtime_args: &RuntimeArgs, global_minimum_delegation: u64, global_maximum_delegation: u64, + global_minimum_bid_amount: u64, ) -> Result { let public_key = Self::get_named_argument(runtime_args, auction::ARG_PUBLIC_KEY)?; let delegation_rate = Self::get_named_argument(runtime_args, auction::ARG_DELEGATION_RATE)?; @@ -135,13 +176,21 @@ impl AuctionMethod { amount, minimum_delegation_amount, maximum_delegation_amount, + minimum_bid_amount: global_minimum_bid_amount, }) } - fn new_withdraw_bid(runtime_args: &RuntimeArgs) -> Result { + fn new_withdraw_bid( + runtime_args: &RuntimeArgs, + global_minimum_bid_amount: u64, + ) -> Result { let public_key = Self::get_named_argument(runtime_args, auction::ARG_PUBLIC_KEY)?; let amount = Self::get_named_argument(runtime_args, auction::ARG_AMOUNT)?; - Ok(Self::WithdrawBid { public_key, amount }) + Ok(Self::WithdrawBid { + public_key, + amount, + minimum_bid_amount: global_minimum_bid_amount, + }) } fn new_delegate( @@ -210,6 +259,7 @@ impl AuctionMethod { } } +/// Bidding request. #[derive(Debug, Clone, PartialEq, Eq)] pub struct BiddingRequest { /// The runtime config. @@ -251,48 +301,59 @@ impl BiddingRequest { } } + /// Returns the config. pub fn config(&self) -> &NativeRuntimeConfig { &self.config } + /// Returns the state hash. pub fn state_hash(&self) -> Digest { self.state_hash } + /// Returns the protocol version. pub fn protocol_version(&self) -> ProtocolVersion { self.protocol_version } + /// Returns the auction method. pub fn auction_method(&self) -> &AuctionMethod { &self.auction_method } + /// Returns the transaction hash. pub fn transaction_hash(&self) -> TransactionHash { self.transaction_hash } + /// Returns the initiator. pub fn initiator(&self) -> &InitiatorAddr { &self.initiator } + /// Returns the authorization keys. pub fn authorization_keys(&self) -> &BTreeSet { &self.authorization_keys } } +/// Auction method ret. #[derive(Debug, Clone)] pub enum AuctionMethodRet { + /// Unit. Unit, + /// Updated amount. UpdatedAmount(U512), } +/// Bidding result. #[derive(Debug)] pub enum BiddingResult { /// Invalid state root hash. RootNotFound, /// Bidding request succeeded Success { - // The ret value, if any. + /// The ret value, if any. ret: AuctionMethodRet, /// Effects of bidding interaction. effects: Effects, diff --git a/storage/src/data_access_layer/balance.rs b/storage/src/data_access_layer/balance.rs index 63c5041260..4ca835fe44 100644 --- a/storage/src/data_access_layer/balance.rs +++ b/storage/src/data_access_layer/balance.rs @@ -48,18 +48,30 @@ pub enum ProofHandling { /// Represents a way to make a balance inquiry. #[derive(Debug, Clone, PartialEq, Eq)] pub enum BalanceIdentifier { + /// Use system refund purse (held by handle payment system contract). Refund, + /// Use system payment purse (held by handle payment system contract). Payment, + /// Use system accumulate purse (held by handle payment system contract). Accumulate, + /// Use purse associated to specified uref. Purse(URef), + /// Use main purse of entity derived from public key. Public(PublicKey), + /// Use main purse of entity from account hash. Account(AccountHash), + /// Use main purse of entity. Entity(EntityAddr), + /// Use purse at Key::Purse(URefAddr). Internal(URefAddr), + /// Penalized account identifier. PenalizedAccount(AccountHash), + /// Penalized payment identifier. + PenalizedPayment, } impl BalanceIdentifier { + /// Returns underlying uref addr from balance identifier, if any. pub fn as_purse_addr(&self) -> Option { match self { BalanceIdentifier::Internal(addr) => Some(*addr), @@ -67,6 +79,7 @@ impl BalanceIdentifier { BalanceIdentifier::Public(_) | BalanceIdentifier::Account(_) | BalanceIdentifier::PenalizedAccount(_) + | BalanceIdentifier::PenalizedPayment | BalanceIdentifier::Entity(_) | BalanceIdentifier::Refund | BalanceIdentifier::Payment @@ -109,7 +122,7 @@ impl BalanceIdentifier { BalanceIdentifier::Refund => { self.get_system_purse(tc, HANDLE_PAYMENT, REFUND_PURSE_KEY)? } - BalanceIdentifier::Payment => { + BalanceIdentifier::Payment | BalanceIdentifier::PenalizedPayment => { self.get_system_purse(tc, HANDLE_PAYMENT, PAYMENT_PURSE_KEY)? } BalanceIdentifier::Accumulate => { @@ -152,9 +165,10 @@ impl BalanceIdentifier { /// Is this balance identifier for penalty? pub fn is_penalty(&self) -> bool { - // currently there is one variant of this kind, but more may be added later to - // support more use cases. - matches!(self, BalanceIdentifier::PenalizedAccount(_)) + matches!( + self, + BalanceIdentifier::Payment | BalanceIdentifier::PenalizedPayment + ) } } @@ -173,6 +187,7 @@ impl From for BalanceIdentifier { } } +/// Processing hold balance handling. #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub struct ProcessingHoldBalanceHandling {} @@ -204,6 +219,7 @@ impl From<(HoldBalanceHandling, u64)> for ProcessingHoldBalanceHandling { } } +/// Gas hold balance handling. #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub struct GasHoldBalanceHandling { handling: HoldBalanceHandling, @@ -389,6 +405,7 @@ impl BalanceRequest { } } +/// Available balance checker. pub trait AvailableBalanceChecker { /// Calculate and return available balance. fn available_balance( @@ -433,6 +450,7 @@ pub trait AvailableBalanceChecker { } } + /// Calculates amortization. fn amortization( &self, hold_kind: BalanceHoldAddrTag, @@ -559,12 +577,15 @@ impl AvailableBalanceChecker for BTreeMap { } } +/// Proofs result. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ProofsResult { + /// Not requested. NotRequested { /// Any time-relevant active holds on the balance, without proofs. balance_holds: BTreeMap, }, + /// Proofs. Proofs { /// A proof that the given value is present in the Merkle trie. total_balance_proof: Box>, @@ -645,6 +666,7 @@ impl ProofsResult { } } +/// Balance failure. #[derive(Debug, Clone)] pub enum BalanceFailure { /// Failed to calculate amortization (checked multiplication). @@ -688,6 +710,7 @@ pub enum BalanceResult { /// Proofs result. proofs_result: ProofsResult, }, + /// Failure. Failure(TrackingCopyError), } @@ -743,6 +766,14 @@ impl BalanceResult { BalanceResult::Success { .. } => true, } } + + /// Tracking copy error, if any. + pub fn error(&self) -> Option<&TrackingCopyError> { + match self { + BalanceResult::RootNotFound | BalanceResult::Success { .. } => None, + BalanceResult::Failure(err) => Some(err), + } + } } impl From for BalanceResult { diff --git a/storage/src/data_access_layer/balance_hold.rs b/storage/src/data_access_layer/balance_hold.rs index f97e65d382..f25f6a6abe 100644 --- a/storage/src/data_access_layer/balance_hold.rs +++ b/storage/src/data_access_layer/balance_hold.rs @@ -11,10 +11,13 @@ use casper_types::{ use std::fmt::{Display, Formatter}; use thiserror::Error; +/// Balance hold kind. #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub enum BalanceHoldKind { + /// All balance holds. #[default] All, + /// Selection of a specific kind of balance. Tag(BalanceHoldAddrTag), } @@ -28,14 +31,21 @@ impl BalanceHoldKind { } } +/// Balance hold mode. #[derive(Debug, Clone, PartialEq, Eq)] pub enum BalanceHoldMode { + /// Balance hold request. Hold { + /// Balance identifier. identifier: BalanceIdentifier, + /// Hold amount. hold_amount: U512, + /// How should insufficient balance be handled. insufficient_handling: InsufficientBalanceHandling, }, + /// Clear balance holds. Clear { + /// Identifier of balance to be cleared of holds. identifier: BalanceIdentifier, }, } @@ -60,6 +70,7 @@ pub enum InsufficientBalanceHandling { Noop, } +/// Balance hold request. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct BalanceHoldRequest { state_hash: Digest, @@ -156,10 +167,18 @@ impl BalanceHoldRequest { #[derive(Error, Debug, Clone)] #[non_exhaustive] pub enum BalanceHoldError { + /// Tracking copy error. TrackingCopy(TrackingCopyError), + /// Balance error. Balance(BalanceFailure), - InsufficientBalance { remaining_balance: U512 }, + /// Insufficient balance error. + InsufficientBalance { + /// Remaining balance error. + remaining_balance: U512, + }, + /// Unexpected wildcard variant error. UnexpectedWildcardVariant, // programmer error, + /// Unexpected hold value error. UnexpectedHoldValue(StoredValue), } @@ -225,6 +244,7 @@ pub enum BalanceHoldResult { } impl BalanceHoldResult { + /// Success ctor. pub fn success( holds: Option>, total_balance: U512, @@ -317,6 +337,7 @@ impl BalanceHoldResult { } } + /// Error message. pub fn error_message(&self) -> String { match self { BalanceHoldResult::Success { hold, held, .. } => { diff --git a/storage/src/data_access_layer/bids.rs b/storage/src/data_access_layer/bids.rs index befeebd4ae..88b5da03f2 100644 --- a/storage/src/data_access_layer/bids.rs +++ b/storage/src/data_access_layer/bids.rs @@ -31,6 +31,7 @@ pub enum BidsResult { /// Current bids. bids: Vec, }, + /// Failure. Failure(TrackingCopyError), } diff --git a/storage/src/data_access_layer/block_global.rs b/storage/src/data_access_layer/block_global.rs index 0d2d754cd5..38b54160d1 100644 --- a/storage/src/data_access_layer/block_global.rs +++ b/storage/src/data_access_layer/block_global.rs @@ -3,9 +3,12 @@ use casper_types::{execution::Effects, BlockTime, Digest, ProtocolVersion}; use std::fmt::{Display, Formatter}; use thiserror::Error; +/// Block global kind. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum BlockGlobalKind { + /// Block time. BlockTime(BlockTime), + /// Message count. MessageCount(u64), } @@ -15,6 +18,7 @@ impl Default for BlockGlobalKind { } } +/// Block global request. #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub struct BlockGlobalRequest { state_hash: Digest, @@ -53,6 +57,7 @@ impl BlockGlobalRequest { } } +/// Block global result. #[derive(Error, Debug, Clone)] pub enum BlockGlobalResult { /// Returned if a passed state root hash is not found. diff --git a/storage/src/data_access_layer/block_rewards.rs b/storage/src/data_access_layer/block_rewards.rs index 3d0676de5a..e4c2d9fd5a 100644 --- a/storage/src/data_access_layer/block_rewards.rs +++ b/storage/src/data_access_layer/block_rewards.rs @@ -12,6 +12,7 @@ use crate::{ tracking_copy::TrackingCopyError, }; +/// Block rewards request. #[derive(Debug, Clone, PartialEq, Eq)] pub struct BlockRewardsRequest { config: Config, @@ -22,6 +23,7 @@ pub struct BlockRewardsRequest { } impl BlockRewardsRequest { + /// Ctor. pub fn new( config: Config, state_hash: Digest, @@ -64,24 +66,34 @@ impl BlockRewardsRequest { } } +/// Block rewards error. #[derive(Clone, Error, Debug)] pub enum BlockRewardsError { + /// Undistributed rewards error. #[error("Undistributed rewards")] UndistributedRewards, + /// Tracking copy error. #[error(transparent)] TrackingCopy(TrackingCopyError), + /// Registry entry not found error. #[error("Registry entry not found: {0}")] RegistryEntryNotFound(String), + /// Transfer error. #[error(transparent)] Transfer(TransferError), + /// Auction error. #[error("Auction error: {0}")] Auction(AuctionError), } +/// Block reward result. #[derive(Debug, Clone)] pub enum BlockRewardsResult { + /// Root not found in global state. RootNotFound, + /// Block rewards failure error. Failure(BlockRewardsError), + /// Success result. Success { /// State hash after distribution outcome is committed to the global state. post_state_hash: Digest, @@ -91,6 +103,7 @@ pub enum BlockRewardsResult { } impl BlockRewardsResult { + /// Returns true if successful, else false. pub fn is_success(&self) -> bool { matches!(self, BlockRewardsResult::Success { .. }) } diff --git a/storage/src/data_access_layer/entry_points.rs b/storage/src/data_access_layer/entry_points.rs index 18093732ea..c6fd4d93bc 100644 --- a/storage/src/data_access_layer/entry_points.rs +++ b/storage/src/data_access_layer/entry_points.rs @@ -37,6 +37,7 @@ pub enum EntryPointsResult { /// An addressable entity. entry_point: EntryPointValue, }, + /// Failure result. Failure(TrackingCopyError), } diff --git a/storage/src/data_access_layer/execution_results_checksum.rs b/storage/src/data_access_layer/execution_results_checksum.rs index a206dd6099..cc4d77e510 100644 --- a/storage/src/data_access_layer/execution_results_checksum.rs +++ b/storage/src/data_access_layer/execution_results_checksum.rs @@ -1,6 +1,7 @@ use crate::tracking_copy::TrackingCopyError; use casper_types::Digest; +/// Execution results checksum literal. pub const EXECUTION_RESULTS_CHECKSUM_NAME: &str = "execution_results_checksum"; /// Represents a request to obtain current execution results checksum. diff --git a/storage/src/data_access_layer/fee.rs b/storage/src/data_access_layer/fee.rs index 5f8d9355f1..9b11da254a 100644 --- a/storage/src/data_access_layer/fee.rs +++ b/storage/src/data_access_layer/fee.rs @@ -12,6 +12,7 @@ use casper_types::{ use crate::tracking_copy::TrackingCopyError; +/// Fee request. #[derive(Debug, Clone, PartialEq, Eq)] pub struct FeeRequest { config: NativeRuntimeConfig, @@ -21,6 +22,7 @@ pub struct FeeRequest { } impl FeeRequest { + /// Ctor. pub fn new( config: NativeRuntimeConfig, state_hash: Digest, @@ -87,26 +89,37 @@ impl FeeRequest { } } +/// Fee error. #[derive(Clone, Error, Debug)] pub enum FeeError { + /// No fees distributed error. #[error("Undistributed fees")] NoFeesDistributed, + /// Tracking copy error. #[error(transparent)] TrackingCopy(TrackingCopyError), + /// Registry entry not found. #[error("Registry entry not found: {0}")] RegistryEntryNotFound(String), + /// Transfer error. #[error(transparent)] Transfer(TransferError), + /// Named keys not found. #[error("Named keys not found")] NamedKeysNotFound, + /// Administrative accounts not found. #[error("Administrative accounts not found")] AdministrativeAccountsNotFound, } +/// Fee result. #[derive(Debug, Clone)] pub enum FeeResult { + /// Root not found in global state. RootNotFound, + /// Failure result. Failure(FeeError), + /// Success result. Success { /// List of transfers that happened during execution. transfers: Vec, @@ -118,6 +131,7 @@ pub enum FeeResult { } impl FeeResult { + /// Returns true if successful, else false. pub fn is_success(&self) -> bool { matches!(self, FeeResult::Success { .. }) } diff --git a/storage/src/data_access_layer/flush.rs b/storage/src/data_access_layer/flush.rs index e28ab0582e..b3853b00f4 100644 --- a/storage/src/data_access_layer/flush.rs +++ b/storage/src/data_access_layer/flush.rs @@ -32,6 +32,7 @@ impl FlushResult { matches!(self, FlushResult::Success) } + /// Transforms flush result to global state error, if relevant. pub fn as_error(self) -> Result<(), GlobalStateError> { match self { FlushResult::ManualSyncDisabled | FlushResult::Success => Ok(()), diff --git a/storage/src/data_access_layer/forced_undelegate.rs b/storage/src/data_access_layer/forced_undelegate.rs index 70a562d1a2..5f05e1f668 100644 --- a/storage/src/data_access_layer/forced_undelegate.rs +++ b/storage/src/data_access_layer/forced_undelegate.rs @@ -8,6 +8,7 @@ use crate::{ tracking_copy::TrackingCopyError, }; +/// Forced undelegate request. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ForcedUndelegateRequest { config: Config, @@ -17,6 +18,7 @@ pub struct ForcedUndelegateRequest { } impl ForcedUndelegateRequest { + /// Ctor. pub fn new( config: Config, state_hash: Digest, @@ -52,22 +54,31 @@ impl ForcedUndelegateRequest { } } +/// Forced undelegation error. #[derive(Clone, Error, Debug)] pub enum ForcedUndelegateError { + /// Tracking copy error. #[error(transparent)] TrackingCopy(TrackingCopyError), + /// Registry entry not found error. #[error("Registry entry not found: {0}")] RegistryEntryNotFound(String), + /// Transfer error. #[error(transparent)] Transfer(TransferError), + /// Auction error. #[error("Auction error: {0}")] Auction(AuctionError), } +/// Forced undelegation result. #[derive(Debug, Clone)] pub enum ForcedUndelegateResult { + /// Root hash not found in global state. RootNotFound, + /// Forced undelegation failed. Failure(ForcedUndelegateError), + /// Forced undelgation succeeded. Success { /// State hash after distribution outcome is committed to the global state. post_state_hash: Digest, @@ -77,6 +88,7 @@ pub enum ForcedUndelegateResult { } impl ForcedUndelegateResult { + /// Returns true if successful, else false. pub fn is_success(&self) -> bool { matches!(self, Self::Success { .. }) } diff --git a/storage/src/data_access_layer/genesis.rs b/storage/src/data_access_layer/genesis.rs index 163270c76f..b032c99a12 100644 --- a/storage/src/data_access_layer/genesis.rs +++ b/storage/src/data_access_layer/genesis.rs @@ -1,4 +1,4 @@ -#[cfg(any(feature = "testing", test))] +#[cfg(test)] use rand::{ distributions::{Distribution, Standard}, Rng, @@ -54,7 +54,7 @@ impl GenesisRequest { } } -#[cfg(any(feature = "testing", test))] +#[cfg(test)] impl Distribution for Standard { fn sample(&self, rng: &mut R) -> GenesisRequest { let input: [u8; 32] = rng.gen(); @@ -73,8 +73,11 @@ impl Distribution for Standard { /// Represents a result of a `genesis` request. #[derive(Debug, Clone)] pub enum GenesisResult { + /// Genesis fatal. Fatal(String), + /// Genesis failure. Failure(GenesisError), + /// Genesis success. Success { /// State hash after genesis is committed to the global state. post_state_hash: Digest, diff --git a/storage/src/data_access_layer/handle_fee.rs b/storage/src/data_access_layer/handle_fee.rs index 8b3853555d..b8fd5fad84 100644 --- a/storage/src/data_access_layer/handle_fee.rs +++ b/storage/src/data_access_layer/handle_fee.rs @@ -7,26 +7,40 @@ use casper_types::{ U512, }; +/// Handle fee mode. #[derive(Debug, Clone, PartialEq, Eq)] pub enum HandleFeeMode { + /// Pay the fee. Pay { + /// Initiator. initiator_addr: Box, + /// Source. source: Box, + /// Target. target: Box, + /// Amount. amount: U512, }, + /// Burn the fee. Burn { + /// Source. source: BalanceIdentifier, + /// Amount. amount: Option, }, + /// Validator credit (used in no fee mode). Credit { + /// Validator. validator: Box, + /// Amount. amount: U512, + /// EraId. era_id: EraId, }, } impl HandleFeeMode { + /// Ctor for Pay mode. pub fn pay( initiator_addr: Box, source: BalanceIdentifier, @@ -60,6 +74,7 @@ impl HandleFeeMode { } } +/// Handle fee request. #[derive(Debug, Clone, PartialEq, Eq)] pub struct HandleFeeRequest { /// The runtime config. @@ -93,22 +108,27 @@ impl HandleFeeRequest { } } + /// Returns config. pub fn config(&self) -> &NativeRuntimeConfig { &self.config } + /// Returns state hash. pub fn state_hash(&self) -> Digest { self.state_hash } + /// Returns handle protocol version. pub fn protocol_version(&self) -> ProtocolVersion { self.protocol_version } + /// Returns handle transaction hash. pub fn transaction_hash(&self) -> TransactionHash { self.transaction_hash } + /// Returns handle fee mode. pub fn handle_fee_mode(&self) -> &HandleFeeMode { &self.handle_fee_mode } @@ -120,7 +140,10 @@ pub enum HandleFeeResult { /// Invalid state root hash. RootNotFound, /// Handle request succeeded. - Success { effects: Effects }, + Success { + /// Handle fee effects. + effects: Effects, + }, /// Handle request failed. Failure(TrackingCopyError), } diff --git a/storage/src/data_access_layer/handle_refund.rs b/storage/src/data_access_layer/handle_refund.rs index ab7fb36038..9b022b70f2 100644 --- a/storage/src/data_access_layer/handle_refund.rs +++ b/storage/src/data_access_layer/handle_refund.rs @@ -7,43 +7,75 @@ use casper_types::{ }; use num_rational::Ratio; +/// Selects refund operation. #[derive(Debug, Clone, PartialEq, Eq)] pub enum HandleRefundMode { + /// Burn. Burn { + /// Refund limit. limit: U512, + /// Refund cost. cost: U512, + /// Refund consumed. consumed: U512, + /// Refund gas price. gas_price: u8, + /// Refund source. source: Box, + /// Refund ratio. ratio: Ratio, }, + /// Refund. Refund { + /// Refund initiator. initiator_addr: Box, + /// Refund limit. limit: U512, + /// Refund cost. cost: U512, + /// Refund consumed. consumed: U512, + /// Refund gas price. gas_price: u8, + /// Refund ratio. ratio: Ratio, + /// Refund source. source: Box, + /// Target for refund. target: Box, }, + /// Place a custom hold. CustomHold { + /// Refund initiator. initiator_addr: Box, + /// Refund limit. limit: U512, + /// Refund cost. cost: U512, + /// Refund gas price. gas_price: u8, }, + /// Refund amount. RefundAmount { + /// Refund limit. limit: U512, + /// Refund cost. cost: U512, + /// Refund consumed. consumed: U512, + /// Refund gas price. gas_price: u8, + /// Refund ratio. ratio: Ratio, + /// Refund source. source: Box, }, + /// Set refund purse. SetRefundPurse { + /// Target for refund. target: Box, }, + /// Clear refund purse. ClearRefundPurse, } @@ -61,6 +93,7 @@ impl HandleRefundMode { } } +/// Handle refund request. #[derive(Debug, Clone, PartialEq, Eq)] pub struct HandleRefundRequest { /// The runtime config. @@ -93,34 +126,42 @@ impl HandleRefundRequest { } } + /// Returns a reference to the config. pub fn config(&self) -> &NativeRuntimeConfig { &self.config } + /// Returns the state hash. pub fn state_hash(&self) -> Digest { self.state_hash } + /// Returns the protocol version. pub fn protocol_version(&self) -> ProtocolVersion { self.protocol_version } + /// Returns the transaction hash. pub fn transaction_hash(&self) -> TransactionHash { self.transaction_hash } + /// Returns the refund mode. pub fn refund_mode(&self) -> &HandleRefundMode { &self.refund_mode } } +/// Handle refund result. #[derive(Debug)] pub enum HandleRefundResult { /// Invalid state root hash. RootNotFound, /// Handle refund request succeeded. Success { + /// The effects. effects: Effects, + /// The amount, if any. amount: Option, }, /// Invalid phase selected (programmer error). diff --git a/storage/src/data_access_layer/key_prefix.rs b/storage/src/data_access_layer/key_prefix.rs index c6bd0745f8..24a4823f63 100644 --- a/storage/src/data_access_layer/key_prefix.rs +++ b/storage/src/data_access_layer/key_prefix.rs @@ -1,5 +1,3 @@ -#[cfg(any(feature = "testing", test))] -use casper_types::testing::TestRng; use casper_types::{ account::AccountHash, bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, @@ -7,8 +5,6 @@ use casper_types::{ system::{auction::BidAddrTag, mint::BalanceHoldAddrTag}, EntityAddr, KeyTag, URefAddr, }; -#[cfg(any(feature = "testing", test))] -use rand::Rng; /// Key prefixes used for querying the global state. #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] @@ -172,6 +168,9 @@ impl FromBytes for KeyPrefix { #[cfg(test)] mod tests { + use casper_types::testing::TestRng; + use rand::Rng; + use casper_types::{ addressable_entity::NamedKeyAddr, contract_messages::MessageAddr, diff --git a/storage/src/data_access_layer/mint.rs b/storage/src/data_access_layer/mint.rs index 8016906cd3..6cc7da9b8b 100644 --- a/storage/src/data_access_layer/mint.rs +++ b/storage/src/data_access_layer/mint.rs @@ -1,21 +1,81 @@ use std::collections::BTreeSet; use crate::{ + data_access_layer::BalanceIdentifier, system::runtime_native::{Config as NativeRuntimeConfig, TransferConfig}, tracking_copy::TrackingCopyCache, }; use casper_types::{ account::AccountHash, execution::Effects, Digest, InitiatorAddr, ProtocolVersion, RuntimeArgs, - TransactionHash, Transfer, + TransactionHash, Transfer, U512, }; use crate::system::transfer::{TransferArgs, TransferError}; +/// Transfer arguments using balance identifiers. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BalanceIdentifierTransferArgs { + to: Option, + source: BalanceIdentifier, + target: BalanceIdentifier, + amount: U512, + arg_id: Option, +} + +impl BalanceIdentifierTransferArgs { + /// Ctor. + pub fn new( + to: Option, + source: BalanceIdentifier, + target: BalanceIdentifier, + amount: U512, + arg_id: Option, + ) -> Self { + BalanceIdentifierTransferArgs { + to, + source, + target, + amount, + arg_id, + } + } + + /// Get to. + pub fn to(&self) -> Option { + self.to + } + + /// Get source. + pub fn source(&self) -> &BalanceIdentifier { + &self.source + } + + /// Get target. + pub fn target(&self) -> &BalanceIdentifier { + &self.target + } + + /// Get amount. + pub fn amount(&self) -> U512 { + self.amount + } + + /// Get arg_id. + pub fn arg_id(&self) -> Option { + self.arg_id + } +} + /// Transfer details. #[derive(Debug, Clone, PartialEq, Eq)] pub enum TransferRequestArgs { + /// Provides opaque arguments in runtime format. Raw(RuntimeArgs), + /// Provides explicit structured args. Explicit(TransferArgs), + /// Provides support for transfers using balance identifiers. + /// The source and target purses will get resolved on usage. + Indirect(Box), } /// Request for motes transfer. @@ -84,10 +144,35 @@ impl TransferRequest { } } + /// Creates new request object using balance identifiers. + #[allow(clippy::too_many_arguments)] + pub fn new_indirect( + config: NativeRuntimeConfig, + state_hash: Digest, + protocol_version: ProtocolVersion, + transaction_hash: TransactionHash, + initiator: InitiatorAddr, + authorization_keys: BTreeSet, + args: BalanceIdentifierTransferArgs, + ) -> Self { + let args = TransferRequestArgs::Indirect(Box::new(args)); + Self { + config, + state_hash, + protocol_version, + transaction_hash, + initiator, + authorization_keys, + args, + } + } + + /// Returns a reference to the runtime config. pub fn config(&self) -> &NativeRuntimeConfig { &self.config } + /// Returns a reference to the transfer config. pub fn transfer_config(&self) -> &TransferConfig { self.config.transfer_config() } @@ -136,6 +221,7 @@ impl TransferRequest { } } +/// Transfer result. #[derive(Debug, Clone)] pub enum TransferResult { /// Invalid state root hash. @@ -162,6 +248,7 @@ impl TransferResult { } } + /// Returns transfers. pub fn transfers(&self) -> Vec { match self { TransferResult::RootNotFound | TransferResult::Failure(_) => vec![], @@ -169,6 +256,7 @@ impl TransferResult { } } + /// Returns transfer error, if any. pub fn error(&self) -> Option { if let Self::Failure(error) = self { Some(error.clone()) diff --git a/storage/src/data_access_layer/prefixed_values.rs b/storage/src/data_access_layer/prefixed_values.rs index 54e557f855..942af11fb0 100644 --- a/storage/src/data_access_layer/prefixed_values.rs +++ b/storage/src/data_access_layer/prefixed_values.rs @@ -41,5 +41,6 @@ pub enum PrefixedValuesResult { /// Current values. values: Vec, }, + /// Failure. Failure(TrackingCopyError), } diff --git a/storage/src/data_access_layer/round_seigniorage.rs b/storage/src/data_access_layer/round_seigniorage.rs index c19dd2d88b..77405d1235 100644 --- a/storage/src/data_access_layer/round_seigniorage.rs +++ b/storage/src/data_access_layer/round_seigniorage.rs @@ -43,5 +43,6 @@ pub enum RoundSeigniorageRateResult { /// The current rate. rate: Ratio, }, + /// Failure. Failure(TrackingCopyError), } diff --git a/storage/src/data_access_layer/step.rs b/storage/src/data_access_layer/step.rs index 44bc0a2877..860cde6809 100644 --- a/storage/src/data_access_layer/step.rs +++ b/storage/src/data_access_layer/step.rs @@ -114,10 +114,12 @@ impl StepRequest { } } + /// Returns the config. pub fn config(&self) -> &Config { &self.config } + /// Returns the transfer config. pub fn transfer_config(&self) -> TransferConfig { self.config.transfer_config().clone() } @@ -215,6 +217,7 @@ pub enum StepResult { } impl StepResult { + /// Returns if step is successful. pub fn is_success(&self) -> bool { matches!(self, StepResult::Success { .. }) } diff --git a/storage/src/data_access_layer/system_entity_registry.rs b/storage/src/data_access_layer/system_entity_registry.rs index d9d4623a9a..c1f1c67e8b 100644 --- a/storage/src/data_access_layer/system_entity_registry.rs +++ b/storage/src/data_access_layer/system_entity_registry.rs @@ -7,7 +7,9 @@ use casper_types::{ /// Used to specify is the requestor wants the registry itself or a named entry within it. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SystemEntityRegistrySelector { + /// Requests all system entity entries. All, + /// Requests system entity by name. ByName(String), } @@ -66,15 +68,17 @@ impl SystemEntityRegistryRequest { } } + /// Returns the state hash. pub fn state_hash(&self) -> Digest { self.state_hash } - /// The selector. + /// Returns the current selector. pub fn selector(&self) -> &SystemEntityRegistrySelector { &self.selector } + /// Protocol version. pub fn protocol_version(&self) -> ProtocolVersion { self.protocol_version } @@ -83,7 +87,9 @@ impl SystemEntityRegistryRequest { /// The payload of a successful request. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SystemEntityRegistryPayload { + /// All registry entries. All(SystemEntityRegistry), + /// Specific system entity registry entry. EntityKey(Key), } @@ -110,11 +116,13 @@ pub enum SystemEntityRegistryResult { } impl SystemEntityRegistryResult { + /// Is success. pub fn is_success(&self) -> bool { matches!(self, SystemEntityRegistryResult::Success { .. }) } - pub fn as_legacy(&self) -> Result { + /// As registry payload. + pub fn as_registry_payload(&self) -> Result { match self { SystemEntityRegistryResult::RootNotFound => Err("Root not found".to_string()), SystemEntityRegistryResult::SystemEntityRegistryNotFound => { diff --git a/storage/src/data_access_layer/tagged_values.rs b/storage/src/data_access_layer/tagged_values.rs index 12a91907b5..bf430bb8a5 100644 --- a/storage/src/data_access_layer/tagged_values.rs +++ b/storage/src/data_access_layer/tagged_values.rs @@ -2,9 +2,10 @@ use crate::tracking_copy::TrackingCopyError; use casper_types::{Digest, KeyTag, StoredValue}; +/// Tagged values selector. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TaggedValuesSelection { - // All values under the specified key tag. + /// All values under the specified key tag. All(KeyTag), } @@ -54,5 +55,6 @@ pub enum TaggedValuesResult { /// Current values. values: Vec, }, + /// Tagged value failure. Failure(TrackingCopyError), } diff --git a/storage/src/data_access_layer/trie.rs b/storage/src/data_access_layer/trie.rs index 57330521f2..cfc09f645b 100644 --- a/storage/src/data_access_layer/trie.rs +++ b/storage/src/data_access_layer/trie.rs @@ -55,7 +55,8 @@ pub enum TrieResult { } impl TrieResult { - pub fn into_legacy(self) -> Result, GlobalStateError> { + /// Transform trie result to raw state. + pub fn into_raw(self) -> Result, GlobalStateError> { match self { TrieResult::ValueNotFound(_) => Ok(None), TrieResult::Success { element } => match element { @@ -83,6 +84,7 @@ impl PutTrieRequest { &self.raw } + /// Take raw trie value. pub fn take_raw(self) -> TrieRaw { self.raw } diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index cc2eaf338b..680f598f60 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -84,7 +84,7 @@ use crate::{ mint::Mint, protocol_upgrade::{ProtocolUpgradeError, ProtocolUpgrader}, runtime_native::{Id, RuntimeNative}, - transfer::{NewTransferTargetMode, TransferError, TransferRuntimeArgsBuilder}, + transfer::{TransferArgs, TransferError, TransferRuntimeArgsBuilder, TransferTargetMode}, }, tracking_copy::{TrackingCopy, TrackingCopyEntityExt, TrackingCopyError, TrackingCopyExt}, AddressGenerator, @@ -130,13 +130,17 @@ pub enum CommitError { TrieNotFoundInCache(Digest), } +/// Scratch provider. pub trait ScratchProvider: CommitProvider { + /// Get scratch state to db. fn get_scratch_global_state(&self) -> ScratchGlobalState; + /// Write scratch state to db. fn write_scratch_to_db( &self, state_root_hash: Digest, scratch_global_state: ScratchGlobalState, ) -> Result; + /// Prune items for imputed keys. fn prune_keys(&self, state_root_hash: Digest, keys: &[Key]) -> TriePruneResult; } @@ -150,6 +154,7 @@ pub trait CommitProvider: StateProvider { effects: Effects, ) -> Result; + /// Commit values to global state. fn commit_values( &self, state_hash: Digest, @@ -618,6 +623,7 @@ pub trait CommitProvider: StateProvider { } } + /// Gets block global data. fn block_global(&self, request: BlockGlobalRequest) -> BlockGlobalResult { let state_hash = request.state_hash(); let tc = match self.tracking_copy(state_hash) { @@ -1276,6 +1282,7 @@ pub trait StateProvider: Send + Sync { amount, minimum_delegation_amount, maximum_delegation_amount, + minimum_bid_amount, } => runtime .add_bid( public_key, @@ -1283,12 +1290,17 @@ pub trait StateProvider: Send + Sync { amount, minimum_delegation_amount, maximum_delegation_amount, + minimum_bid_amount, 0, ) .map(AuctionMethodRet::UpdatedAmount) .map_err(TrackingCopyError::Api), - AuctionMethod::WithdrawBid { public_key, amount } => runtime - .withdraw_bid(public_key, amount) + AuctionMethod::WithdrawBid { + public_key, + amount, + minimum_bid_amount, + } => runtime + .withdraw_bid(public_key, amount, minimum_bid_amount) .map(AuctionMethodRet::UpdatedAmount) .map_err(|auc_err| { TrackingCopyError::SystemContract(system::Error::Auction(auc_err)) @@ -2030,6 +2042,33 @@ pub trait StateProvider: Send + Sync { Err(cve) => return TransferResult::Failure(TransferError::CLValue(cve)), } } + TransferRequestArgs::Indirect(bita) => { + let source_uref = match bita + .source() + .purse_uref(&mut tc.borrow_mut(), protocol_version) + { + Ok(source_uref) => source_uref, + Err(tce) => return TransferResult::Failure(TransferError::TrackingCopy(tce)), + }; + let target_uref = match bita + .target() + .purse_uref(&mut tc.borrow_mut(), protocol_version) + { + Ok(target_uref) => target_uref, + Err(tce) => return TransferResult::Failure(TransferError::TrackingCopy(tce)), + }; + let transfer_args = TransferArgs::new( + bita.to(), + source_uref, + target_uref, + bita.amount(), + bita.arg_id(), + ); + match RuntimeArgs::try_from(transfer_args) { + Ok(runtime_args) => runtime_args, + Err(cve) => return TransferResult::Failure(TransferError::CLValue(cve)), + } + } }; let remaining_spending_limit = match runtime_args.try_get_number(ARG_AMOUNT) { @@ -2108,11 +2147,10 @@ pub trait StateProvider: Send + Sync { ); match transfer_target_mode { - NewTransferTargetMode::ExistingAccount { .. } - | NewTransferTargetMode::PurseExists { .. } => { + TransferTargetMode::ExistingAccount { .. } | TransferTargetMode::PurseExists { .. } => { // Noop } - NewTransferTargetMode::CreateAccount(account_hash) => { + TransferTargetMode::CreateAccount(account_hash) => { let main_purse = match runtime.mint(U512::zero()) { Ok(uref) => uref, Err(mint_error) => { diff --git a/storage/src/global_state/trie/mod.rs b/storage/src/global_state/trie/mod.rs index 4593aacce2..bf1d5f50c3 100644 --- a/storage/src/global_state/trie/mod.rs +++ b/storage/src/global_state/trie/mod.rs @@ -376,6 +376,7 @@ impl Trie { } } + /// Tag type for current trie element. pub fn tag_type(&self) -> String { match self { Trie::Leaf { .. } => "Leaf".to_string(), diff --git a/storage/src/global_state/trie_store/mod.rs b/storage/src/global_state/trie_store/mod.rs index e6e7eff507..286daa997a 100644 --- a/storage/src/global_state/trie_store/mod.rs +++ b/storage/src/global_state/trie_store/mod.rs @@ -2,6 +2,7 @@ //! //! See the [lmdb](lmdb/index.html#usage) modules for usage examples. pub mod lmdb; +/// Trie store operational logic. pub mod operations; // An in-mem cache backed up by a store that is used to optimize batch writes. diff --git a/storage/src/global_state/trie_store/operations/mod.rs b/storage/src/global_state/trie_store/operations/mod.rs index 102dc3398a..aa374cb832 100644 --- a/storage/src/global_state/trie_store/operations/mod.rs +++ b/storage/src/global_state/trie_store/operations/mod.rs @@ -27,11 +27,15 @@ use self::store_wrappers::NonDeserializingStore; use super::{cache::TrieCache, TrieStoreCacheError}; +/// Result of attemptint to read a record from the trie store. #[allow(clippy::enum_variant_names)] #[derive(Debug, PartialEq, Eq)] pub enum ReadResult { + /// Requested item found in trie store. Found(V), + /// Requested item not found in trie store. NotFound, + /// Root hash not found in trie store. RootNotFound, } @@ -403,11 +407,16 @@ where } } +/// Result of attempting to prune an item from the trie store. #[derive(Debug, PartialEq, Eq)] pub enum TriePruneResult { + /// Successfully pruned item from trie store. Pruned(Digest), + /// Requested key not found in trie store. MissingKey, + /// Root hash not found in trie store. RootNotFound, + /// Prune failure. Failure(GlobalStateError), } @@ -852,13 +861,18 @@ where }) } +/// Result of attemptint to write to trie store. #[derive(Debug, PartialEq, Eq)] pub enum WriteResult { + /// Record written to trie store. Written(Digest), + /// Record already exists in trie store. AlreadyExists, + /// Requested global state root hash does not exist in trie store. RootNotFound, } +/// Write to trie store. pub fn write( txn: &mut T, store: &S, @@ -952,6 +966,7 @@ where } } +/// Batch write to trie store. pub fn batch_write( txn: &mut T, store: &S, @@ -1006,6 +1021,7 @@ struct VisitedTrieNode { path: Vec, } +/// Iterator for trie store keys. pub struct KeysIterator<'a, 'b, K, V, T, S: TrieStore> { initial_descend: VecDeque, visited: Vec, diff --git a/storage/src/lib.rs b/storage/src/lib.rs index f288c7043a..cd180a895b 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -2,15 +2,23 @@ #![doc(html_root_url = "https://docs.rs/casper-storage/2.0.0")] #![doc( - html_favicon_url = "https://raw.githubusercontent.com/CasperLabs/casper-node/master/images/CasperLabs_Logo_Favicon_RGB_50px.png", - html_logo_url = "https://raw.githubusercontent.com/CasperLabs/casper-node/master/images/CasperLabs_Logo_Symbol_RGB.png" + html_favicon_url = "https://raw.githubusercontent.com/casper-network/casper-node/master/images/CasperLabs_Logo_Favicon_RGB_50px.png", + html_logo_url = "https://raw.githubusercontent.com/casper-network/casper-node/master/images/CasperLabs_Logo_Symbol_RGB.png" )] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn(missing_docs)] +/// Address generator logic. pub mod address_generator; +/// Block store logic. pub mod block_store; +/// Data access layer logic. pub mod data_access_layer; +/// Global state logic. pub mod global_state; +/// Storage layer logic. pub mod system; +/// Tracking copy. pub mod tracking_copy; pub use address_generator::{AddressGenerator, AddressGeneratorBuilder}; diff --git a/storage/src/system.rs b/storage/src/system.rs index 697858bf86..246f0643cd 100644 --- a/storage/src/system.rs +++ b/storage/src/system.rs @@ -1,9 +1,18 @@ +/// Auction logic. pub mod auction; +/// Error definition. pub mod error; +/// Genesis logic. pub mod genesis; +/// Handle payment logic. pub mod handle_payment; +/// Mint logic. pub mod mint; +/// Protocol upgrade logic. pub mod protocol_upgrade; +/// Runtime native logic. pub mod runtime_native; +/// Standard payment logic. pub mod standard_payment; +/// Transfer logic. pub mod transfer; diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index 0461bd9d0d..9361262301 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -1,5 +1,7 @@ mod auction_native; +/// Auction business logic. pub mod detail; +/// System logic providers. pub mod providers; use std::collections::BTreeMap; @@ -66,6 +68,7 @@ pub trait Auction: /// [`DELEGATION_RATE_DENOMINATOR`]. /// /// Returns a [`U512`] value indicating total amount of tokens staked for given `public_key`. + #[allow(clippy::too_many_arguments)] fn add_bid( &mut self, public_key: PublicKey, @@ -73,6 +76,7 @@ pub trait Auction: amount: U512, minimum_delegation_amount: u64, maximum_delegation_amount: u64, + minimum_bid_amount: u64, // TODO: reservation list functionality implementation _reserved_slots: u32, ) -> Result { @@ -82,7 +86,8 @@ pub trait Auction: return Err(Error::AuctionBidsDisabled.into()); } - if amount.is_zero() { + if amount < U512::from(minimum_bid_amount) { + println!("bond too small"); return Err(Error::BondTooSmall.into()); } @@ -154,7 +159,12 @@ pub trait Auction: /// An attempt to reduce stake by more than is staked will instead 0 the stake. /// /// The function returns the remaining staked amount (we allow partial unbonding). - fn withdraw_bid(&mut self, public_key: PublicKey, amount: U512) -> Result { + fn withdraw_bid( + &mut self, + public_key: PublicKey, + amount: U512, + minimum_bid_amount: u64, + ) -> Result { let provided_account_hash = AccountHash::from_public_key(&public_key, |x| self.blake2b(x)); if !self.is_allowed_session_caller(&provided_account_hash) { @@ -186,7 +196,7 @@ pub trait Auction: "withdrawing bid for {} reducing {} by {} to {}", validator_bid_addr, initial_amount, unbonding_amount, updated_stake ); - if updated_stake.is_zero() { + if updated_stake < U512::from(minimum_bid_amount) { // Unbond all delegators and zero them out let delegators = read_delegator_bids(self, &public_key)?; for mut delegator in delegators { @@ -1027,6 +1037,7 @@ fn rewards_per_validator( Ok(results) } +/// Aggregated rewards data for a validator. #[derive(Debug, Default)] pub struct RewardsPerValidator { validator_reward: U512, diff --git a/storage/src/system/auction/detail.rs b/storage/src/system/auction/detail.rs index 4a01ccde42..a4978fce74 100644 --- a/storage/src/system/auction/detail.rs +++ b/storage/src/system/auction/detail.rs @@ -51,6 +51,7 @@ where provider.write(uref, value) } +/// Aggregated bid data for a Validator. #[derive(Debug, Default)] pub struct ValidatorBidsDetail { validator_bids: ValidatorBids, @@ -59,6 +60,7 @@ pub struct ValidatorBidsDetail { } impl ValidatorBidsDetail { + /// Ctor. pub fn new() -> Self { ValidatorBidsDetail { validator_bids: BTreeMap::new(), @@ -210,6 +212,7 @@ pub fn prune_validator_credits

( } } +/// Returns the imputed validator bids. pub fn get_validator_bids

(provider: &mut P, era_id: EraId) -> Result where P: StorageProvider + RuntimeProvider + ?Sized, @@ -238,6 +241,7 @@ where Ok(ret) } +/// Sets the imputed validator bids. pub fn set_validator_bids

(provider: &mut P, validators: ValidatorBids) -> Result<(), Error> where P: StorageProvider + RuntimeProvider + ?Sized, @@ -249,6 +253,7 @@ where Ok(()) } +/// Returns the unbonding purses. pub fn get_unbonding_purses

(provider: &mut P) -> Result where P: StorageProvider + RuntimeProvider + ?Sized, @@ -269,6 +274,7 @@ where Ok(ret) } +/// Sets the unbonding purses. pub fn set_unbonding_purses

( provider: &mut P, unbonding_purses: UnbondingPurses, @@ -282,6 +288,7 @@ where Ok(()) } +/// Returns the era id. pub fn get_era_id

(provider: &mut P) -> Result where P: StorageProvider + RuntimeProvider + ?Sized, @@ -289,6 +296,7 @@ where read_from(provider, ERA_ID_KEY) } +/// Sets the era id. pub fn set_era_id

(provider: &mut P, era_id: EraId) -> Result<(), Error> where P: StorageProvider + RuntimeProvider + ?Sized, @@ -296,6 +304,7 @@ where write_to(provider, ERA_ID_KEY, era_id) } +/// Returns the era end timestamp. pub fn get_era_end_timestamp_millis

(provider: &mut P) -> Result where P: StorageProvider + RuntimeProvider + ?Sized, @@ -303,6 +312,7 @@ where read_from(provider, ERA_END_TIMESTAMP_MILLIS_KEY) } +/// Sets the era end timestamp. pub fn set_era_end_timestamp_millis

( provider: &mut P, era_end_timestamp_millis: u64, @@ -317,6 +327,7 @@ where ) } +/// Returns seigniorage recipients snapshot. pub fn get_seigniorage_recipients_snapshot

( provider: &mut P, ) -> Result @@ -326,6 +337,7 @@ where read_from(provider, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY) } +/// Set seigniorage recipients snapshot. pub fn set_seigniorage_recipients_snapshot

( provider: &mut P, snapshot: SeigniorageRecipientsSnapshot, @@ -336,6 +348,7 @@ where write_to(provider, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, snapshot) } +/// Returns the number of validator slots. pub fn get_validator_slots

(provider: &mut P) -> Result where P: StorageProvider + RuntimeProvider + ?Sized, @@ -353,6 +366,7 @@ where Ok(validator_slots) } +/// Returns auction delay. pub fn get_auction_delay

(provider: &mut P) -> Result where P: StorageProvider + RuntimeProvider + ?Sized, @@ -782,6 +796,7 @@ where Ok(updated_amount) } +/// Returns validator bid by key. pub fn read_validator_bid

(provider: &mut P, bid_key: &Key) -> Result, Error> where P: StorageProvider + ?Sized, @@ -826,6 +841,7 @@ where Err(Error::ValidatorNotFound) } +/// Returns all delegator bids for imputed validator. pub fn read_delegator_bids

( provider: &mut P, validator_public_key: &PublicKey, @@ -848,6 +864,7 @@ where Ok(ret) } +/// Returns delegator bid by key. pub fn read_delegator_bid

(provider: &mut P, bid_key: &Key) -> Result, Error> where P: RuntimeProvider + ?Sized + StorageProvider, @@ -862,6 +879,7 @@ where } } +/// Applies seigniorage recipient changes. pub fn seigniorage_recipients( validator_weights: &ValidatorWeights, validator_bids: &ValidatorBids, @@ -972,6 +990,7 @@ where } } +/// Returns all delegators for imputed validator. pub fn delegators

( provider: &mut P, validator_public_key: &PublicKey, diff --git a/storage/src/system/error.rs b/storage/src/system/error.rs index 8fc7058bf0..cfb8c1914e 100644 --- a/storage/src/system/error.rs +++ b/storage/src/system/error.rs @@ -3,6 +3,8 @@ use casper_types::account::AccountHash; /// Implementation level errors for system contract providers #[derive(Debug)] pub enum ProviderError { + /// System contract registry. SystemEntityRegistry, - AddressableEntityByAccountHash(AccountHash), + /// Account hash. + AccountHash(AccountHash), } diff --git a/storage/src/system/genesis.rs b/storage/src/system/genesis.rs index a181b5bbc9..6fe516d570 100644 --- a/storage/src/system/genesis.rs +++ b/storage/src/system/genesis.rs @@ -135,6 +135,7 @@ impl fmt::Display for GenesisError { } } +/// State for genesis installer. pub struct GenesisInstaller where S: StateProvider + ?Sized, @@ -149,6 +150,7 @@ impl GenesisInstaller where S: StateProvider + ?Sized, { + /// Ctor. pub fn new( genesis_config_hash: Digest, protocol_version: ProtocolVersion, @@ -171,6 +173,7 @@ where } } + /// Finalize genesis. pub fn finalize(self) -> Effects { self.tracking_copy.borrow().effects() } @@ -360,7 +363,7 @@ where for (validator_public_key, delegator_public_key, _, delegated_amount) in genesis_delegators.iter() { - if delegated_amount.is_zero() { + if *delegated_amount == &Motes::zero() { return Err(GenesisError::InvalidDelegatedAmount { public_key: (*delegator_public_key).clone(), } @@ -605,6 +608,7 @@ where Ok(contract_hash) } + /// Create genesis accounts. pub fn create_accounts(&self, total_supply_key: Key) -> Result<(), Box> { let accounts = { let mut ret: Vec = self.config.accounts_iter().cloned().collect(); diff --git a/storage/src/system/handle_payment.rs b/storage/src/system/handle_payment.rs index b9854da1ed..a093e18b6e 100644 --- a/storage/src/system/handle_payment.rs +++ b/storage/src/system/handle_payment.rs @@ -1,7 +1,10 @@ mod handle_payment_native; mod internal; +/// Provides mint logic for handle payment processing. pub mod mint_provider; +/// Provides runtime logic for handle payment processing. pub mod runtime_provider; +/// Provides storage logic for handle payment processing. pub mod storage_provider; use casper_types::{ diff --git a/storage/src/system/mint.rs b/storage/src/system/mint.rs index bbcd0b48b4..539da23b29 100644 --- a/storage/src/system/mint.rs +++ b/storage/src/system/mint.rs @@ -1,7 +1,11 @@ pub(crate) mod detail; +/// Provides native mint processing. mod mint_native; +/// Provides runtime logic for mint processing. pub mod runtime_provider; +/// Provides storage logic for mint processing. pub mod storage_provider; +/// Provides system logic for mint processing. pub mod system_provider; use num_rational::Ratio; diff --git a/storage/src/system/mint/mint_native.rs b/storage/src/system/mint/mint_native.rs index eb8e6b6643..3b9d890dc1 100644 --- a/storage/src/system/mint/mint_native.rs +++ b/storage/src/system/mint/mint_native.rs @@ -61,7 +61,7 @@ where Ok((_, entity)) => Ok(Some(entity)), Err(tce) => { error!(%tce, "error reading addressable entity by account hash"); - Err(ProviderError::AddressableEntityByAccountHash(account_hash)) + Err(ProviderError::AccountHash(account_hash)) } } } diff --git a/storage/src/system/mint/runtime_provider.rs b/storage/src/system/mint/runtime_provider.rs index fbebcfa1a3..19e24b18cf 100644 --- a/storage/src/system/mint/runtime_provider.rs +++ b/storage/src/system/mint/runtime_provider.rs @@ -12,11 +12,13 @@ pub trait RuntimeProvider { /// This method should return the immediate caller of the current context. fn get_immediate_caller(&self) -> Option; + /// Is the caller standard payment logic? fn is_called_from_standard_payment(&self) -> bool; /// Get system entity registry. fn get_system_entity_registry(&self) -> Result; + /// Read addressable entity by account hash. fn read_addressable_entity_by_account_hash( &mut self, account_hash: AccountHash, diff --git a/storage/src/system/protocol_upgrade.rs b/storage/src/system/protocol_upgrade.rs index 7f28840fe9..3cae245272 100644 --- a/storage/src/system/protocol_upgrade.rs +++ b/storage/src/system/protocol_upgrade.rs @@ -168,6 +168,7 @@ where } } + /// Apply a protocol upgrade. pub fn upgrade( mut self, pre_state_hash: Digest, @@ -492,6 +493,7 @@ where )) } + /// Migrate the system account to addressable entity if necessary. pub fn migrate_system_account( &mut self, pre_state_hash: Digest, @@ -759,6 +761,7 @@ where Ok(()) } + /// Applies the necessary changes if a new auction delay is part of the upgrade. pub fn handle_new_auction_delay( &mut self, auction: AddressableEntityHash, @@ -781,6 +784,7 @@ where Ok(()) } + /// Applies the necessary changes if a new locked funds period is part of the upgrade. pub fn handle_new_locked_funds_period_millis( &mut self, auction: AddressableEntityHash, @@ -803,6 +807,7 @@ where Ok(()) } + /// Applies the necessary changes if a new unbonding delay is part of the upgrade. pub fn handle_new_unbonding_delay( &mut self, auction: AddressableEntityHash, @@ -827,6 +832,7 @@ where Ok(()) } + /// Applies the necessary changes if a new round seigniorage rate is part of the upgrade. pub fn handle_new_round_seigniorage_rate( &mut self, mint: AddressableEntityHash, diff --git a/storage/src/system/runtime_native.rs b/storage/src/system/runtime_native.rs index 798ad0a8cb..54aadfa536 100644 --- a/storage/src/system/runtime_native.rs +++ b/storage/src/system/runtime_native.rs @@ -13,6 +13,7 @@ use parking_lot::RwLock; use std::{cell::RefCell, collections::BTreeSet, rc::Rc, sync::Arc}; use tracing::error; +/// Configuration settings. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Config { transfer_config: TransferConfig, @@ -29,6 +30,7 @@ pub struct Config { } impl Config { + /// Ctor. #[allow(clippy::too_many_arguments)] pub const fn new( transfer_config: TransferConfig, @@ -58,6 +60,7 @@ impl Config { } } + /// Ctor from chainspec. pub fn from_chainspec(chainspec: &Chainspec) -> Self { let transfer_config = TransferConfig::from_chainspec(chainspec); let fee_handling = chainspec.core_config.fee_handling; @@ -88,50 +91,62 @@ impl Config { ) } + /// Returns transfer config. pub fn transfer_config(&self) -> &TransferConfig { &self.transfer_config } + /// Returns fee handling setting. pub fn fee_handling(&self) -> &FeeHandling { &self.fee_handling } + /// Returns refund handling setting. pub fn refund_handling(&self) -> &RefundHandling { &self.refund_handling } + /// Returns vesting schedule period millis setting. pub fn vesting_schedule_period_millis(&self) -> u64 { self.vesting_schedule_period_millis } + /// Returns if auction bids are allowed. pub fn allow_auction_bids(&self) -> bool { self.allow_auction_bids } + /// Returns if rewards should be computed. pub fn compute_rewards(&self) -> bool { self.compute_rewards } + /// Returns max delegators per validator setting. pub fn max_delegators_per_validator(&self) -> u32 { self.max_delegators_per_validator } + /// Returns minimum delegation amount setting. pub fn minimum_delegation_amount(&self) -> u64 { self.minimum_delegation_amount } + /// Returns balance hold interval setting. pub fn balance_hold_interval(&self) -> u64 { self.balance_hold_interval } + /// Returns include credit setting. pub fn include_credits(&self) -> bool { self.include_credits } + /// Returns validator credit cap setting. pub fn credit_cap(&self) -> Ratio { self.credit_cap } + /// Changes the transfer config. pub fn set_transfer_config(self, transfer_config: TransferConfig) -> Self { Config { transfer_config, @@ -149,12 +164,20 @@ impl Config { } } +/// Configuration for transfer. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum TransferConfig { + /// Transfers are affected by the existence of administrative_accounts. This is a + /// behavior specific to private or managed chains, not a public chain. Administered { + /// Retrusn the set of account hashes for all administrators. administrative_accounts: BTreeSet, + /// If true, transfers are unrestricted. + /// If false, the source and / or target of a transfer must be an administrative account. allow_unrestricted_transfers: bool, }, + /// Transfers are not affected by the existence of administrative_accounts (the standard + /// behavior). #[default] Unadministered, } @@ -233,12 +256,16 @@ impl TransferConfig { } } +/// Id for runtime processing. pub enum Id { + /// Hash of current transaction. Transaction(TransactionHash), + /// An arbitrary set of bytes to be used as a seed value. Seed(Vec), } impl Id { + /// Ctor for id enum. pub fn seed(&self) -> Vec { match self { Id::Transaction(hash) => hash.digest().into_vec(), @@ -247,6 +274,7 @@ impl Id { } } +/// State held by an instance of runtime native. pub struct RuntimeNative { config: Config, @@ -269,6 +297,7 @@ impl RuntimeNative where S: StateReader, { + /// Ctor. #[allow(clippy::too_many_arguments)] pub fn new( config: Config, @@ -387,102 +416,127 @@ where }) } + /// Returns mutable reference to address generator. pub fn address_generator(&mut self) -> Arc> { Arc::clone(&self.address_generator) } + /// Returns reference to config. pub fn config(&self) -> &Config { &self.config } + /// Returns reference to transfer config. pub fn transfer_config(&self) -> &TransferConfig { &self.config.transfer_config } + /// Returns protocol version. pub fn protocol_version(&self) -> ProtocolVersion { self.protocol_version } + /// Returns handle to tracking copy. pub fn tracking_copy(&self) -> Rc>> { Rc::clone(&self.tracking_copy) } + /// Returns account hash being used by this instance. pub fn address(&self) -> AccountHash { self.address } + /// Changes the account hash being used by this instance. pub fn with_address(&mut self, account_hash: AccountHash) { self.address = account_hash; } + /// Returns the entity key being used by this instance. pub fn entity_key(&self) -> &Key { &self.entity_key } + /// Returns the addressable entity being used by this instance. pub fn addressable_entity(&self) -> &AddressableEntity { &self.addressable_entity } + /// Changes the addressable entity being used by this instance. pub fn with_addressable_entity(&mut self, entity: AddressableEntity) { self.addressable_entity = entity; } + /// Returns a reference to the named keys being used by this instance. pub fn named_keys(&self) -> &NamedKeys { &self.named_keys } + /// Returns a mutable reference to the named keys being used by this instance. pub fn named_keys_mut(&mut self) -> &mut NamedKeys { &mut self.named_keys } + /// Returns a reference to the access rights being used by this instance. pub fn access_rights(&self) -> &ContextAccessRights { &self.access_rights } + /// Returns a mutable reference to the access rights being used by this instance. pub fn access_rights_mut(&mut self) -> &mut ContextAccessRights { &mut self.access_rights } + /// Extends the access rights being used by this instance. pub fn extend_access_rights(&mut self, urefs: &[URef]) { self.access_rights.extend(urefs) } + /// Returns the remaining spending limit. pub fn remaining_spending_limit(&self) -> U512 { self.remaining_spending_limit } + /// Set remaining spending limit. pub fn set_remaining_spending_limit(&mut self, remaining: U512) { self.remaining_spending_limit = remaining; } + /// Get references to transfers. pub fn transfers(&self) -> &Vec { &self.transfers } + /// Push transfer instance. pub fn push_transfer(&mut self, transfer: Transfer) { self.transfers.push(transfer); } + /// Get id. pub fn id(&self) -> &Id { &self.id } + /// Get phase. pub fn phase(&self) -> Phase { self.phase } + /// Vesting schedule period in milliseconds. pub fn vesting_schedule_period_millis(&self) -> u64 { self.config.vesting_schedule_period_millis } + /// Are auction bids allowed? pub fn allow_auction_bids(&self) -> bool { self.config.allow_auction_bids } + /// Are rewards computed? pub fn compute_rewards(&self) -> bool { self.config.compute_rewards } + /// Extracts transfer items. pub fn into_transfers(self) -> Vec { self.transfers } diff --git a/storage/src/system/transfer.rs b/storage/src/system/transfer.rs index 633989a5d8..315ae4d5e5 100644 --- a/storage/src/system/transfer.rs +++ b/storage/src/system/transfer.rs @@ -15,6 +15,7 @@ use crate::{ tracking_copy::{TrackingCopy, TrackingCopyEntityExt, TrackingCopyError, TrackingCopyExt}, }; +/// Transfer error. #[derive(Clone, Error, Debug)] pub enum TransferError { /// Invalid key variant. @@ -76,22 +77,10 @@ impl From for TransferError { } } -/// A target mode indicates if a native transfer's arguments will resolve to an existing purse, or -/// will have to create a new account first. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum TransferTargetMode { - /// Unknown target mode. - Unknown, - /// Native transfer arguments resolved into a transfer to a purse. - PurseExists(URef), - /// Native transfer arguments resolved into a transfer to an account. - CreateAccount(AccountHash), -} - /// A target mode indicates if a native transfer's arguments will resolve to an existing purse, or /// will have to create a new account first. #[derive(Copy, Clone, Debug, PartialEq)] -pub enum NewTransferTargetMode { +pub enum TransferTargetMode { /// Native transfer arguments resolved into a transfer to an existing account. ExistingAccount { /// Existing account hash. @@ -110,19 +99,19 @@ pub enum NewTransferTargetMode { CreateAccount(AccountHash), } -impl NewTransferTargetMode { +impl TransferTargetMode { /// Target account hash, if any. pub fn target_account_hash(&self) -> Option { match self { - NewTransferTargetMode::PurseExists { + TransferTargetMode::PurseExists { target_account_hash, .. } => *target_account_hash, - NewTransferTargetMode::ExistingAccount { + TransferTargetMode::ExistingAccount { target_account_hash, .. } => Some(*target_account_hash), - NewTransferTargetMode::CreateAccount(target_account_hash) => Some(*target_account_hash), + TransferTargetMode::CreateAccount(target_account_hash) => Some(*target_account_hash), } } } @@ -311,12 +300,12 @@ impl TransferRuntimeArgsBuilder { /// If the "target" account hash is not existing, then a special variant is returned that /// indicates that the system has to create new account first. /// - /// Returns [`NewTransferTargetMode`] with a resolved variant. + /// Returns [`TransferTargetMode`] with a resolved variant. pub fn resolve_transfer_target_mode( &mut self, protocol_version: ProtocolVersion, tracking_copy: Rc>>, - ) -> Result + ) -> Result where R: StateReader, { @@ -342,7 +331,7 @@ impl TransferRuntimeArgsBuilder { return Err(TransferError::InvalidPurse); } - return Ok(NewTransferTargetMode::PurseExists { + return Ok(TransferTargetMode::PurseExists { purse_uref, target_account_hash, }); @@ -371,12 +360,12 @@ impl TransferRuntimeArgsBuilder { { Ok((_, entity)) => { let main_purse_addable = entity.main_purse().with_access_rights(AccessRights::ADD); - Ok(NewTransferTargetMode::ExistingAccount { + Ok(TransferTargetMode::ExistingAccount { target_account_hash: account_hash, main_purse: main_purse_addable, }) } - Err(_) => Ok(NewTransferTargetMode::CreateAccount(account_hash)), + Err(_) => Ok(TransferTargetMode::CreateAccount(account_hash)), } } @@ -425,23 +414,24 @@ impl TransferRuntimeArgsBuilder { where R: StateReader, { - let (to, target) = - match self.resolve_transfer_target_mode(protocol_version, Rc::clone(&tracking_copy))? { - NewTransferTargetMode::ExistingAccount { - main_purse: purse_uref, - target_account_hash: target_account, - } => (Some(target_account), purse_uref), - NewTransferTargetMode::PurseExists { - target_account_hash, - purse_uref, - } => (target_account_hash, purse_uref), - NewTransferTargetMode::CreateAccount(_) => { - // Method "build()" is called after `resolve_transfer_target_mode` is first called - // and handled by creating a new account. Calling `resolve_transfer_target_mode` - // for the second time should never return `CreateAccount` variant. - return Err(TransferError::InvalidOperation); - } - }; + let (to, target) = match self + .resolve_transfer_target_mode(protocol_version, Rc::clone(&tracking_copy))? + { + TransferTargetMode::ExistingAccount { + main_purse: purse_uref, + target_account_hash: target_account, + } => (Some(target_account), purse_uref), + TransferTargetMode::PurseExists { + target_account_hash, + purse_uref, + } => (target_account_hash, purse_uref), + TransferTargetMode::CreateAccount(_) => { + // Method "build()" is called after `resolve_transfer_target_mode` is first called + // and handled by creating a new account. Calling `resolve_transfer_target_mode` + // for the second time should never return `CreateAccount` variant. + return Err(TransferError::InvalidOperation); + } + }; let source = self.resolve_source_uref(from, entity_named_keys, Rc::clone(&tracking_copy))?; diff --git a/storage/src/tracking_copy/ext.rs b/storage/src/tracking_copy/ext.rs index 21bcc66712..243dcf59de 100644 --- a/storage/src/tracking_copy/ext.rs +++ b/storage/src/tracking_copy/ext.rs @@ -104,10 +104,9 @@ pub trait TrackingCopyExt { /// Gets a package by hash. fn get_package(&mut self, package_hash: PackageHash) -> Result; - fn get_legacy_contract( - &mut self, - legacy_contract: ContractHash, - ) -> Result; + + /// Get a Contract record. + fn get_contract(&mut self, contract_hash: ContractHash) -> Result; /// Gets the system entity registry. fn get_system_entity_registry(&self) -> Result; @@ -652,13 +651,10 @@ where } } - fn get_legacy_contract( - &mut self, - legacy_contract: ContractHash, - ) -> Result { - let key = Key::Hash(legacy_contract.value()); + fn get_contract(&mut self, contract_hash: ContractHash) -> Result { + let key = Key::Hash(contract_hash.value()); match self.read(&key)? { - Some(StoredValue::Contract(legacy_contract)) => Ok(legacy_contract), + Some(StoredValue::Contract(contract)) => Ok(contract), Some(other) => Err(Self::Error::TypeMismatch(StoredValueTypeMismatch::new( "Contract".to_string(), other.type_name(), diff --git a/storage/src/tracking_copy/ext_entity.rs b/storage/src/tracking_copy/ext_entity.rs index 1aceaa1115..2f560259ac 100644 --- a/storage/src/tracking_copy/ext_entity.rs +++ b/storage/src/tracking_copy/ext_entity.rs @@ -25,9 +25,13 @@ use crate::{ /// Fees purse handling. #[derive(Debug, Clone, PartialEq, Eq)] pub enum FeesPurseHandling { + /// Transfer fees to proposer. ToProposer(AccountHash), + /// Transfer all fees to a system-wide accumulation purse, for future disbursement. Accumulate, + /// Burn all fees. Burn, + /// No fees are charged. None(URef), } @@ -76,6 +80,8 @@ pub trait TrackingCopyEntityExt { entity_addr: EntityAddr, named_keys: NamedKeys, ) -> Result<(), Self::Error>; + + /// Migrate entry points from Contract to top level entries. fn migrate_entry_points( &mut self, entity_addr: EntityAddr, @@ -92,12 +98,14 @@ pub trait TrackingCopyEntityExt { stored_value: StoredValue, ) -> Result<(), Self::Error>; + /// Migrate Account to AddressableEntity. fn migrate_account( &mut self, account_hash: AccountHash, protocol_version: ProtocolVersion, ) -> Result<(), Self::Error>; + /// Create an addressable entity to receive transfer to a non-existent addressable entity. fn create_new_addressable_entity_on_transfer( &mut self, account_hash: AccountHash, @@ -105,11 +113,14 @@ pub trait TrackingCopyEntityExt { protocol_version: ProtocolVersion, ) -> Result<(), Self::Error>; + /// Create an addressable entity instance using the field data of an account instance. fn create_addressable_entity_from_account( &mut self, account: Account, protocol_version: ProtocolVersion, ) -> Result<(), Self::Error>; + + /// Migrate ContractPackage to Package. fn migrate_package( &mut self, legacy_package_key: Key, diff --git a/storage/src/tracking_copy/mod.rs b/storage/src/tracking_copy/mod.rs index e0e2283ab8..42a00711a8 100644 --- a/storage/src/tracking_copy/mod.rs +++ b/storage/src/tracking_copy/mod.rs @@ -250,6 +250,7 @@ impl + Copy + Default> GenericTrackingCopyCache { self.reads_cached.get_refresh(key).map(|v| &*v) } + /// Get cached items by prefix. fn get_muts_cached_by_byte_prefix(&self, prefix: &[u8]) -> Vec { self.muts_cached .range(prefix.to_vec()..) @@ -258,6 +259,7 @@ impl + Copy + Default> GenericTrackingCopyCache { .collect() } + /// Does the prune cache contain key. pub fn is_pruned(&self, key: &Key) -> bool { self.prunes_cached.contains(key) } @@ -448,6 +450,7 @@ where self.cache.clone() } + /// Destructure cached entries. pub fn destructure(self) -> (Vec<(Key, StoredValue)>, BTreeSet, Effects) { let (writes, prunes) = self.cache.into_muts(); let writes: Vec<(Key, StoredValue)> = writes.into_iter().map(|(k, v)| (k.0, v)).collect(); @@ -455,6 +458,7 @@ where (writes, prunes, self.effects) } + /// Get record by key. pub fn get(&mut self, key: &Key) -> Result, TrackingCopyError> { if let Some(value) = self.cache.get(key) { return Ok(Some(value.to_owned())); @@ -477,6 +481,7 @@ where self.get_by_byte_prefix(&[*key_tag as u8]) } + /// Get keys by prefix. pub fn get_keys_by_prefix( &self, key_prefix: &KeyPrefix, diff --git a/types/CHANGELOG.md b/types/CHANGELOG.md index 80b37f6393..b04b30a7fc 100644 --- a/types/CHANGELOG.md +++ b/types/CHANGELOG.md @@ -1,205 +1,424 @@ # Changelog -All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog]. +All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog]. -[comment]: <> (Added: new features) -[comment]: <> (Changed: changes in existing functionality) +[comment]: <> (Added: new features) +[comment]: <> (Changed: changes in existing functionality) [comment]: <> (Deprecated: soon-to-be removed features) -[comment]: <> (Removed: now removed features) -[comment]: <> (Fixed: any bug fixes) -[comment]: <> (Security: in case of vulnerabilities) - - +[comment]: <> (Removed: now removed features) +[comment]: <> (Fixed: any bug fixes) +[comment]: <> (Security: in case of vulnerabilities) ## [Unreleased] (node 2.0) ### Added -* Add new `EntryPointType::Install`, `EntryPointType::Normal`, `EntryPointAccess::Abstract` variants to support implementation of a factory pattern. -* Add new types (`types/src/binary_port`) to support the BinaryPort interface +- enum EntityKind +- enum addressable_entity::EntityKindTag +- enum EntityAddr +- struct addressable_entity::NamedKeyAddr +- struct addressable_entity::NamedKeyValue +- struct addressable_entity::MessageTopics +- enum addressable_entity::MessageTopicError +- struct AddressableEntity +- struct addressable_entity::ActionThresholds +- enum addressable_entity::ActionType +- struct addressable_entity::AssociatedKeys +- struct contract::EntryPoint +- enum EntryPointType +- enum EntryPointPayment +- struct EntryPoint +- enum EntryPointAddr +- enum EntryPointValue +- enum addressable_entity::FromAccountHashStrError +- enum addressable_entity::SetThresholdFailure +- struct addressable_entity::TryFromSliceForAccountHashError +- struct addressable_entity::NamedKeys +- struct BlockV1 +- struct BlockBodyV1 +- struct BlockV2 +- struct BlockHeaderV2 +- struct BlockBodyV2 +- struct ChainNameDigest +- enum EraEnd +- struct EraEndV1 +- struct EraEndV2 +- struct EraReport +- enum FinalitySignature +- struct FinalitySignatureV1 +- struct FinalitySignatureV2 +- struct FinalitySignatureId +- struct JsonBlockWithSignatures +- struct RewardedSignatures +- struct SingleBlockRewardedSignatures +- enum Rewards +- struct SignedBlock +- enum SignedBlockHeaderValidationError +- struct SignedBlockHeader +- enum BlockValidationError (moved from casper-node) +- enum Block (don't confuse with previous `Block` struct, see `Changed` section for details) +- enum BlockHeader (don't confuse with previous `BlockHeader` struct, see `Changed` section for details) +- struct HoldsEpoch +- struct addressable_entity::TryFromSliceForContractHashError +- enum addressable_entity::FromStrError +- enum contract_messages::FromStrError +- enum ByteCodeAddr +- struct ByteCodeHash +- enum ByteCodeKind +- struct ByteCode +- struct Chainspec +- struct AccountsConfig +- struct AccountConfig +- struct DelegatorConfig +- struct GenesisValidator +- struct AdministratorAccount +- enum GenesisAccount +- struct ValidatorConfig +- enum ActivationPoint +- struct ChainspecRawBytes +- struct CoreConfig +- enum ConsensusProtocolName +- enum LegacyRequiredFinality +- enum FeeHandling +- struct GenesisConfig +- struct GenesisConfigBuilder +- struct GlobalStateUpdateConfig +- struct GlobalStateUpdate +- enum GlobalStateUpdateError +- struct HighwayConfig +- enum HoldBalanceHandling +- struct NetworkConfig +- struct NextUpgrade +- enum PricingHandling +- struct ProtocolConfig +- enum RefundHandling +- struct TransactionConfig +- struct TransactionLimitsDefinition +- struct TransactionV1Config +- struct ProtocolUpgradeConfig +- struct VacancyConfig +- struct AuctionCosts +- struct ChainspecRegistry +- struct HandlePaymentCosts +- struct HostFunctionCosts +- struct MessageLimits +- struct MintCosts +- struct BrTableCost +- struct ControlFlowCosts +- struct OpcodeCosts +- struct StandardPaymentCosts +- struct StorageCosts +- struct SystemConfig +- struct WasmConfig +- struct WasmV1Config +- struct ChecksumRegistry +- struct SystemEntityRegistry +- struct contract_messages::MessageAddr +- type contract_messages::Messages +- struct contract_messages::MessageChecksum +- enum contract_messages::MessagePayload +- struct contract_messages::Message +- struct contract_messages::TopicNameHash +- struct contract_messages::MessageTopicSummary +- struct Contract +- struct EntryPoints +- struct Digest +- struct DigestError +- struct ChunkWithProof +- enum MerkleConstructionError +- enum MerkleVerificationError +- struct IndexedMerkleProof +- struct DisplayIter +- struct execution::Effects; +- enum execution::ExecutionResult (not to be confused with previous `ExecutionResult`, see `Changed` secion for details) +- struct execution::ExecutionResultV2 +- struct execution::TransformV2 +- struct execution::TransformError +- struct execution::TransformInstruction +- struct execution::TransformKindV2 +- struct execution::PaymentInfo +- enum global_state::TrieMerkleProofStep +- enum global_state::TrieMerkleProof +- struct Pointer +- trait GasLimited +- enum AddressableEntityIdentifier +- struct Approval +- struct ApprovalsHash +- enum InvalidDeploy +- enum DeployBuilder +- enum DeployBuilderError +- enum DeployDecodeFromJsonError +- struct ExecutableDeployItem, +- enum ExecutableDeployItemIdentifier +- struct ExecutionInfo +- enum InitiatorAddr, +- enum InvalidTransaction, +- enum InvalidTransactionV1 +- enum PackageIdentifier +- enum PricingMode +- enum PricingModeError +- enum Transaction +- enum TransactionEntryPoint, +- enum TransactionHash +- struct TransactionId +- enum TransactionInvocationTarget +- enum TransactionRuntime +- enum TransactionScheduling +- enum TransactionTarget +- struct TransactionV1, +- struct TransactionV1Payload, +- struct TransactionV1Hash +- struct TransactionV1Builder +- enum TransactionV1BuilderError +- enum TransactionV1DecodeFromJsonError +- enum TransactionV1Error +- struct TransactionV1ExcessiveSizeError +- enum TransferTarget +- struct TransferV2 +- enum ValidatorChange +- type contracts::ProtocolVersionMajor +- type EntityVersion +- struct EntityVersionKey +- struct EntityVersions +- struct PackageHash +- enum PackageStatus +- struct Package +- struct PeerEntry +- struct Peers +- enum system::auction::BidAddr +- enum system::auction::BidAddrTag +- enum system::auction::BidKind +- enum system::auction::BidKindTag +- enum system::auction::Bridge +- enum system::auction::Reservation +- enum system::auction::ValidatorBid; +- enum system::auction::ValidatorBids +- enum system::auction::DelegatorBids +- enum system::auction::ValidatorCredits +- enum system::auction::Staking +- trait system::auction::BidsExt +- enum system::auction::Error has new variants: ForgedReference, MissingPurse, ValidatorBidExistsAlready,BridgeRecordChainTooLong,UnexpectedBidVariant, DelegationAmountTooLarge +- enum system::CallerTag +- enum system::Caller +- enum system::handle_payment::Error +- enum system::handle_payment::Error has new variants IncompatiblePaymentSettings, UnexpectedKeyVariant +- enum system::mint::BalanceHoldAddrTag +- enum system::mint::Error has new variant: ForgedReference +- enum system::reservation::ReservationKind +- method CLValue::to_t +- function handle_stored_dictionary_value +- in arg_handling namespace functions: has_valid_activate_bid_args, has_valid_add_bid_args, has_valid_change_bid_public_key_args, has_valid_delegate_args, has_valid_redelegate_args, has_valid_transfer_args, has_valid_undelegate_args, has_valid_withdraw_bid_args, new_add_bid_args, new_delegate_args, new_redelegate_args, new_transfer_args, new_undelegate_args, new_withdraw_bid_args +- methods in ContractWasm: `new` and `take_bytes` +- method `lock_status` in struct ContractPackage +- function bytesrepr::allocate_buffer_for_size(expected_size: usize) -> Result, Error> +- Enum EntryPointAccess has new variant `Template` added + +### Changed + +- pub enum ApiError has new variants: MessageTopicAlreadyRegistered, MaxTopicsNumberExceeded, MaxTopicNameSizeExceeded, MessageTopicNotRegistered, MessageTopicFull, MessageTooLarge, MaxMessagesPerBlockExceeded,NotAllowedToAddContractVersion,InvalidDelegationAmountLimits,InvalidCallerInfoRequest +- struct AuctionState#bids is now a BTreeMap instead of Vec. This field is still serialized as an array. Due to this change the elements of the array will have more fields than before (added `validator_public_key`, `vesting_schedule`). +- Variants of enum EntryPointType changed +- Struct Parameter moved from contracts to addressable_entity::entry_points +- struct EraId has new methods `iter_range_inclusive`, `increment` +- struct ExecutionEffect moved to module execution::execution_result_v1 +- enum OpKind moved to module execution::execution_result_v1 +- struct Operation moved to module execution::execution_result_v1 +- enum Transform changed name to TransformKindV1, moved to module execution::execution_result_v1 and has new variants (WriteAddressableEntity, Prune, WriteBidKind) +- enum ExecutionResult changed name to ExecutionResultV1, moved to module execution::execution_result_v1 +- struct TransformEntry changed name to TransformV1 and moved to module execution::execution_result_v1 +- moved NamedKey to module execution::execution_result_v1 +- KeyTag::SystemContractRegistry variant changed name to KeyTag::SystemEntityRegistry +- variants for KeyTag enum: BidAddr = 15, Package = 16, AddressableEntity = 17, ByteCode = 18, Message = 19, NamedKey = 20, BlockGlobal = 21, BalanceHold = 22, EntryPoint = 23, +- enum Key::SystemContractRegistry changed name to Key::SystemEntityRegistry +- variants for enum Key: BidAddr, Package, AddressableEntity, ByteCode, Message, NamedKey, BlockGlobal, BalanceHold, EntryPoint, +- struct ExcessiveSizeError changed name to DeployExcessiveSizeError +- struct Transfer changed name to TransferV1 +- enum GlobalStateIdentifier +- enum StoredValue has new variants: LegacyTransfer, AddressableEntity, BidKind, Package, ByteCode, MessageTopic, Message, NamedKey,Reservation,EntryPoint, +- enum system::SystemContractType changed name to system::SystemEntityType +- enum system::handle_payment::Error variant SystemFunctionCalledByUserAccount changed to InvalidCaller +- struct EntryPoint has a new field `entry_point_payment` +- struct BlockHeader was renamed to BlockHeaderV1 and used as a variant in enum BlockHeader +- struct Block was renamed to BlockV1 and used as a variant in enum Block +- Gas::from_motes now takes `u8` instead of `u64` as second parameter +### Removed + +- type Groups (there is now a struct with that name) +- type EntryPointsMap +- type NamedKeys +- methods `groups_mut`, `add_group`, `lookup_contract_hash`, `is_version_enabled`, `is_contract_enabled`, `insert_contract_version`, `disable_contract_version`, `enable_contract_version`, `enabled_versions`, `remove_group`, `next_contract_version_for`, `current_contract_version`, `current_contract_hash` in struct ContractPackage +- in enum StoredValue removed variant Transfer (replaced with LegacyTransfer) ## [Unreleased] (node 1.5.4) ### Changed -* Remove filesystem I/O functionality from the `std` feature, and gated this behind a new feature `std-fs-io` which depends upon `std`. - +- Remove filesystem I/O functionality from the `std` feature, and gated this behind a new feature `std-fs-io` which depends upon `std`. ## 4.0.1 ### Added -* Add a new `SyncHandling` enum, which allows a node to opt out of historical sync. + +- Add a new `SyncHandling` enum, which allows a node to opt out of historical sync. ### Changed -* Update `k256` to version 0.13.1. + +- Update `k256` to version 0.13.1. ### Removed -* Remove `ExecutionResult::successful_transfers`. -### Security -* Update `ed25519-dalek` to version 2.0.0 as mitigation for [RUSTSEC-2022-0093](https://rustsec.org/advisories/RUSTSEC-2022-0093) +- Remove `ExecutionResult::successful_transfers`. +### Security +- Update `ed25519-dalek` to version 2.0.0 as mitigation for [RUSTSEC-2022-0093](https://rustsec.org/advisories/RUSTSEC-2022-0093) ## 3.0.0 ### Added -* Add new `bytesrepr::Error::NotRepresentable` error variant that represents values that are not representable by the serialization format. -* Add new `Key::Unbond` key variant under which the new unbonding information (to support redelegation) is written. -* Add new `Key::ChainspecRegistry` key variant under which the `ChainspecRegistry` is written. -* Add new `Key::ChecksumRegistry` key variant under which a registry of checksums for a given block is written. There are two checksums in the registry, one for the execution results and the other for the approvals of all deploys in the block. -* Add new `StoredValue::Unbonding` variant to support redelegating. -* Add a new type `WithdrawPurses` which is meant to represent `UnbondingPurses` as they exist in current live networks. + +- Add new `bytesrepr::Error::NotRepresentable` error variant that represents values that are not representable by the serialization format. +- Add new `Key::Unbond` key variant under which the new unbonding information (to support redelegation) is written. +- Add new `Key::ChainspecRegistry` key variant under which the `ChainspecRegistry` is written. +- Add new `Key::ChecksumRegistry` key variant under which a registry of checksums for a given block is written. There are two checksums in the registry, one for the execution results and the other for the approvals of all deploys in the block. +- Add new `StoredValue::Unbonding` variant to support redelegating. +- Add a new type `WithdrawPurses` which is meant to represent `UnbondingPurses` as they exist in current live networks. ### Changed -* Extend `UnbondingPurse` to take a new field `new_validator` which represents the validator to whom tokens will be re-delegated. -* Increase `DICTIONARY_ITEM_KEY_MAX_LENGTH` to 128. -* Change prefix of formatted string representation of `ContractPackageHash` from "contract-package-wasm" to "contract-package-". Parsing from the old format is still supported. -* Apply `#[non_exhaustive]` to error enums. -* Change Debug output of `DeployHash` to hex-encoded string rather than a list of integers. -### Fixed -* Fix some integer casts, where failure is now detected and reported via new error variant `NotRepresentable`. +- Extend `UnbondingPurse` to take a new field `new_validator` which represents the validator to whom tokens will be re-delegated. +- Increase `DICTIONARY_ITEM_KEY_MAX_LENGTH` to 128. +- Change prefix of formatted string representation of `ContractPackageHash` from "contract-package-wasm" to "contract-package-". Parsing from the old format is still supported. +- Apply `#[non_exhaustive]` to error enums. +- Change Debug output of `DeployHash` to hex-encoded string rather than a list of integers. +### Fixed +- Fix some integer casts, where failure is now detected and reported via new error variant `NotRepresentable`. ## 2.0.0 ### Fixed -* Republish v1.6.0 as v2.0.0 due to missed breaking change in API (addition of new variant to `Key`). - +- Republish v1.6.0 as v2.0.0 due to missed breaking change in API (addition of new variant to `Key`). ## 1.6.0 [YANKED] ### Added -* Extend asymmetric key functionality, available via feature `std` (moved from `casper-nodes` crate). -* Provide `Timestamp` and `TimeDiff` types for time operations, with extended functionality available via feature `std` (moved from `casper-nodes` crate). -* Provide test-only functionality, in particular a seedable RNG `TestRng` which outputs its seed on test failure. Available via a new feature `testing`. -* Add new `Key::EraSummary` key variant under which the era summary info is written on each switch block execution. -### Deprecated -* Deprecate `gens` feature: its functionality is included in the new `testing` feature. +- Extend asymmetric key functionality, available via feature `std` (moved from `casper-nodes` crate). +- Provide `Timestamp` and `TimeDiff` types for time operations, with extended functionality available via feature `std` (moved from `casper-nodes` crate). +- Provide test-only functionality, in particular a seedable RNG `TestRng` which outputs its seed on test failure. Available via a new feature `testing`. +- Add new `Key::EraSummary` key variant under which the era summary info is written on each switch block execution. +### Deprecated +- Deprecate `gens` feature: its functionality is included in the new `testing` feature. ## 1.5.0 ### Added -* Provide types and functionality to support improved access control inside execution engine. -* Provide `CLTyped` impl for `ContractPackage` to allow it to be passed into contracts. -### Fixed -* Limit parsing of CLTyped objects to a maximum of 50 types deep. +- Provide types and functionality to support improved access control inside execution engine. +- Provide `CLTyped` impl for `ContractPackage` to allow it to be passed into contracts. +### Fixed +- Limit parsing of CLTyped objects to a maximum of 50 types deep. ## 1.4.6 - 2021-12-29 ### Changed -* Disable checksummed-hex encoding, but leave checksummed-hex decoding in place. - +- Disable checksummed-hex encoding, but leave checksummed-hex decoding in place. ## 1.4.5 - 2021-12-06 ### Added -* Add function to `auction::MintProvider` trait to support minting into an existing purse. -### Changed -* Change checksummed hex implementation to use 32 byte rather than 64 byte blake2b digests. +- Add function to `auction::MintProvider` trait to support minting into an existing purse. +### Changed +- Change checksummed hex implementation to use 32 byte rather than 64 byte blake2b digests. ## [1.4.4] - 2021-11-18 ### Fixed -* Revert the accidental change to the `std` feature causing a broken build when this feature is enabled. - +- Revert the accidental change to the `std` feature causing a broken build when this feature is enabled. ## [1.4.3] - 2021-11-17 [YANKED] - - ## [1.4.2] - 2021-11-13 [YANKED] ### Added -* Add checksummed hex encoding following a scheme similar to [EIP-55](https://eips.ethereum.org/EIPS/eip-55). - +- Add checksummed hex encoding following a scheme similar to [EIP-55](https://eips.ethereum.org/EIPS/eip-55). ## [1.4.1] - 2021-10-23 No changes. - - ## [1.4.0] - 2021-10-21 [YANKED] ### Added -* Add `json-schema` feature, disabled by default, to enable many types to be used to produce JSON-schema data. -* Add implicit `datasize` feature, disabled by default, to enable many types to derive the `DataSize` trait. -* Add `StoredValue` types to this crate. + +- Add `json-schema` feature, disabled by default, to enable many types to be used to produce JSON-schema data. +- Add implicit `datasize` feature, disabled by default, to enable many types to derive the `DataSize` trait. +- Add `StoredValue` types to this crate. ### Changed -* Support building and testing using stable Rust. -* Allow longer hex string to be presented in `json` files. Current maximum is increased from 100 to 150 characters. -* Improve documentation and `Debug` impls for `ApiError`. -### Deprecated -* Feature `std` is deprecated as it is now a no-op, since there is no benefit to linking the std lib via this crate. +- Support building and testing using stable Rust. +- Allow longer hex string to be presented in `json` files. Current maximum is increased from 100 to 150 characters. +- Improve documentation and `Debug` impls for `ApiError`. +### Deprecated +- Feature `std` is deprecated as it is now a no-op, since there is no benefit to linking the std lib via this crate. ## [1.3.0] - 2021-07-19 ### Changed -* Restrict summarization when JSON pretty-printing to contiguous long hex strings. -* Update pinned version of Rust to `nightly-2021-06-17`. -### Removed -* Remove ability to clone `SecretKey`s. +- Restrict summarization when JSON pretty-printing to contiguous long hex strings. +- Update pinned version of Rust to `nightly-2021-06-17`. +### Removed +- Remove ability to clone `SecretKey`s. ## [1.2.0] - 2021-05-27 ### Changed -* Change to Apache 2.0 license. -* Return a `Result` from the constructor of `SecretKey` rather than potentially panicking. -* Improve `Key` error reporting and tests. -### Fixed -* Fix `Key` deserialization. +- Change to Apache 2.0 license. +- Return a `Result` from the constructor of `SecretKey` rather than potentially panicking. +- Improve `Key` error reporting and tests. +### Fixed +- Fix `Key` deserialization. ## [1.1.1] - 2021-04-19 No changes. - - ## [1.1.0] - 2021-04-13 [YANKED] No changes. - - ## [1.0.1] - 2021-04-08 No changes. - - ## [1.0.0] - 2021-03-30 ### Added -* Initial release of types for use by software compatible with Casper mainnet. - +- Initial release of types for use by software compatible with Casper mainnet. [Keep a Changelog]: https://keepachangelog.com/en/1.0.0 [unreleased]: https://github.com/casper-network/casper-node/compare/24fc4027a...dev diff --git a/types/Cargo.toml b/types/Cargo.toml index 5a684732fe..1db7598bb8 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -7,7 +7,7 @@ description = "Types shared by many casper crates for use on the Casper network. readme = "README.md" documentation = "https://docs.rs/casper-types" homepage = "https://casperlabs.io" -repository = "https://github.com/CasperLabs/casper-node/tree/master/types" +repository = "https://github.com/casper-network/casper-node/tree/master/types" license = "Apache-2.0" [dependencies] @@ -18,7 +18,7 @@ bincode = { version = "1.3.1", optional = true } blake2 = { version = "0.9.0", default-features = false } datasize = { version = "0.2.15", optional = true } derp = { version = "0.0.14", optional = true } -ed25519-dalek = { version = "2.0.0", default-features = false, features = ["alloc", "zeroize"] } +ed25519-dalek = { version = "2.1.1", default-features = false, features = ["alloc", "zeroize"] } getrandom = { version = "0.2.0", features = ["rdrand", "js"], optional = true } hex = { version = "0.4.2", default-features = false, features = ["alloc"] } hex_fmt = "0.3.0" @@ -27,7 +27,7 @@ itertools = { version = "0.10.3", default-features = false } libc = { version = "0.2.146", optional = true, default-features = false } k256 = { version = "0.13.1", default-features = false, features = ["ecdsa", "sha256"] } num = { version = "0.4.0", default-features = false, features = ["alloc"] } -num-derive = { version = "0.3.0", default-features = false } +num-derive = { version = "0.4.2", default-features = false } num-integer = { version = "0.1.42", default-features = false } num-rational = { version = "0.4.0", default-features = false, features = ["serde"] } num-traits = { version = "0.2.10", default-features = false } diff --git a/types/README.md b/types/README.md index 46f14ea228..5a95924a6a 100644 --- a/types/README.md +++ b/types/README.md @@ -5,7 +5,7 @@ [![Build Status](https://drone-auto-casper-network.casperlabs.io/api/badges/casper-network/casper-node/status.svg?branch=dev)](http://drone-auto-casper-network.casperlabs.io/casper-network/casper-node) [![Crates.io](https://img.shields.io/crates/v/casper-types)](https://crates.io/crates/casper-types) [![Documentation](https://docs.rs/casper-types/badge.svg)](https://docs.rs/casper-types) -[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/CasperLabs/casper-node/blob/master/LICENSE) +[![License](https://img.shields.io/badge/license-Apache-blue)](https://github.com/casper-network/casper-node/blob/master/LICENSE) Types shared by many casper crates for use on the Casper network. diff --git a/types/src/account/account_hash.rs b/types/src/account/account_hash.rs index 0d0e0a73f6..00bf784ca5 100644 --- a/types/src/account/account_hash.rs +++ b/types/src/account/account_hash.rs @@ -65,6 +65,11 @@ impl AccountHash { ) } + /// Hexadecimal representation of the hash. + pub fn to_hex_string(&self) -> String { + base16::encode_lower(&self.0) + } + /// Parses a string formatted as per `Self::to_formatted_string()` into an `AccountHash`. pub fn from_formatted_str(input: &str) -> Result { let remainder = input diff --git a/types/src/addressable_entity.rs b/types/src/addressable_entity.rs index b21d1afe39..efd1cd1aab 100644 --- a/types/src/addressable_entity.rs +++ b/types/src/addressable_entity.rs @@ -79,13 +79,24 @@ pub const MAX_GROUPS: u8 = 10; /// Maximum number of URefs which can be assigned across all user groups. pub const MAX_TOTAL_UREFS: usize = 100; -const ADDRESSABLE_ENTITY_STRING_PREFIX: &str = "addressable-entity-"; - -const ENTITY_PREFIX: &str = "entity-"; -const ACCOUNT_ENTITY_PREFIX: &str = "account-"; -const CONTRACT_ENTITY_PREFIX: &str = "contract-"; -const SYSTEM_ENTITY_PREFIX: &str = "system-"; -const NAMED_KEY_PREFIX: &str = "named-key-"; +/// The prefix applied to the hex-encoded `Addressable Entity` to produce a formatted string +/// representation. +pub const ADDRESSABLE_ENTITY_STRING_PREFIX: &str = "addressable-entity-"; +/// The prefix applied to the hex-encoded `Entity` to produce a formatted string +/// representation. +pub const ENTITY_PREFIX: &str = "entity-"; +/// The prefix applied to the hex-encoded `Account` to produce a formatted string +/// representation. +pub const ACCOUNT_ENTITY_PREFIX: &str = "account-"; +/// The prefix applied to the hex-encoded `Smart contract` to produce a formatted string +/// representation. +pub const CONTRACT_ENTITY_PREFIX: &str = "contract-"; +/// The prefix applied to the hex-encoded `System entity account or contract` to produce a formatted +/// string representation. +pub const SYSTEM_ENTITY_PREFIX: &str = "system-"; +/// The prefix applied to the hex-encoded `Named Key` to produce a formatted string +/// representation. +pub const NAMED_KEY_PREFIX: &str = "named-key-"; /// Set of errors which may happen when working with contract headers. #[derive(Debug, PartialEq, Eq)] @@ -279,6 +290,11 @@ impl AddressableEntityHash { ) } + /// Hexadecimal representation of the hash. + pub fn to_hex_string(&self) -> String { + base16::encode_lower(&self.0) + } + /// Parses a string formatted as per `Self::to_formatted_string()` into a /// `AddressableEntityHash`. pub fn from_formatted_str(input: &str) -> Result { diff --git a/types/src/block.rs b/types/src/block.rs index 623c552a82..f1d77d9f11 100644 --- a/types/src/block.rs +++ b/types/src/block.rs @@ -445,25 +445,44 @@ impl Block { Block::V1(_) => false, Block::V2(block_v2) => { let mint_count = block_v2.mint().count(); - if mint_count as u64 >= transaction_config.transaction_v1_config.native_mint_lane[4] + if mint_count as u64 + >= transaction_config + .transaction_v1_config + .native_mint_lane + .max_transaction_count() { return true; } let auction_count = block_v2.auction().count(); if auction_count as u64 - >= transaction_config.transaction_v1_config.native_auction_lane[4] + >= transaction_config + .transaction_v1_config + .native_auction_lane + .max_transaction_count() { return true; } - for (category, transactions) in block_v2.body.transactions() { + + let install_upgrade_count = block_v2.install_upgrade().count(); + if install_upgrade_count as u64 + >= transaction_config + .transaction_v1_config + .install_upgrade_lane + .max_transaction_count() + { + return true; + } + + for (lane_id, transactions) in block_v2.body.transactions() { let transaction_count = transactions.len(); - if *category < 2 { + if *lane_id < 2 { continue; }; let max_transaction_count = transaction_config .transaction_v1_config - .get_max_transaction_count(*category); + .get_max_transaction_count(*lane_id); + if transaction_count as u64 >= max_transaction_count { return true; } diff --git a/types/src/block/block_body/block_body_v2.rs b/types/src/block/block_body/block_body_v2.rs index 86d02b5c54..8aae7f75d9 100644 --- a/types/src/block/block_body/block_body_v2.rs +++ b/types/src/block/block_body/block_body_v2.rs @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; use crate::{ block::RewardedSignatures, bytesrepr::{self, FromBytes, ToBytes}, - transaction::{TransactionHash, TransactionLane}, - Digest, + Digest, TransactionHash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, LARGE_WASM_LANE_ID, + MEDIUM_WASM_LANE_ID, MINT_LANE_ID, SMALL_WASM_LANE_ID, }; /// The body portion of a block. Version 2. @@ -47,46 +47,33 @@ impl BlockBodyV2 { } } - fn transactions_by_lane(&self, transaction_lane: TransactionLane) -> Vec { - match self.transactions.get(&(transaction_lane as u8)) { + /// Returns the hashes of the transactions within the block filtered by lane_id. + pub fn transaction_by_lane(&self, lane_id: u8) -> impl Iterator { + match self.transactions.get(&lane_id) { Some(transactions) => transactions.to_vec(), None => vec![], } + .into_iter() } /// Returns the hashes of the mint transactions within the block. pub fn mint(&self) -> impl Iterator { - self.transactions_by_lane(TransactionLane::Mint).into_iter() + self.transaction_by_lane(MINT_LANE_ID) } /// Returns the hashes of the auction transactions within the block. pub fn auction(&self) -> impl Iterator { - self.transactions_by_lane(TransactionLane::Auction) - .into_iter() + self.transaction_by_lane(AUCTION_LANE_ID) } /// Returns the hashes of the installer/upgrader transactions within the block. pub fn install_upgrade(&self) -> impl Iterator { - self.transactions_by_lane(TransactionLane::InstallUpgrade) - .into_iter() + self.transaction_by_lane(INSTALL_UPGRADE_LANE_ID) } - /// Returns the hashes of all other transactions within the block. - pub fn large(&self) -> impl Iterator { - self.transactions_by_lane(TransactionLane::Large) - .into_iter() - } - - /// Returns the hashes of all other transactions within the block. - pub fn medium(&self) -> impl Iterator { - self.transactions_by_lane(TransactionLane::Medium) - .into_iter() - } - - /// Returns the hashes of all other transactions within the block. - pub fn small(&self) -> impl Iterator { - self.transactions_by_lane(TransactionLane::Small) - .into_iter() + /// Returns the hashes of the transactions filtered by lane id within the block. + pub fn transactions_by_lane_id(&self, lane_id: u8) -> impl Iterator { + self.transaction_by_lane(lane_id) } /// Returns a reference to the collection of mapped transactions. @@ -143,14 +130,13 @@ impl Display for BlockBodyV2 { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { write!( formatter, - "block body, {} mint, {} auction, {} \ - installer/upgraders, {} large, {} medium, {} small", + "block body, {} mint, {} auction, {} install_upgrade, {} large wasm, {} medium wasm, {} small wasm", self.mint().count(), self.auction().count(), self.install_upgrade().count(), - self.large().count(), - self.medium().count(), - self.small().count() + self.transaction_by_lane(LARGE_WASM_LANE_ID).count(), + self.transaction_by_lane(MEDIUM_WASM_LANE_ID).count(), + self.transaction_by_lane(SMALL_WASM_LANE_ID).count(), ) } } diff --git a/types/src/block/block_hash.rs b/types/src/block/block_hash.rs index f6906c333f..4046169834 100644 --- a/types/src/block/block_hash.rs +++ b/types/src/block/block_hash.rs @@ -1,4 +1,4 @@ -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::fmt::{self, Display, Formatter}; #[cfg(feature = "datasize")] @@ -53,6 +53,11 @@ impl BlockHash { &self.0 } + /// Hexadecimal representation of the hash. + pub fn to_hex_string(&self) -> String { + base16::encode_lower(self.inner()) + } + // This method is not intended to be used by third party crates. #[doc(hidden)] #[cfg(feature = "json-schema")] diff --git a/types/src/block/block_v2.rs b/types/src/block/block_v2.rs index f450653429..7a45122814 100644 --- a/types/src/block/block_v2.rs +++ b/types/src/block/block_v2.rs @@ -19,14 +19,14 @@ use once_cell::sync::OnceCell; use super::{Block, BlockBodyV2, BlockConversionError, RewardedSignatures}; #[cfg(any(all(feature = "std", feature = "testing"), test))] use crate::testing::TestRng; -#[cfg(feature = "json-schema")] -use crate::transaction::{TransactionLane, TransactionV1Hash}; use crate::{ bytesrepr::{self, FromBytes, ToBytes}, transaction::TransactionHash, BlockHash, BlockHeaderV2, BlockValidationError, Digest, EraEndV2, EraId, ProtocolVersion, PublicKey, Timestamp, }; +#[cfg(feature = "json-schema")] +use crate::{TransactionV1Hash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID}; #[cfg(feature = "json-schema")] static BLOCK_V2: Lazy = Lazy::new(|| { @@ -50,18 +50,11 @@ static BLOCK_V2: Lazy = Lazy::new(|| { let installer_upgrader_hashes = vec![TransactionHash::V1(TransactionV1Hash::new( Digest::from([22; Digest::LENGTH]), ))]; - let standard = vec![TransactionHash::V1( - crate::transaction::TransactionV1Hash::new(Digest::from([23; Digest::LENGTH])), - )]; let transactions = { let mut ret = BTreeMap::new(); - ret.insert(TransactionLane::Mint as u8, mint_hashes); - ret.insert(TransactionLane::Auction as u8, auction_hashes); - ret.insert( - TransactionLane::InstallUpgrade as u8, - installer_upgrader_hashes, - ); - ret.insert(TransactionLane::Large as u8, standard); + ret.insert(MINT_LANE_ID, mint_hashes); + ret.insert(AUCTION_LANE_ID, auction_hashes); + ret.insert(INSTALL_UPGRADE_LANE_ID, installer_upgrader_hashes); ret }; let rewarded_signatures = RewardedSignatures::default(); @@ -255,24 +248,14 @@ impl BlockV2 { self.body.auction() } - /// Returns the hashes of the installer/upgrader transactions within the block. + /// Returns the hashes of the install/upgrade wasm transactions within the block. pub fn install_upgrade(&self) -> impl Iterator { self.body.install_upgrade() } - /// Returns the hashes of all other large transactions within the block. - pub fn large(&self) -> impl Iterator { - self.body.large() - } - - /// Returns the hashes of all other medium transactions within the block. - pub fn medium(&self) -> impl Iterator { - self.body.medium() - } - - /// Returns the hashes of all other small transactions within the block. - pub fn small(&self) -> impl Iterator { - self.body.small() + /// Returns the hashes of the transactions filtered by lane id within the block. + pub fn transactions_by_lane_id(&self, lane_id: u8) -> impl Iterator { + self.body.transaction_by_lane(lane_id) } /// Returns all of the transaction hashes in the order in which they were executed. diff --git a/types/src/block/test_block_builder/test_block_v2_builder.rs b/types/src/block/test_block_builder/test_block_v2_builder.rs index 48047dc460..d2c268a2f1 100644 --- a/types/src/block/test_block_builder/test_block_v2_builder.rs +++ b/types/src/block/test_block_builder/test_block_v2_builder.rs @@ -1,15 +1,13 @@ -use core::convert::TryInto; use std::iter; use alloc::collections::BTreeMap; use rand::Rng; use crate::{ - system::auction::ValidatorWeights, - testing::TestRng, - transaction::{Transaction, TransactionLane}, - Block, BlockHash, BlockV2, Digest, EraEndV2, EraId, ProtocolVersion, PublicKey, - RewardedSignatures, Timestamp, U512, + system::auction::ValidatorWeights, testing::TestRng, Block, BlockHash, BlockV2, Digest, + EraEndV2, EraId, ProtocolVersion, PublicKey, RewardedSignatures, Timestamp, Transaction, + TransactionEntryPoint, TransactionTarget, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, + LARGE_WASM_LANE_ID, MEDIUM_WASM_LANE_ID, MINT_LANE_ID, SMALL_WASM_LANE_ID, U512, }; /// A helper to build the blocks with various properties required for tests. @@ -182,30 +180,38 @@ impl TestBlockV2Builder { let mut small_hashes = vec![]; for txn in txns { let txn_hash = txn.hash(); - let lane: TransactionLane = txn - .transaction_lane() - .try_into() - .expect("Expected a valid priority"); - match lane { - TransactionLane::Mint => mint_hashes.push(txn_hash), - TransactionLane::Auction => auction_hashes.push(txn_hash), - TransactionLane::InstallUpgrade => install_upgrade_hashes.push(txn_hash), - TransactionLane::Large => large_hashes.push(txn_hash), - TransactionLane::Medium => medium_hashes.push(txn_hash), - TransactionLane::Small => small_hashes.push(txn_hash), + let lane_id = match txn { + Transaction::Deploy(deploy) => { + if deploy.is_transfer() { + MINT_LANE_ID + } else { + LARGE_WASM_LANE_ID + } + } + Transaction::V1(transaction_v1) => { + let entry_point = transaction_v1.get_transaction_entry_point().unwrap(); + let target = transaction_v1.get_transaction_target().unwrap(); + simplified_calculate_transaction_lane_from_values(&entry_point, &target) + } + }; + match lane_id { + MINT_LANE_ID => mint_hashes.push(txn_hash), + AUCTION_LANE_ID => auction_hashes.push(txn_hash), + INSTALL_UPGRADE_LANE_ID => install_upgrade_hashes.push(txn_hash), + LARGE_WASM_LANE_ID => large_hashes.push(txn_hash), + MEDIUM_WASM_LANE_ID => medium_hashes.push(txn_hash), + SMALL_WASM_LANE_ID => small_hashes.push(txn_hash), + _ => panic!("Invalid lane id"), } } let transactions = { let mut ret = BTreeMap::new(); - ret.insert(TransactionLane::Mint as u8, mint_hashes); - ret.insert(TransactionLane::Auction as u8, auction_hashes); - ret.insert( - TransactionLane::InstallUpgrade as u8, - install_upgrade_hashes, - ); - ret.insert(TransactionLane::Large as u8, large_hashes); - ret.insert(TransactionLane::Medium as u8, medium_hashes); - ret.insert(TransactionLane::Small as u8, small_hashes); + ret.insert(MINT_LANE_ID, mint_hashes); + ret.insert(AUCTION_LANE_ID, auction_hashes); + ret.insert(INSTALL_UPGRADE_LANE_ID, install_upgrade_hashes); + ret.insert(LARGE_WASM_LANE_ID, large_hashes); + ret.insert(MEDIUM_WASM_LANE_ID, medium_hashes); + ret.insert(SMALL_WASM_LANE_ID, small_hashes); ret }; let rewarded_signatures = rewarded_signatures.unwrap_or_default(); @@ -240,6 +246,72 @@ impl TestBlockV2Builder { } } +// A simplified way of calculating transaction lanes. It doesn't take +// into consideration the size of the transaction against the chainspec +// and doesn't take `additional_compufsdetation_factor` into consideration. +// This is only used for tests purposes. +fn simplified_calculate_transaction_lane_from_values( + entry_point: &TransactionEntryPoint, + target: &TransactionTarget, +) -> u8 { + match target { + TransactionTarget::Native => match entry_point { + TransactionEntryPoint::Transfer => MINT_LANE_ID, + TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => AUCTION_LANE_ID, + TransactionEntryPoint::Call => panic!("EntryPointCannotBeCall"), + TransactionEntryPoint::Custom(_) => panic!("EntryPointCannotBeCustom"), + }, + TransactionTarget::Stored { .. } => match entry_point { + TransactionEntryPoint::Custom(_) => LARGE_WASM_LANE_ID, + TransactionEntryPoint::Call + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + panic!("EntryPointMustBeCustom") + } + }, + TransactionTarget::Session { + is_install_upgrade, .. + } => match entry_point { + TransactionEntryPoint::Call => { + if *is_install_upgrade { + INSTALL_UPGRADE_LANE_ID + } else { + LARGE_WASM_LANE_ID + } + } + TransactionEntryPoint::Custom(_) + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + panic!("EntryPointMustBeCall") + } + }, + } +} + fn gen_era_end_v2( rng: &mut TestRng, validator_weights: Option>, diff --git a/types/src/bytesrepr.rs b/types/src/bytesrepr.rs index f335a9af40..2ca8547a0b 100644 --- a/types/src/bytesrepr.rs +++ b/types/src/bytesrepr.rs @@ -110,7 +110,7 @@ pub fn allocate_buffer(to_be_serialized: &T) -> Result, Erro /// Returns a `Vec` initialized with sufficient capacity to hold `expected_size` bytes, /// or an error if the capacity would exceed `u32::max_value()`. pub fn allocate_buffer_for_size(expected_size: usize) -> Result, Error> { - if expected_size > u32::max_value() as usize { + if expected_size > u32::MAX as usize { return Err(Error::OutOfMemory); } Ok(Vec::with_capacity(expected_size)) diff --git a/types/src/chainspec.rs b/types/src/chainspec.rs index 3926792511..b71654dc6d 100644 --- a/types/src/chainspec.rs +++ b/types/src/chainspec.rs @@ -48,6 +48,7 @@ pub use core_config::DEFAULT_FEE_HANDLING; pub use core_config::DEFAULT_REFUND_HANDLING; pub use core_config::{ ConsensusProtocolName, CoreConfig, LegacyRequiredFinality, DEFAULT_GAS_HOLD_INTERVAL, + DEFAULT_MINIMUM_BID_AMOUNT, }; pub use fee_handling::FeeHandling; #[cfg(any(feature = "std", test))] @@ -60,18 +61,19 @@ pub use next_upgrade::NextUpgrade; pub use pricing_handling::PricingHandling; pub use protocol_config::ProtocolConfig; pub use refund_handling::RefundHandling; -pub use transaction_config::{DeployConfig, TransactionConfig, TransactionV1Config}; +pub use transaction_config::{ + DeployConfig, TransactionConfig, TransactionLimitsDefinition, TransactionV1Config, +}; #[cfg(any(feature = "testing", test))] pub use transaction_config::{ - DEFAULT_INSTALL_UPGRADE_GAS_LIMIT, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, - DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES, + DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES, }; pub use upgrade_config::ProtocolUpgradeConfig; pub use vacancy_config::VacancyConfig; pub use vm_config::{ AuctionCosts, BrTableCost, ChainspecRegistry, ControlFlowCosts, HandlePaymentCosts, HostFunction, HostFunctionCost, HostFunctionCosts, MessageLimits, MintCosts, OpcodeCosts, - StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, + StandardPaymentCosts, StorageCosts, SystemConfig, WasmConfig, WasmV1Config, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, }; #[cfg(any(feature = "testing", test))] @@ -86,9 +88,9 @@ pub use vm_config::{ DEFAULT_CONTROL_FLOW_RETURN_OPCODE, DEFAULT_CONTROL_FLOW_SELECT_OPCODE, DEFAULT_CONVERSION_COST, DEFAULT_CURRENT_MEMORY_COST, DEFAULT_DELEGATE_COST, DEFAULT_DIV_COST, DEFAULT_GLOBAL_COST, DEFAULT_GROW_MEMORY_COST, DEFAULT_INTEGER_COMPARISON_COST, - DEFAULT_LOAD_COST, DEFAULT_LOCAL_COST, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_MUL_COST, - DEFAULT_NEW_DICTIONARY_COST, DEFAULT_NOP_COST, DEFAULT_STORE_COST, DEFAULT_TRANSFER_COST, - DEFAULT_UNREACHABLE_COST, DEFAULT_WASM_MAX_MEMORY, + DEFAULT_LOAD_COST, DEFAULT_LOCAL_COST, DEFAULT_MUL_COST, DEFAULT_NEW_DICTIONARY_COST, + DEFAULT_NOP_COST, DEFAULT_STORE_COST, DEFAULT_TRANSFER_COST, DEFAULT_UNREACHABLE_COST, + DEFAULT_V1_MAX_STACK_HEIGHT, DEFAULT_V1_WASM_MAX_MEMORY, }; /// A collection of configuration settings describing the state of the system at genesis and after @@ -128,6 +130,9 @@ pub struct Chainspec { /// Vacancy behavior config #[serde(rename = "vacancy")] pub vacancy_config: VacancyConfig, + + /// Storage costs. + pub storage_costs: StorageCosts, } impl Chainspec { @@ -220,7 +225,7 @@ impl Chainspec { .saturating_sub(self.core_config.gas_hold_interval.millis()) } - /// Is the given transaction category supported. + /// Is the given transaction lane supported. pub fn is_supported(&self, lane: u8) -> bool { self.transaction_config .transaction_v1_config @@ -228,28 +233,28 @@ impl Chainspec { } /// Returns the max serialized for the given category. - pub fn get_max_serialized_length_by_lane(&self, lane: u8) -> u64 { + pub fn get_max_serialized_length_by_category(&self, lane: u8) -> u64 { self.transaction_config .transaction_v1_config .get_max_serialized_length(lane) } /// Returns the max args length for the given category. - pub fn get_max_args_length_lane(&self, lane: u8) -> u64 { + pub fn get_max_args_length_by_category(&self, lane: u8) -> u64 { self.transaction_config .transaction_v1_config .get_max_args_length(lane) } /// Returns the max gas limit for the given category. - pub fn get_max_gas_limit_by_lane(&self, lane: u8) -> u64 { + pub fn get_max_gas_limit_by_category(&self, lane: u8) -> u64 { self.transaction_config .transaction_v1_config - .get_max_gas_limit(lane) + .get_max_transaction_gas_limit(lane) } /// Returns the max transaction count for the given category. - pub fn get_max_transaction_count_by_lane(&self, lane: u8) -> u64 { + pub fn get_max_transaction_count_by_category(&self, lane: u8) -> u64 { self.transaction_config .transaction_v1_config .get_max_transaction_count(lane) @@ -278,6 +283,7 @@ impl Chainspec { wasm_config, system_costs_config, vacancy_config, + storage_costs: rng.gen(), } } @@ -315,7 +321,8 @@ impl ToBytes for Chainspec { self.transaction_config.write_bytes(writer)?; self.wasm_config.write_bytes(writer)?; self.system_costs_config.write_bytes(writer)?; - self.vacancy_config.write_bytes(writer) + self.vacancy_config.write_bytes(writer)?; + self.storage_costs.write_bytes(writer) } fn to_bytes(&self) -> Result, bytesrepr::Error> { @@ -333,6 +340,7 @@ impl ToBytes for Chainspec { + self.wasm_config.serialized_length() + self.system_costs_config.serialized_length() + self.vacancy_config.serialized_length() + + self.storage_costs.serialized_length() } } @@ -346,6 +354,7 @@ impl FromBytes for Chainspec { let (wasm_config, remainder) = WasmConfig::from_bytes(remainder)?; let (system_costs_config, remainder) = SystemConfig::from_bytes(remainder)?; let (vacancy_config, remainder) = VacancyConfig::from_bytes(remainder)?; + let (storage_costs, remainder) = FromBytes::from_bytes(remainder)?; let chainspec = Chainspec { protocol_config, network_config, @@ -355,6 +364,7 @@ impl FromBytes for Chainspec { wasm_config, system_costs_config, vacancy_config, + storage_costs, }; Ok((chainspec, remainder)) } diff --git a/types/src/chainspec/core_config.rs b/types/src/chainspec/core_config.rs index 25e50ed509..f753c8e901 100644 --- a/types/src/chainspec/core_config.rs +++ b/types/src/chainspec/core_config.rs @@ -43,6 +43,9 @@ pub const DEFAULT_FEE_HANDLING: FeeHandling = FeeHandling::NoFee; /// Default allow reservations. pub const DEFAULT_ALLOW_RESERVATIONS: bool = false; +/// Default value for minimum bid amount in motes. +pub const DEFAULT_MINIMUM_BID_AMOUNT: u64 = 1; + /// Default processing hold balance handling. #[allow(unused)] pub const DEFAULT_PROCESSING_HOLD_BALANCE_HANDLING: HoldBalanceHandling = @@ -125,6 +128,9 @@ pub struct CoreConfig { /// The maximum bound of motes that can be delegated to a validator. pub maximum_delegation_amount: u64, + /// The minimum bound of motes that can be bid for a validator. + pub minimum_bid_amount: u64, + /// Global state prune batch size (0 means the feature is off in the current protocol version). pub prune_batch_size: u64, @@ -231,6 +237,7 @@ impl CoreConfig { let minimum_delegation_amount = rng.gen::() as u64; // `maximum_delegation_amount` must be greater than `minimum_delegation_amount`. let maximum_delegation_amount = rng.gen_range(minimum_delegation_amount..u32::MAX as u64); + let minimum_bid_amount = DEFAULT_MINIMUM_BID_AMOUNT; let prune_batch_size = rng.gen_range(0..100); let strict_argument_checking = rng.gen(); let simultaneous_peer_requests = rng.gen_range(3..100); @@ -294,6 +301,7 @@ impl CoreConfig { max_runtime_call_stack_height, minimum_delegation_amount, maximum_delegation_amount, + minimum_bid_amount, prune_batch_size, strict_argument_checking, simultaneous_peer_requests, @@ -339,6 +347,7 @@ impl Default for CoreConfig { max_runtime_call_stack_height: DEFAULT_MAX_RUNTIME_CALL_STACK_HEIGHT, minimum_delegation_amount: 500_000_000_000, maximum_delegation_amount: 1_000_000_000_000_000_000, + minimum_bid_amount: DEFAULT_MINIMUM_BID_AMOUNT, prune_batch_size: 0, strict_argument_checking: false, simultaneous_peer_requests: 5, @@ -386,6 +395,7 @@ impl ToBytes for CoreConfig { buffer.extend(self.max_runtime_call_stack_height.to_bytes()?); buffer.extend(self.minimum_delegation_amount.to_bytes()?); buffer.extend(self.maximum_delegation_amount.to_bytes()?); + buffer.extend(self.minimum_bid_amount.to_bytes()?); buffer.extend(self.prune_batch_size.to_bytes()?); buffer.extend(self.strict_argument_checking.to_bytes()?); buffer.extend(self.simultaneous_peer_requests.to_bytes()?); @@ -429,6 +439,7 @@ impl ToBytes for CoreConfig { + self.max_runtime_call_stack_height.serialized_length() + self.minimum_delegation_amount.serialized_length() + self.maximum_delegation_amount.serialized_length() + + self.minimum_bid_amount.serialized_length() + self.prune_batch_size.serialized_length() + self.strict_argument_checking.serialized_length() + self.simultaneous_peer_requests.serialized_length() @@ -472,6 +483,7 @@ impl FromBytes for CoreConfig { let (max_runtime_call_stack_height, remainder) = u32::from_bytes(remainder)?; let (minimum_delegation_amount, remainder) = u64::from_bytes(remainder)?; let (maximum_delegation_amount, remainder) = u64::from_bytes(remainder)?; + let (minimum_bid_amount, remainder) = u64::from_bytes(remainder)?; let (prune_batch_size, remainder) = u64::from_bytes(remainder)?; let (strict_argument_checking, remainder) = bool::from_bytes(remainder)?; let (simultaneous_peer_requests, remainder) = u8::from_bytes(remainder)?; @@ -510,6 +522,7 @@ impl FromBytes for CoreConfig { max_runtime_call_stack_height, minimum_delegation_amount, maximum_delegation_amount, + minimum_bid_amount, prune_batch_size, strict_argument_checking, simultaneous_peer_requests, diff --git a/types/src/chainspec/genesis_config.rs b/types/src/chainspec/genesis_config.rs index c694939d80..efe48eac5c 100644 --- a/types/src/chainspec/genesis_config.rs +++ b/types/src/chainspec/genesis_config.rs @@ -16,6 +16,8 @@ use crate::{ SystemConfig, WasmConfig, }; +use super::StorageCosts; + /// Default number of validator slots. pub const DEFAULT_VALIDATOR_SLOTS: u32 = 5; /// Default auction delay. @@ -54,6 +56,7 @@ pub struct GenesisConfig { genesis_timestamp_millis: u64, gas_hold_balance_handling: HoldBalanceHandling, gas_hold_interval_millis: u64, + storage_costs: StorageCosts, } impl GenesisConfig { @@ -78,6 +81,7 @@ impl GenesisConfig { genesis_timestamp_millis: u64, gas_hold_balance_handling: HoldBalanceHandling, gas_hold_interval_millis: u64, + storage_costs: StorageCosts, ) -> GenesisConfig { GenesisConfig { accounts, @@ -91,6 +95,7 @@ impl GenesisConfig { genesis_timestamp_millis, gas_hold_balance_handling, gas_hold_interval_millis, + storage_costs, } } @@ -209,6 +214,7 @@ impl Distribution for Standard { let genesis_timestamp_millis = rng.gen(); let gas_hold_balance_handling = rng.gen(); let gas_hold_interval_millis = rng.gen(); + let storage_costs = rng.gen(); GenesisConfig { accounts, @@ -222,6 +228,7 @@ impl Distribution for Standard { genesis_timestamp_millis, gas_hold_balance_handling, gas_hold_interval_millis, + storage_costs, } } } @@ -243,6 +250,7 @@ pub struct GenesisConfigBuilder { genesis_timestamp_millis: Option, gas_hold_balance_handling: Option, gas_hold_interval_millis: Option, + storage_costs: Option, } impl GenesisConfigBuilder { @@ -320,6 +328,12 @@ impl GenesisConfigBuilder { self } + /// Sets the storage_costs handling. + pub fn with_storage_costs(mut self, storage_costs: StorageCosts) -> Self { + self.storage_costs = Some(storage_costs); + self + } + /// Builds a new [`GenesisConfig`] object. pub fn build(self) -> GenesisConfig { GenesisConfig { @@ -344,6 +358,7 @@ impl GenesisConfigBuilder { gas_hold_interval_millis: self .gas_hold_interval_millis .unwrap_or(DEFAULT_GAS_HOLD_INTERVAL_MILLIS), + storage_costs: self.storage_costs.unwrap_or_default(), } } } @@ -357,6 +372,7 @@ impl From<&Chainspec> for GenesisConfig { .map_or(0, |timestamp| timestamp.millis()); let gas_hold_interval_millis = chainspec.core_config.gas_hold_interval.millis(); let gas_hold_balance_handling = chainspec.core_config.gas_hold_balance_handling; + let storage_costs = chainspec.storage_costs; // TODO: maybe construct this instead of accreting the values //GenesisConfigBuilder::new(account,..) @@ -372,6 +388,7 @@ impl From<&Chainspec> for GenesisConfig { .with_genesis_timestamp_millis(genesis_timestamp_millis) .with_gas_hold_balance_handling(gas_hold_balance_handling) .with_gas_hold_interval_millis(gas_hold_interval_millis) + .with_storage_costs(storage_costs) .build() } } diff --git a/types/src/chainspec/transaction_config.rs b/types/src/chainspec/transaction_config.rs index 8c32d67fe1..d80e9aeb84 100644 --- a/types/src/chainspec/transaction_config.rs +++ b/types/src/chainspec/transaction_config.rs @@ -6,6 +6,7 @@ mod transaction_v1_config; use datasize::DataSize; #[cfg(any(feature = "testing", test))] use rand::Rng; +use runtime_config::RuntimeConfig; use serde::{Deserialize, Serialize}; #[cfg(any(feature = "testing", test))] @@ -18,12 +19,9 @@ use crate::{ pub use deploy_config::DeployConfig; #[cfg(any(feature = "testing", test))] pub use deploy_config::DEFAULT_MAX_PAYMENT_MOTES; -pub use runtime_config::RuntimeConfig; -pub use transaction_v1_config::TransactionV1Config; #[cfg(any(feature = "testing", test))] -pub use transaction_v1_config::{ - DEFAULT_INSTALL_UPGRADE_GAS_LIMIT, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, -}; +pub use transaction_v1_config::DEFAULT_LARGE_TRANSACTION_GAS_LIMIT; +pub use transaction_v1_config::{TransactionLimitsDefinition, TransactionV1Config}; /// The default minimum number of motes that can be transferred. pub const DEFAULT_MIN_TRANSFER_MOTES: u64 = 2_500_000_000; diff --git a/types/src/chainspec/transaction_config/deploy_config.rs b/types/src/chainspec/transaction_config/deploy_config.rs index b1aeadcfdc..2a03d28081 100644 --- a/types/src/chainspec/transaction_config/deploy_config.rs +++ b/types/src/chainspec/transaction_config/deploy_config.rs @@ -22,8 +22,6 @@ pub const DEFAULT_MAX_PAYMENT_MOTES: u64 = 2_500_000_000; pub struct DeployConfig { /// Maximum amount any deploy can pay. pub max_payment_cost: Motes, - /// Maximum time to live any deploy can specify. - pub max_dependencies: u8, /// Maximum length in bytes of payment args per deploy. pub payment_args_max_length: u32, /// Maximum length in bytes of session args per deploy. @@ -35,13 +33,11 @@ impl DeployConfig { /// Generates a random instance using a `TestRng`. pub fn random(rng: &mut TestRng) -> Self { let max_payment_cost = Motes::new(rng.gen_range(1_000_000..1_000_000_000)); - let max_dependencies = 0; let payment_args_max_length = rng.gen(); let session_args_max_length = rng.gen(); DeployConfig { max_payment_cost, - max_dependencies, payment_args_max_length, session_args_max_length, } @@ -53,7 +49,6 @@ impl Default for DeployConfig { fn default() -> Self { DeployConfig { max_payment_cost: Motes::new(DEFAULT_MAX_PAYMENT_MOTES), - max_dependencies: 0, payment_args_max_length: 1024, session_args_max_length: 1024, } @@ -63,7 +58,6 @@ impl Default for DeployConfig { impl ToBytes for DeployConfig { fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { self.max_payment_cost.write_bytes(writer)?; - self.max_dependencies.write_bytes(writer)?; self.payment_args_max_length.write_bytes(writer)?; self.session_args_max_length.write_bytes(writer) } @@ -76,7 +70,6 @@ impl ToBytes for DeployConfig { fn serialized_length(&self) -> usize { self.max_payment_cost.value().serialized_length() - + self.max_dependencies.serialized_length() + self.payment_args_max_length.serialized_length() + self.session_args_max_length.serialized_length() } @@ -85,12 +78,10 @@ impl ToBytes for DeployConfig { impl FromBytes for DeployConfig { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { let (max_payment_cost, remainder) = Motes::from_bytes(bytes)?; - let (max_dependencies, remainder) = u8::from_bytes(remainder)?; let (payment_args_max_length, remainder) = u32::from_bytes(remainder)?; let (session_args_max_length, remainder) = u32::from_bytes(remainder)?; let config = DeployConfig { max_payment_cost, - max_dependencies, payment_args_max_length, session_args_max_length, }; diff --git a/types/src/chainspec/transaction_config/transaction_v1_config.rs b/types/src/chainspec/transaction_config/transaction_v1_config.rs index f468a03e57..d423bb225d 100644 --- a/types/src/chainspec/transaction_config/transaction_v1_config.rs +++ b/types/src/chainspec/transaction_config/transaction_v1_config.rs @@ -1,53 +1,214 @@ +use core::cmp; + #[cfg(feature = "datasize")] use datasize::DataSize; +#[cfg(any(feature = "once_cell", test))] +use once_cell::sync::OnceCell; #[cfg(any(feature = "testing", test))] use rand::Rng; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{Error, Unexpected}, + ser::SerializeSeq, + Deserialize, Deserializer, Serialize, Serializer, +}; #[cfg(any(feature = "testing", test))] use crate::testing::TestRng; -#[cfg(any(feature = "testing", test))] -use crate::INSTALL_UPGRADE_LANE_ID; use crate::{ bytesrepr::{self, FromBytes, ToBytes}, - transaction::TransactionLane, + AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, }; -/// Default gas limit of install / upgrade contracts -pub const DEFAULT_INSTALL_UPGRADE_GAS_LIMIT: u64 = 3_500_000_000_000; - /// Default gas limit of standard transactions -pub const DEFAULT_LARGE_TRANSACTION_GAS_LIMIT: u64 = 500_000_000_000; +pub const DEFAULT_LARGE_TRANSACTION_GAS_LIMIT: u64 = 6_000_000_000_000; const DEFAULT_NATIVE_MINT_LANE: [u64; 5] = [0, 1_048_576, 1024, 2_500_000_000, 650]; -const DEFAULT_NATIVE_AUCTION_LANE: [u64; 5] = [1, 1_048_576, 1024, 2_500_000_000, 145]; +const DEFAULT_NATIVE_AUCTION_LANE: [u64; 5] = [1, 1_048_576, 1024, 5_000_000_000_000, 145]; +const DEFAULT_INSTALL_UPGRADE_LANE: [u64; 5] = [2, 1_048_576, 2048, 3_500_000_000_000, 2]; -const KIND: usize = 0; -const MAX_TRANSACTION_LENGTH: usize = 1; -const MAX_TRANSACTION_ARGS_LENGTH: usize = 2; -const MAX_TRANSACTION_GAS_LIMIT: usize = 3; -const MAX_TRANSACTION_COUNT: usize = 4; +const TRANSACTION_ID_INDEX: usize = 0; +const TRANSACTION_LENGTH_INDEX: usize = 1; +const TRANSACTION_ARGS_LENGTH_INDEX: usize = 2; +const TRANSACTION_GAS_LIMIT_INDEX: usize = 3; +const TRANSACTION_COUNT_INDEX: usize = 4; -/// Configuration values associated with V1 Transactions. +/// Structured limits imposed on a transaction lane #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] +pub struct TransactionLimitsDefinition { + /// The lane identifier + pub id: u8, + /// The maximum length of a transaction i bytes + pub max_transaction_length: u64, + /// The maximum number of runtime args + pub max_transaction_args_length: u64, + /// The maximum gas limit + pub max_transaction_gas_limit: u64, + /// The maximum number of transactions + pub max_transaction_count: u64, +} + +impl TryFrom> for TransactionLimitsDefinition { + type Error = TransactionConfigError; + + fn try_from(v: Vec) -> Result { + if v.len() != 5 { + return Err(TransactionConfigError::InvalidArgsProvided); + } + Ok(TransactionLimitsDefinition { + id: v[TRANSACTION_ID_INDEX] as u8, + max_transaction_length: v[TRANSACTION_LENGTH_INDEX], + max_transaction_args_length: v[TRANSACTION_ARGS_LENGTH_INDEX], + max_transaction_gas_limit: v[TRANSACTION_GAS_LIMIT_INDEX], + max_transaction_count: v[TRANSACTION_COUNT_INDEX], + }) + } +} + +impl TransactionLimitsDefinition { + /// Creates a new instance of TransactionLimitsDefinition + pub fn new( + id: u8, + max_transaction_length: u64, + max_transaction_args_length: u64, + max_transaction_gas_limit: u64, + max_transaction_count: u64, + ) -> Self { + Self { + id, + max_transaction_length, + max_transaction_args_length, + max_transaction_gas_limit, + max_transaction_count, + } + } + + fn as_vec(&self) -> Vec { + vec![ + self.id as u64, + self.max_transaction_length, + self.max_transaction_args_length, + self.max_transaction_gas_limit, + self.max_transaction_count, + ] + } + + /// Returns max_transaction_length + pub fn max_transaction_length(&self) -> u64 { + self.max_transaction_length + } + + /// Returns max_transaction_args_length + pub fn max_transaction_args_length(&self) -> u64 { + self.max_transaction_args_length + } + + /// Returns max_transaction_gas_limit + pub fn max_transaction_gas_limit(&self) -> u64 { + self.max_transaction_gas_limit + } + + /// Returns max_transaction_count + pub fn max_transaction_count(&self) -> u64 { + self.max_transaction_count + } + + /// Returns id + pub fn id(&self) -> u8 { + self.id + } +} + +#[derive(Debug, Clone)] +pub enum TransactionConfigError { + InvalidArgsProvided, +} + +/// Configuration values associated with V1 Transactions. +#[derive(Clone, Eq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] // Disallow unknown fields to ensure config files and command-line overrides contain valid keys. #[serde(deny_unknown_fields)] pub struct TransactionV1Config { + #[serde( + serialize_with = "limit_definition_to_vec", + deserialize_with = "vec_to_limit_definition" + )] /// Lane configuration of the native mint interaction. - pub native_mint_lane: Vec, + pub native_mint_lane: TransactionLimitsDefinition, + #[serde( + serialize_with = "limit_definition_to_vec", + deserialize_with = "vec_to_limit_definition" + )] /// Lane configuration for the native auction interaction. - pub native_auction_lane: Vec, - /// Lane configurations for the Wasm based lanes. - pub wasm_lanes: Vec>, + pub native_auction_lane: TransactionLimitsDefinition, + #[serde( + serialize_with = "limit_definition_to_vec", + deserialize_with = "vec_to_limit_definition" + )] + /// Lane configuration for the install/upgrade interaction. + pub install_upgrade_lane: TransactionLimitsDefinition, + #[serde( + serialize_with = "wasm_definitions_to_vec", + deserialize_with = "definition_to_wasms" + )] + /// Lane configurations for Wasm based lanes that are not declared as install/upgrade. + pub wasm_lanes: Vec, + #[cfg_attr(any(all(feature = "std", feature = "once_cell"), test), serde(skip))] + #[cfg_attr( + all(any(feature = "once_cell", test), feature = "datasize"), + data_size(skip) + )] + #[cfg(any(feature = "once_cell", test))] + wasm_lanes_ordered_by_transaction_size: OnceCell>, +} + +impl PartialEq for TransactionV1Config { + fn eq(&self, other: &TransactionV1Config) -> bool { + // Destructure to make sure we don't accidentally omit fields. + let TransactionV1Config { + native_mint_lane, + native_auction_lane, + install_upgrade_lane, + wasm_lanes, + #[cfg(any(feature = "once_cell", test))] + wasm_lanes_ordered_by_transaction_size: _, + } = self; + *native_mint_lane == other.native_mint_lane + && *native_auction_lane == other.native_auction_lane + && *install_upgrade_lane == other.install_upgrade_lane + && *wasm_lanes == other.wasm_lanes + } } impl TransactionV1Config { + /// Cretaes a new instance of TransactionV1Config + pub fn new( + native_mint_lane: TransactionLimitsDefinition, + native_auction_lane: TransactionLimitsDefinition, + install_upgrade_lane: TransactionLimitsDefinition, + wasm_lanes: Vec, + ) -> Self { + #[cfg(any(feature = "once_cell", test))] + let wasm_lanes_ordered_by_transaction_size = OnceCell::with_value( + Self::build_wasm_lanes_ordered_by_transaction_size(wasm_lanes.clone()), + ); + TransactionV1Config { + native_mint_lane, + native_auction_lane, + install_upgrade_lane, + wasm_lanes, + #[cfg(any(feature = "once_cell", test))] + wasm_lanes_ordered_by_transaction_size, + } + } + #[cfg(any(feature = "testing", test))] /// Generates a random instance using a `TestRng`. pub fn random(rng: &mut TestRng) -> Self { let native_mint_lane = DEFAULT_NATIVE_MINT_LANE.to_vec(); let native_auction_lane = DEFAULT_NATIVE_AUCTION_LANE.to_vec(); + let install_upgrade_lane = DEFAULT_INSTALL_UPGRADE_LANE.to_vec(); let mut wasm_lanes = vec![]; for kind in 2..7 { let lane = vec![ @@ -55,144 +216,93 @@ impl TransactionV1Config { rng.gen_range(0..=1_048_576), rng.gen_range(0..=1024), rng.gen_range(0..=2_500_000_000), + rng.gen_range(5..=150), ]; - wasm_lanes.push(lane) + wasm_lanes.push(lane.try_into().unwrap()) } - TransactionV1Config { - native_mint_lane, - native_auction_lane, + TransactionV1Config::new( + native_mint_lane.try_into().unwrap(), + native_auction_lane.try_into().unwrap(), + install_upgrade_lane.try_into().unwrap(), wasm_lanes, - } - } - - /// Returns true if the lane identifier is for either the mint or auction. - pub fn is_native_lane(&self, lane: u8) -> bool { - lane as u64 == DEFAULT_NATIVE_MINT_LANE[0] || lane as u64 == DEFAULT_NATIVE_AUCTION_LANE[0] + ) } - /// Returns the max serialized length of a transaction for the given category. - pub fn get_max_serialized_length(&self, lane: u8) -> u64 { - if !self.is_supported(lane) { - return 0; - } - match lane { - 0 => self.native_mint_lane[MAX_TRANSACTION_LENGTH], - 1 => self.native_auction_lane[MAX_TRANSACTION_LENGTH], - _ => { - match self - .wasm_lanes - .iter() - .find(|wasm_lane: &&Vec| wasm_lane.first() == Some(&(lane as u64))) - { - Some(wasm_lane) => wasm_lane[MAX_TRANSACTION_LENGTH], - None => 0, - } - } + /// Returns the max serialized length of a transaction for the given lane. + pub fn get_max_serialized_length(&self, lane_id: u8) -> u64 { + match lane_id { + MINT_LANE_ID => self.native_mint_lane.max_transaction_length, + AUCTION_LANE_ID => self.native_auction_lane.max_transaction_length, + INSTALL_UPGRADE_LANE_ID => self.install_upgrade_lane.max_transaction_length, + _ => match self.wasm_lanes.iter().find(|lane| lane.id == lane_id) { + Some(wasm_lane) => wasm_lane.max_transaction_length, + None => 0, + }, } } - /// Returns the max serialized args length of a transaction for the given category. - pub fn get_max_args_length(&self, lane: u8) -> u64 { - if !self.is_supported(lane) { - return 0; - } - match lane { - 0 => self.native_mint_lane[MAX_TRANSACTION_ARGS_LENGTH], - 1 => self.native_auction_lane[MAX_TRANSACTION_ARGS_LENGTH], - _ => { - match self - .wasm_lanes - .iter() - .find(|wasm_lane| wasm_lane.first() == Some(&(lane as u64))) - { - Some(wasm_lane) => wasm_lane[MAX_TRANSACTION_ARGS_LENGTH], - None => 0, - } - } + /// Returns the max number of runtime args + pub fn get_max_args_length(&self, lane_id: u8) -> u64 { + match lane_id { + MINT_LANE_ID => self.native_mint_lane.max_transaction_args_length, + AUCTION_LANE_ID => self.native_auction_lane.max_transaction_args_length, + INSTALL_UPGRADE_LANE_ID => self.install_upgrade_lane.max_transaction_args_length, + _ => match self.wasm_lanes.iter().find(|lane| lane.id == lane_id) { + Some(wasm_lane) => wasm_lane.max_transaction_args_length, + None => 0, + }, } } - /// Returns the max gas limit of a transaction for the given category. - pub fn get_max_gas_limit(&self, lane: u8) -> u64 { - if !self.is_supported(lane) { - return 0; - } - match lane { - 0 => self.native_mint_lane[MAX_TRANSACTION_GAS_LIMIT], - 1 => self.native_auction_lane[MAX_TRANSACTION_GAS_LIMIT], - _ => { - match self - .wasm_lanes - .iter() - .find(|wasm_lane| wasm_lane.first() == Some(&(lane as u64))) - { - Some(wasm_lane) => wasm_lane[MAX_TRANSACTION_GAS_LIMIT], - None => 0, - } - } + /// Returns the max gas limit of a transaction for the given lane. + pub fn get_max_transaction_gas_limit(&self, lane_id: u8) -> u64 { + match lane_id { + MINT_LANE_ID => self.native_mint_lane.max_transaction_gas_limit, + AUCTION_LANE_ID => self.native_auction_lane.max_transaction_gas_limit, + INSTALL_UPGRADE_LANE_ID => self.install_upgrade_lane.max_transaction_gas_limit, + _ => match self.wasm_lanes.iter().find(|lane| lane.id == lane_id) { + Some(wasm_lane) => wasm_lane.max_transaction_gas_limit, + None => 0, + }, } } - /// Returns the max gas limit of a transaction for the given category. - pub fn get_max_transaction_count(&self, lane: u8) -> u64 { - if !self.is_supported(lane) { - return 0; - } - match lane { - 0 => self.native_mint_lane[MAX_TRANSACTION_COUNT], - 1 => self.native_auction_lane[MAX_TRANSACTION_COUNT], - _ => { - match self - .wasm_lanes - .iter() - .find(|wasm_lane| wasm_lane.first() == Some(&(lane as u64))) - { - Some(wasm_lane) => wasm_lane[MAX_TRANSACTION_COUNT], - None => 0, - } - } - } - } - - /// Returns true if the given category is supported. - pub fn is_supported(&self, lane: u8) -> bool { - if !self.is_native_lane(lane) { - return self - .wasm_lanes - .iter() - .any(|wasm_lane| wasm_lane.first() == Some(&(lane as u64))); + /// Returns the max transactions count for the given lane. + pub fn get_max_transaction_count(&self, lane_id: u8) -> u64 { + match lane_id { + MINT_LANE_ID => self.native_mint_lane.max_transaction_count, + AUCTION_LANE_ID => self.native_auction_lane.max_transaction_count, + INSTALL_UPGRADE_LANE_ID => self.install_upgrade_lane.max_transaction_count, + _ => match self.wasm_lanes.iter().find(|lane| lane.id == lane_id) { + Some(wasm_lane) => wasm_lane.max_transaction_count, + None => 0, + }, } - - true - } - - /// Returns the max total count for all transactions across all lanes allowed in a block. - pub fn get_max_block_count(&self) -> u64 { - self.native_mint_lane[MAX_TRANSACTION_COUNT] - + self.native_auction_lane[MAX_TRANSACTION_COUNT] - + self - .wasm_lanes - .iter() - .map(|lane| lane[MAX_TRANSACTION_COUNT]) - .sum::() } /// Returns the maximum number of Wasm based transactions across wasm lanes. pub fn get_max_wasm_transaction_count(&self) -> u64 { let mut ret = 0; for lane in self.wasm_lanes.iter() { - ret += lane[MAX_TRANSACTION_COUNT]; + ret += lane.max_transaction_count; } ret } + /// Are the given transaction parameters supported. + pub fn is_supported(&self, lane_id: u8) -> bool { + if !self.is_predefined_lane(lane_id) { + return self.wasm_lanes.iter().any(|lane| lane.id == lane_id); + } + true + } + /// Returns the list of currently supported lane identifiers. - pub fn get_supported_categories(&self) -> Vec { - let mut ret = vec![0, 1]; + pub fn get_supported_lanes(&self) -> Vec { + let mut ret = vec![0, 1, 2]; for lane in self.wasm_lanes.iter() { - let lane_id = lane[KIND] as u8; - ret.push(lane_id); + ret.push(lane.id); } ret } @@ -207,75 +317,126 @@ impl TransactionV1Config { large: Option, ) -> Self { if let Some(mint_count) = mint { - self.native_mint_lane[MAX_TRANSACTION_COUNT] = mint_count; + self.native_mint_lane.max_transaction_count = mint_count; } if let Some(auction_count) = auction { - self.native_auction_lane[MAX_TRANSACTION_COUNT] = auction_count; + self.native_auction_lane.max_transaction_count = auction_count; } - if let Some(install_upgrade_count) = install { - let (index, lane) = self - .wasm_lanes - .iter() - .enumerate() - .find(|(_, lane)| lane.first() == Some(&(INSTALL_UPGRADE_LANE_ID as u64))) - .expect("must get install upgrade lane"); - let mut updated_lane = lane.clone(); - self.wasm_lanes.remove(index); - updated_lane[MAX_TRANSACTION_COUNT] = install_upgrade_count; - self.wasm_lanes.push(updated_lane); + if let Some(install_upgrade) = install { + self.install_upgrade_lane.max_transaction_count = install_upgrade; } if let Some(large_limit) = large { - let (index, lane) = self + for lane in self.wasm_lanes.iter_mut() { + if lane.id == 3 { + lane.max_transaction_count = large_limit; + } + } + } + self + } + + /// Returns the max total count for all transactions across all lanes allowed in a block. + pub fn get_max_block_count(&self) -> u64 { + self.native_mint_lane.max_transaction_count + + self.native_auction_lane.max_transaction_count + + self.install_upgrade_lane.max_transaction_count + + self .wasm_lanes .iter() - .enumerate() - .find(|(_, lane)| lane.first() == Some(&3)) - .expect("must get install upgrade lane"); - let mut updated_lane = lane.clone(); - self.wasm_lanes.remove(index); - updated_lane[MAX_TRANSACTION_COUNT] = large_limit; - self.wasm_lanes.push(updated_lane); + .map(|lane| lane.max_transaction_count) + .sum::() + } + + /// Returns true if the lane identifier is for one of the predefined lanes. + pub fn is_predefined_lane(&self, lane: u8) -> bool { + lane == AUCTION_LANE_ID || lane == MINT_LANE_ID || lane == INSTALL_UPGRADE_LANE_ID + } + + /// Returns a wasm lane id based on the transaction size adjusted by + /// maybe_additional_computation_factor if necessary. + pub fn get_wasm_lane_id( + &self, + transaction_size: u64, + additional_computation_factor: u8, + ) -> Option { + let mut maybe_adequate_lane_index = None; + let buckets = self.get_wasm_lanes_ordered(); + let number_of_lanes = buckets.len(); + for (i, lane) in buckets.iter().enumerate() { + let lane_size = lane.max_transaction_length; + if lane_size >= transaction_size { + maybe_adequate_lane_index = Some(i); + break; + } } - self + if let Some(adequate_lane_index) = maybe_adequate_lane_index { + maybe_adequate_lane_index = Some(cmp::min( + adequate_lane_index + additional_computation_factor as usize, + number_of_lanes - 1, + )); + } + maybe_adequate_lane_index.map(|index| buckets[index].id) + } + + #[allow(unreachable_code)] + //We're allowing unreachable code here because there's a possibility that someone might + // want to use the types crate without once_cell + fn get_wasm_lanes_ordered(&self) -> &Vec { + #[cfg(any(feature = "once_cell", test))] + return self.wasm_lanes_ordered_by_transaction_size.get_or_init(|| { + Self::build_wasm_lanes_ordered_by_transaction_size(self.wasm_lanes.clone()) + }); + &Self::build_wasm_lanes_ordered_by_transaction_size(self.wasm_lanes.clone()) + } + + fn build_wasm_lanes_ordered_by_transaction_size( + wasm_lanes: Vec, + ) -> Vec { + let mut wasm_lanes_ordered_by_transaction_size = wasm_lanes; + wasm_lanes_ordered_by_transaction_size + .sort_by(|a, b| a.max_transaction_length.cmp(&b.max_transaction_length)); + wasm_lanes_ordered_by_transaction_size } } #[cfg(any(feature = "std", test))] impl Default for TransactionV1Config { fn default() -> Self { - let large_lane = vec![ - TransactionLane::Large as u64, + let wasm_lane = vec![ + 3_u64, //large lane id 1_048_576, 1024, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, 10, ]; - let install_upgrade_lane = vec![ - TransactionLane::InstallUpgrade as u64, - 1_048_576, - 2048, - DEFAULT_INSTALL_UPGRADE_GAS_LIMIT, - 2, - ]; - let native_mint_lane = DEFAULT_NATIVE_MINT_LANE.to_vec(); let native_auction_lane = DEFAULT_NATIVE_AUCTION_LANE.to_vec(); - let wasm_lanes = vec![large_lane, install_upgrade_lane]; + let install_upgrade_lane = DEFAULT_INSTALL_UPGRADE_LANE.to_vec(); + let raw_wasm_lanes = vec![wasm_lane]; + let wasm_lanes: Result, _> = + raw_wasm_lanes.into_iter().map(|v| v.try_into()).collect(); - TransactionV1Config { - native_mint_lane, - native_auction_lane, - wasm_lanes, - } + TransactionV1Config::new( + native_mint_lane.try_into().unwrap(), + native_auction_lane.try_into().unwrap(), + install_upgrade_lane.try_into().unwrap(), + wasm_lanes.unwrap(), + ) } } impl ToBytes for TransactionV1Config { fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - self.native_mint_lane.write_bytes(writer)?; - self.native_auction_lane.write_bytes(writer)?; - self.wasm_lanes.write_bytes(writer) + self.native_mint_lane.as_vec().write_bytes(writer)?; + self.native_auction_lane.as_vec().write_bytes(writer)?; + self.install_upgrade_lane.as_vec().write_bytes(writer)?; + let wasm_lanes_as_vecs: Vec> = self + .wasm_lanes + .iter() + .map(TransactionLimitsDefinition::as_vec) + .collect(); + wasm_lanes_as_vecs.write_bytes(writer) } fn to_bytes(&self) -> Result, bytesrepr::Error> { @@ -285,30 +446,119 @@ impl ToBytes for TransactionV1Config { } fn serialized_length(&self) -> usize { - self.native_mint_lane.serialized_length() - + self.native_auction_lane.serialized_length() - + self.wasm_lanes.serialized_length() + let wasm_lanes_as_vecs: Vec> = self + .wasm_lanes + .iter() + .map(TransactionLimitsDefinition::as_vec) + .collect(); + self.native_mint_lane.as_vec().serialized_length() + + self.native_auction_lane.as_vec().serialized_length() + + self.install_upgrade_lane.as_vec().serialized_length() + + wasm_lanes_as_vecs.serialized_length() } } impl FromBytes for TransactionV1Config { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (native_mint_lane, remainder) = FromBytes::from_bytes(bytes)?; - let (native_auction_lane, remainder) = FromBytes::from_bytes(remainder)?; - let (wasm_lanes, remainder) = FromBytes::from_bytes(remainder)?; - let config = TransactionV1Config { + let (raw_native_mint_lane, remainder): (Vec, &[u8]) = FromBytes::from_bytes(bytes)?; + let (raw_native_auction_lane, remainder): (Vec, &[u8]) = + FromBytes::from_bytes(remainder)?; + let (raw_install_upgrade_lane, remainder): (Vec, &[u8]) = + FromBytes::from_bytes(remainder)?; + let (raw_wasm_lanes, remainder): (Vec>, &[u8]) = FromBytes::from_bytes(remainder)?; + let native_mint_lane = raw_native_mint_lane + .try_into() + .map_err(|_| bytesrepr::Error::Formatting)?; + let native_auction_lane = raw_native_auction_lane + .try_into() + .map_err(|_| bytesrepr::Error::Formatting)?; + let install_upgrade_lane = raw_install_upgrade_lane + .try_into() + .map_err(|_| bytesrepr::Error::Formatting)?; + let wasm_lanes: Result, _> = + raw_wasm_lanes.into_iter().map(|v| v.try_into()).collect(); + let config = TransactionV1Config::new( native_mint_lane, native_auction_lane, - wasm_lanes, - }; + install_upgrade_lane, + wasm_lanes.map_err(|_| bytesrepr::Error::Formatting)?, + ); Ok((config, remainder)) } } +fn vec_to_limit_definition<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let vec = Vec::::deserialize(deserializer)?; + let limits = TransactionLimitsDefinition::try_from(vec).map_err(|_| { + D::Error::invalid_value( + Unexpected::Seq, + &"expected 5 u64 compliant numbers to create a TransactionLimitsDefinition", + ) + })?; + Ok(limits) +} + +fn limit_definition_to_vec( + limits: &TransactionLimitsDefinition, + serializer: S, +) -> Result +where + S: Serializer, +{ + let vec = limits.as_vec(); + let mut seq = serializer.serialize_seq(Some(vec.len()))?; + for element in vec { + seq.serialize_element(&element)?; + } + seq.end() +} + +fn definition_to_wasms<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let vec = Vec::>::deserialize(deserializer)?; + let result: Result, TransactionConfigError> = + vec.into_iter().map(|v| v.try_into()).collect(); + result.map_err(|_| { + D::Error::invalid_value( + Unexpected::Seq, + &"sequence of sequences to assemble wasm definitions", + ) + }) +} + +fn wasm_definitions_to_vec( + limits: &[TransactionLimitsDefinition], + serializer: S, +) -> Result +where + S: Serializer, +{ + let vec_of_vecs: Vec> = limits.iter().map(|v| v.as_vec()).collect(); + let mut seq = serializer.serialize_seq(Some(vec_of_vecs.len()))?; + for element in vec_of_vecs { + seq.serialize_element(&element)?; + } + seq.end() +} + #[cfg(test)] mod tests { - use super::*; + use serde_json::Value; + use super::*; + const EXAMPLE_JSON: &str = r#"{ + "native_mint_lane": [0,1,2,3,4], + "native_auction_lane": [1,5,6,7,8], + "install_upgrade_lane": [2,9,10,11,12], + "wasm_lanes": [[3,13,14,15,16], [4,17,18,19,20], [5,21,22,23,24]] + }"#; #[test] fn bytesrepr_roundtrip() { let mut rng = TestRng::new(); @@ -321,7 +571,183 @@ mod tests { let config = TransactionV1Config::default(); assert!(config.is_supported(0)); assert!(config.is_supported(1)); + assert!(config.is_supported(2)); assert!(config.is_supported(3)); assert!(!config.is_supported(10)); } + + #[test] + fn should_get_configuration_for_wasm() { + let config = build_example_transaction_config(); + let got = config.get_wasm_lane_id(100, 0); + assert_eq!(got, Some(3)); + let config = build_example_transaction_config_reverse_wasm_ids(); + let got = config.get_wasm_lane_id(100, 0); + assert_eq!(got, Some(5)); + } + + #[test] + fn given_too_big_transaction_should_return_none() { + let config = build_example_transaction_config(); + let got = config.get_wasm_lane_id(100000000, 0); + assert!(got.is_none()); + let config = build_example_transaction_config_reverse_wasm_ids(); + let got = config.get_wasm_lane_id(100000000, 0); + assert!(got.is_none()); + } + + #[test] + fn given_wasm_should_return_first_fit() { + let config = build_example_transaction_config(); + + let got = config.get_wasm_lane_id(660, 0); + assert_eq!(got, Some(4)); + + let got = config.get_wasm_lane_id(800, 0); + assert_eq!(got, Some(5)); + + let got = config.get_wasm_lane_id(1, 0); + assert_eq!(got, Some(3)); + + let config = build_example_transaction_config_reverse_wasm_ids(); + + let got = config.get_wasm_lane_id(660, 0); + assert_eq!(got, Some(4)); + + let got = config.get_wasm_lane_id(800, 0); + assert_eq!(got, Some(3)); + + let got = config.get_wasm_lane_id(1, 0); + assert_eq!(got, Some(5)); + } + + #[test] + fn given_additional_computation_factor_should_be_applied() { + let config = build_example_transaction_config(); + let got = config.get_wasm_lane_id(660, 1); + assert_eq!(got, Some(5)); + + let config = build_example_transaction_config_reverse_wasm_ids(); + let got = config.get_wasm_lane_id(660, 1); + assert_eq!(got, Some(3)); + } + + #[test] + fn given_additional_computation_factor_should_not_overflow() { + let config = build_example_transaction_config(); + let got = config.get_wasm_lane_id(660, 2); + assert_eq!(got, Some(5)); + let got_2 = config.get_wasm_lane_id(660, 20); + assert_eq!(got_2, Some(5)); + + let config = build_example_transaction_config_reverse_wasm_ids(); + let got = config.get_wasm_lane_id(660, 2); + assert_eq!(got, Some(3)); + let got_2 = config.get_wasm_lane_id(660, 20); + assert_eq!(got_2, Some(3)); + } + + #[test] + fn given_no_wasm_lanes_should_return_none() { + let config = build_example_transaction_config_no_wasms(); + let got = config.get_wasm_lane_id(660, 2); + assert!(got.is_none()); + let got = config.get_wasm_lane_id(660, 0); + assert!(got.is_none()); + let got = config.get_wasm_lane_id(660, 20); + assert!(got.is_none()); + } + + #[test] + fn should_deserialize() { + let got: TransactionV1Config = serde_json::from_str(EXAMPLE_JSON).unwrap(); + let expected = TransactionV1Config::new( + TransactionLimitsDefinition::new(0, 1, 2, 3, 4), + TransactionLimitsDefinition::new(1, 5, 6, 7, 8), + TransactionLimitsDefinition::new(2, 9, 10, 11, 12), + vec![ + TransactionLimitsDefinition::new(3, 13, 14, 15, 16), + TransactionLimitsDefinition::new(4, 17, 18, 19, 20), + TransactionLimitsDefinition::new(5, 21, 22, 23, 24), + ], + ); + assert_eq!(got, expected); + } + + #[test] + fn should_serialize() { + let input = TransactionV1Config::new( + TransactionLimitsDefinition::new(0, 1, 2, 3, 4), + TransactionLimitsDefinition::new(1, 5, 6, 7, 8), + TransactionLimitsDefinition::new(2, 9, 10, 11, 12), + vec![ + TransactionLimitsDefinition::new(3, 13, 14, 15, 16), + TransactionLimitsDefinition::new(4, 17, 18, 19, 20), + TransactionLimitsDefinition::new(5, 21, 22, 23, 24), + ], + ); + let raw = serde_json::to_string(&input).unwrap(); + let got = serde_json::from_str::(&raw).unwrap(); + let expected: Value = serde_json::from_str::(EXAMPLE_JSON).unwrap(); + assert_eq!(got, expected); + } + + fn example_native() -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(0, 1500, 1024, 1_500_000_000, 150) + } + + fn example_auction() -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(1, 500, 3024, 3_500_000_000, 350) + } + + fn example_install_upgrade() -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(2, 10000, 2024, 2_500_000_000, 250) + } + + fn wasm_small(id: u8) -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(id, 600, 4024, 4_500_000_000, 450) + } + + fn wasm_medium(id: u8) -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(id, 700, 5024, 5_500_000_000, 550) + } + + fn wasm_large(id: u8) -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(id, 800, 6024, 6_500_000_000, 650) + } + + fn example_wasm() -> Vec { + vec![wasm_small(3), wasm_medium(4), wasm_large(5)] + } + + fn example_wasm_reversed_ids() -> Vec { + vec![wasm_small(5), wasm_medium(4), wasm_large(3)] + } + + fn build_example_transaction_config_no_wasms() -> TransactionV1Config { + TransactionV1Config::new( + example_native(), + example_auction(), + example_install_upgrade(), + vec![], + ) + } + + fn build_example_transaction_config() -> TransactionV1Config { + TransactionV1Config::new( + example_native(), + example_auction(), + example_install_upgrade(), + example_wasm(), + ) + } + + fn build_example_transaction_config_reverse_wasm_ids() -> TransactionV1Config { + TransactionV1Config::new( + example_native(), + example_auction(), + example_install_upgrade(), + example_wasm_reversed_ids(), + ) + } } diff --git a/types/src/chainspec/vm_config.rs b/types/src/chainspec/vm_config.rs index 0d1bde8821..52c76dc6a1 100644 --- a/types/src/chainspec/vm_config.rs +++ b/types/src/chainspec/vm_config.rs @@ -9,6 +9,7 @@ mod standard_payment_costs; mod storage_costs; mod system_config; mod wasm_config; +mod wasm_v1_config; pub use auction_costs::AuctionCosts; #[cfg(any(feature = "testing", test))] @@ -43,5 +44,6 @@ pub use standard_payment_costs::StandardPaymentCosts; pub use storage_costs::StorageCosts; pub use system_config::SystemConfig; pub use wasm_config::WasmConfig; +pub use wasm_v1_config::WasmV1Config; #[cfg(any(feature = "testing", test))] -pub use wasm_config::{DEFAULT_MAX_STACK_HEIGHT, DEFAULT_WASM_MAX_MEMORY}; +pub use wasm_v1_config::{DEFAULT_V1_MAX_STACK_HEIGHT, DEFAULT_V1_WASM_MAX_MEMORY}; diff --git a/types/src/chainspec/vm_config/auction_costs.rs b/types/src/chainspec/vm_config/auction_costs.rs index d46fb9e6c8..dcc07774e3 100644 --- a/types/src/chainspec/vm_config/auction_costs.rs +++ b/types/src/chainspec/vm_config/auction_costs.rs @@ -15,15 +15,15 @@ pub const DEFAULT_GET_ERA_VALIDATORS_COST: u32 = 10_000; /// Default cost of the `read_seigniorage_recipients` auction entry point. pub const DEFAULT_READ_SEIGNIORAGE_RECIPIENTS_COST: u32 = 10_000; /// Default cost of the `add_bid` auction entry point. -pub const DEFAULT_ADD_BID_COST: u32 = 2_500_000_000; +pub const DEFAULT_ADD_BID_COST: u64 = 2_500_000_000; /// Default cost of the `withdraw_bid` auction entry point. -pub const DEFAULT_WITHDRAW_BID_COST: u32 = DEFAULT_ADD_BID_COST; +pub const DEFAULT_WITHDRAW_BID_COST: u32 = 2_500_000_000; /// Default cost of the `delegate` auction entry point. -pub const DEFAULT_DELEGATE_COST: u32 = DEFAULT_ADD_BID_COST; +pub const DEFAULT_DELEGATE_COST: u32 = DEFAULT_WITHDRAW_BID_COST; /// Default cost of the `redelegate` auction entry point. -pub const DEFAULT_REDELEGATE_COST: u32 = DEFAULT_ADD_BID_COST; +pub const DEFAULT_REDELEGATE_COST: u32 = DEFAULT_WITHDRAW_BID_COST; /// Default cost of the `undelegate` auction entry point. -pub const DEFAULT_UNDELEGATE_COST: u32 = DEFAULT_ADD_BID_COST; +pub const DEFAULT_UNDELEGATE_COST: u32 = DEFAULT_WITHDRAW_BID_COST; /// Default cost of the `run_auction` auction entry point. pub const DEFAULT_RUN_AUCTION_COST: u32 = 10_000; /// Default cost of the `slash` auction entry point. @@ -41,9 +41,9 @@ pub const DEFAULT_ACTIVATE_BID_COST: u32 = 10_000; /// Default cost of the `change_bid_public_key` auction entry point. pub const DEFAULT_CHANGE_BID_PUBLIC_KEY_COST: u64 = 5_000_000_000; /// Default cost of the `add_reservations` auction entry point. -pub const DEFAULT_ADD_RESERVATIONS_COST: u32 = DEFAULT_ADD_BID_COST; +pub const DEFAULT_ADD_RESERVATIONS_COST: u32 = DEFAULT_WITHDRAW_BID_COST; /// Default cost of the `cancel_reservations` auction entry point. -pub const DEFAULT_CANCEL_RESERVATIONS_COST: u32 = DEFAULT_ADD_BID_COST; +pub const DEFAULT_CANCEL_RESERVATIONS_COST: u32 = DEFAULT_WITHDRAW_BID_COST; /// Description of the costs of calling auction entrypoints. #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)] @@ -55,7 +55,7 @@ pub struct AuctionCosts { /// Cost of calling the `read_seigniorage_recipients` entry point. pub read_seigniorage_recipients: u32, /// Cost of calling the `add_bid` entry point. - pub add_bid: u32, + pub add_bid: u64, /// Cost of calling the `withdraw_bid` entry point. pub withdraw_bid: u32, /// Cost of calling the `delegate` entry point. @@ -246,11 +246,12 @@ impl Distribution for Standard { // there's a bug in toml...under the hood it uses an i64 when it should use a u64 // this causes flaky test failures if the random result exceeds i64::MAX let change_bid_public_key = rng.gen::() as u64; + let add_bid = rng.gen::() as u64; AuctionCosts { get_era_validators: rng.gen(), read_seigniorage_recipients: rng.gen(), - add_bid: rng.gen(), + add_bid, withdraw_bid: rng.gen(), delegate: rng.gen(), undelegate: rng.gen(), @@ -280,7 +281,7 @@ pub mod gens { pub fn auction_costs_arb()( get_era_validators in num::u32::ANY, read_seigniorage_recipients in num::u32::ANY, - add_bid in num::u32::ANY, + add_bid in num::u64::ANY, withdraw_bid in num::u32::ANY, delegate in num::u32::ANY, undelegate in num::u32::ANY, diff --git a/types/src/chainspec/vm_config/host_function_costs.rs b/types/src/chainspec/vm_config/host_function_costs.rs index d1b873a429..1ae51ef215 100644 --- a/types/src/chainspec/vm_config/host_function_costs.rs +++ b/types/src/chainspec/vm_config/host_function_costs.rs @@ -156,14 +156,15 @@ where } /// Calculate gas cost for a host function - pub fn calculate_gas_cost(&self, weights: T) -> Gas { + pub fn calculate_gas_cost(&self, weights: T) -> Option { let mut gas = Gas::new(self.cost); for (argument, weight) in self.arguments.as_ref().iter().zip(weights.as_ref()) { let lhs = Gas::new(*argument); let rhs = Gas::new(*weight); - gas += lhs * rhs; + let product = lhs.checked_mul(rhs)?; + gas = gas.checked_add(product)?; } - gas + Some(gas) } } @@ -337,6 +338,8 @@ pub struct HostFunctionCosts { pub manage_message_topic: HostFunction<[Cost; 4]>, /// Cost of calling the `casper_emit_message` host function. pub emit_message: HostFunction<[Cost; 4]>, + /// Cost of calling the `get_block_info` host function. + pub get_block_info: HostFunction<[Cost; 2]>, } impl Zero for HostFunctionCosts { @@ -390,6 +393,7 @@ impl Zero for HostFunctionCosts { manage_message_topic: HostFunction::zero(), emit_message: HostFunction::zero(), cost_increase_per_message: Zero::zero(), + get_block_info: HostFunction::zero(), } } @@ -443,6 +447,7 @@ impl Zero for HostFunctionCosts { enable_contract_version, manage_message_topic, emit_message, + get_block_info, } = self; read_value.is_zero() && dictionary_get.is_zero() @@ -492,6 +497,7 @@ impl Zero for HostFunctionCosts { && emit_message.is_zero() && cost_increase_per_message.is_zero() && add_package_version.is_zero() + && get_block_info.is_zero() } } @@ -679,6 +685,7 @@ impl Default for HostFunctionCosts { ], ), cost_increase_per_message: DEFAULT_COST_INCREASE_PER_MESSAGE_EMITTED, + get_block_info: HostFunction::new(DEFAULT_FIXED_COST, [NOT_USED, NOT_USED]), } } } @@ -734,6 +741,7 @@ impl ToBytes for HostFunctionCosts { ret.append(&mut self.manage_message_topic.to_bytes()?); ret.append(&mut self.emit_message.to_bytes()?); ret.append(&mut self.cost_increase_per_message.to_bytes()?); + ret.append(&mut self.get_block_info.to_bytes()?); Ok(ret) } @@ -786,6 +794,7 @@ impl ToBytes for HostFunctionCosts { + self.manage_message_topic.serialized_length() + self.emit_message.serialized_length() + self.cost_increase_per_message.serialized_length() + + self.get_block_info.serialized_length() } } @@ -839,6 +848,7 @@ impl FromBytes for HostFunctionCosts { let (manage_message_topic, rem) = FromBytes::from_bytes(rem)?; let (emit_message, rem) = FromBytes::from_bytes(rem)?; let (cost_increase_per_message, rem) = FromBytes::from_bytes(rem)?; + let (get_block_info, rem) = FromBytes::from_bytes(rem)?; Ok(( HostFunctionCosts { read_value, @@ -889,6 +899,7 @@ impl FromBytes for HostFunctionCosts { manage_message_topic, emit_message, cost_increase_per_message, + get_block_info, }, rem, )) @@ -947,6 +958,7 @@ impl Distribution for Standard { manage_message_topic: rng.gen(), emit_message: rng.gen(), cost_increase_per_message: rng.gen(), + get_block_info: rng.gen(), } } } @@ -1014,6 +1026,7 @@ pub mod gens { manage_message_topic in host_function_cost_arb(), emit_message in host_function_cost_arb(), cost_increase_per_message in num::u32::ANY, + get_block_info in host_function_cost_arb(), ) -> HostFunctionCosts { HostFunctionCosts { read_value, @@ -1064,6 +1077,7 @@ pub mod gens { manage_message_topic, emit_message, cost_increase_per_message, + get_block_info } } } @@ -1088,7 +1102,7 @@ mod tests { + (ARGUMENT_COSTS[2] * WEIGHTS[2]); assert_eq!( host_function.calculate_gas_cost(WEIGHTS), - Gas::new(expected_cost) + Some(Gas::new(expected_cost)) ); } @@ -1107,7 +1121,7 @@ mod tests { let large_value = U512::from(large_value); let rhs = large_value + (U512::from(4) * large_value * large_value); - assert_eq!(lhs, Gas::new(rhs)); + assert_eq!(lhs, Some(Gas::new(rhs))); } } diff --git a/types/src/chainspec/vm_config/opcode_costs.rs b/types/src/chainspec/vm_config/opcode_costs.rs index d87caf0c04..d17dcfcc37 100644 --- a/types/src/chainspec/vm_config/opcode_costs.rs +++ b/types/src/chainspec/vm_config/opcode_costs.rs @@ -72,6 +72,8 @@ pub const DEFAULT_CONTROL_FLOW_DROP_OPCODE: u32 = 440; pub const DEFAULT_CONTROL_FLOW_BR_TABLE_OPCODE: u32 = 35_000; /// Default multiplier for the size of targets in `br_table` Wasm opcode. pub const DEFAULT_CONTROL_FLOW_BR_TABLE_MULTIPLIER: u32 = 100; +/// Default cost of the sign extension opcodes +pub const DEFAULT_SIGN_COST: u32 = 300; /// Definition of a cost table for a Wasm `br_table` opcode. /// @@ -438,6 +440,8 @@ pub struct OpcodeCosts { pub grow_memory: u32, /// Control flow operations multiplier. pub control_flow: ControlFlowCosts, + /// Sign ext operations costs + pub sign: u32, } impl Default for OpcodeCosts { @@ -459,6 +463,7 @@ impl Default for OpcodeCosts { current_memory: DEFAULT_CURRENT_MEMORY_COST, grow_memory: DEFAULT_GROW_MEMORY_COST, control_flow: ControlFlowCosts::default(), + sign: DEFAULT_SIGN_COST, } } } @@ -483,6 +488,7 @@ impl Distribution for Standard { current_memory: rng.gen(), grow_memory: rng.gen(), control_flow: rng.gen(), + sign: rng.gen(), } } } @@ -508,6 +514,7 @@ impl ToBytes for OpcodeCosts { current_memory, grow_memory, control_flow, + sign, } = self; ret.append(&mut bit.to_bytes()?); @@ -526,6 +533,7 @@ impl ToBytes for OpcodeCosts { ret.append(&mut current_memory.to_bytes()?); ret.append(&mut grow_memory.to_bytes()?); ret.append(&mut control_flow.to_bytes()?); + ret.append(&mut sign.to_bytes()?); Ok(ret) } @@ -548,6 +556,7 @@ impl ToBytes for OpcodeCosts { current_memory, grow_memory, control_flow, + sign, } = self; bit.serialized_length() + add.serialized_length() @@ -565,6 +574,7 @@ impl ToBytes for OpcodeCosts { + current_memory.serialized_length() + grow_memory.serialized_length() + control_flow.serialized_length() + + sign.serialized_length() } } @@ -586,6 +596,7 @@ impl FromBytes for OpcodeCosts { let (current_memory, bytes): (_, &[u8]) = FromBytes::from_bytes(bytes)?; let (grow_memory, bytes): (_, &[u8]) = FromBytes::from_bytes(bytes)?; let (control_flow, bytes): (_, &[u8]) = FromBytes::from_bytes(bytes)?; + let (sign, bytes): (_, &[u8]) = FromBytes::from_bytes(bytes)?; let opcode_costs = OpcodeCosts { bit, @@ -604,6 +615,7 @@ impl FromBytes for OpcodeCosts { current_memory, grow_memory, control_flow, + sign, }; Ok((opcode_costs, bytes)) } @@ -628,6 +640,7 @@ impl Zero for OpcodeCosts { current_memory: 0, grow_memory: 0, control_flow: ControlFlowCosts::zero(), + sign: 0, } } @@ -649,6 +662,7 @@ impl Zero for OpcodeCosts { current_memory, grow_memory, control_flow, + sign, } = self; bit.is_zero() && add.is_zero() @@ -666,6 +680,7 @@ impl Zero for OpcodeCosts { && current_memory.is_zero() && grow_memory.is_zero() && control_flow.is_zero() + && sign.is_zero() } } @@ -738,6 +753,7 @@ pub mod gens { current_memory in num::u32::ANY, grow_memory in num::u32::ANY, control_flow in control_flow_cost_arb(), + sign in num::u32::ANY, ) -> OpcodeCosts { OpcodeCosts { bit, @@ -756,6 +772,7 @@ pub mod gens { current_memory, grow_memory, control_flow, + sign, } } } diff --git a/types/src/chainspec/vm_config/storage_costs.rs b/types/src/chainspec/vm_config/storage_costs.rs index b696911498..67df0a563b 100644 --- a/types/src/chainspec/vm_config/storage_costs.rs +++ b/types/src/chainspec/vm_config/storage_costs.rs @@ -99,6 +99,7 @@ pub mod tests { use crate::U512; use super::*; + use proptest::prelude::*; const SMALL_WEIGHT: usize = 123456789; const LARGE_WEIGHT: usize = usize::MAX; @@ -122,22 +123,24 @@ pub mod tests { let expected_cost = U512::from(DEFAULT_GAS_PER_BYTE_COST) * U512::from(LARGE_WEIGHT); assert_eq!(cost, Gas::new(expected_cost)); } + + proptest! { + #[test] + fn bytesrepr_roundtrip(storage_costs in super::gens::storage_costs_arb()) { + bytesrepr::test_serialization_roundtrip(&storage_costs); + } + } } #[doc(hidden)] -#[cfg(any(feature = "gens", test))] +#[cfg(test)] pub mod gens { - use proptest::{num, prop_compose}; + use crate::gens::example_u32_arb; use super::StorageCosts; + use proptest::prelude::*; - prop_compose! { - pub fn storage_costs_arb()( - gas_per_byte in num::u32::ANY, - ) -> StorageCosts { - StorageCosts { - gas_per_byte, - } - } + pub(super) fn storage_costs_arb() -> impl Strategy { + example_u32_arb().prop_map(StorageCosts::new) } } diff --git a/types/src/chainspec/vm_config/wasm_config.rs b/types/src/chainspec/vm_config/wasm_config.rs index fe432b1da6..e273a6c7b7 100644 --- a/types/src/chainspec/vm_config/wasm_config.rs +++ b/types/src/chainspec/vm_config/wasm_config.rs @@ -10,131 +10,73 @@ use serde::{Deserialize, Serialize}; use crate::{ bytesrepr::{self, FromBytes, ToBytes}, - chainspec::vm_config::{HostFunctionCosts, MessageLimits, OpcodeCosts, StorageCosts}, + chainspec::vm_config::MessageLimits, }; -/// Default maximum number of pages of the Wasm memory. -pub const DEFAULT_WASM_MAX_MEMORY: u32 = 64; -/// Default maximum stack height. -pub const DEFAULT_MAX_STACK_HEIGHT: u32 = 500; +use super::wasm_v1_config::WasmV1Config; /// Configuration of the Wasm execution environment. /// /// This structure contains various Wasm execution configuration options, such as memory limits, /// stack limits and costs. -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[serde(deny_unknown_fields)] pub struct WasmConfig { - /// Maximum amount of heap memory (represented in 64kB pages) each contract can use. - pub max_memory: u32, - /// Max stack height (native WebAssembly stack limiter). - pub max_stack_height: u32, - /// Wasm opcode costs table. - opcode_costs: OpcodeCosts, - /// Storage costs. - storage_costs: StorageCosts, - /// Host function costs table. - host_function_costs: HostFunctionCosts, /// Messages limits. messages_limits: MessageLimits, + /// Configuration for wasms in v1 execution engine. + v1: WasmV1Config, } impl WasmConfig { /// Creates new Wasm config. - pub const fn new( - max_memory: u32, - max_stack_height: u32, - opcode_costs: OpcodeCosts, - storage_costs: StorageCosts, - host_function_costs: HostFunctionCosts, - messages_limits: MessageLimits, - ) -> Self { + pub const fn new(messages_limits: MessageLimits, v1: WasmV1Config) -> Self { Self { - max_memory, - max_stack_height, - opcode_costs, - storage_costs, - host_function_costs, messages_limits, + v1, } } - /// Returns opcode costs. - pub fn opcode_costs(&self) -> OpcodeCosts { - self.opcode_costs - } - - /// Returns storage costs. - pub fn storage_costs(&self) -> StorageCosts { - self.storage_costs - } - - /// Returns host function costs and consumes this object. - pub fn take_host_function_costs(self) -> HostFunctionCosts { - self.host_function_costs - } - /// Returns the limits config for messages. pub fn messages_limits(&self) -> MessageLimits { self.messages_limits } -} -impl Default for WasmConfig { - fn default() -> Self { - Self { - max_memory: DEFAULT_WASM_MAX_MEMORY, - max_stack_height: DEFAULT_MAX_STACK_HEIGHT, - opcode_costs: OpcodeCosts::default(), - storage_costs: StorageCosts::default(), - host_function_costs: HostFunctionCosts::default(), - messages_limits: MessageLimits::default(), - } + /// Returns the config for v1 wasms. + pub fn v1(&self) -> &WasmV1Config { + &self.v1 + } + + /// Returns mutable v1 reference + #[cfg(any(feature = "testing", test))] + pub fn v1_mut(&mut self) -> &mut WasmV1Config { + &mut self.v1 } } impl ToBytes for WasmConfig { fn to_bytes(&self) -> Result, bytesrepr::Error> { let mut ret = bytesrepr::unchecked_allocate_buffer(self); - - ret.append(&mut self.max_memory.to_bytes()?); - ret.append(&mut self.max_stack_height.to_bytes()?); - ret.append(&mut self.opcode_costs.to_bytes()?); - ret.append(&mut self.storage_costs.to_bytes()?); - ret.append(&mut self.host_function_costs.to_bytes()?); ret.append(&mut self.messages_limits.to_bytes()?); - + ret.append(&mut self.v1.to_bytes()?); Ok(ret) } fn serialized_length(&self) -> usize { - self.max_memory.serialized_length() - + self.max_stack_height.serialized_length() - + self.opcode_costs.serialized_length() - + self.storage_costs.serialized_length() - + self.host_function_costs.serialized_length() - + self.messages_limits.serialized_length() + self.messages_limits.serialized_length() + self.v1.serialized_length() } } impl FromBytes for WasmConfig { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (max_memory, rem) = FromBytes::from_bytes(bytes)?; - let (max_stack_height, rem) = FromBytes::from_bytes(rem)?; - let (opcode_costs, rem) = FromBytes::from_bytes(rem)?; - let (storage_costs, rem) = FromBytes::from_bytes(rem)?; - let (host_function_costs, rem) = FromBytes::from_bytes(rem)?; - let (messages_limits, rem) = FromBytes::from_bytes(rem)?; + let (messages_limits, rem) = FromBytes::from_bytes(bytes)?; + let (v1, rem) = FromBytes::from_bytes(rem)?; Ok(( WasmConfig { - max_memory, - max_stack_height, - opcode_costs, - storage_costs, - host_function_costs, messages_limits, + v1, }, rem, )) @@ -145,12 +87,20 @@ impl FromBytes for WasmConfig { impl Distribution for Standard { fn sample(&self, rng: &mut R) -> WasmConfig { WasmConfig { - max_memory: rng.gen(), - max_stack_height: rng.gen(), - opcode_costs: rng.gen(), - storage_costs: rng.gen(), - host_function_costs: rng.gen(), messages_limits: rng.gen(), + v1: rng.gen(), + } + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use proptest::prelude::*; + proptest! { + #[test] + fn bytesrepr_roundtrip(wasm_config in super::gens::wasm_config_arb()) { + bytesrepr::test_serialization_roundtrip(&wasm_config); } } } @@ -158,33 +108,23 @@ impl Distribution for Standard { #[doc(hidden)] #[cfg(any(feature = "gens", test))] pub mod gens { - use proptest::{num, prop_compose}; + use proptest::prop_compose; use crate::{ chainspec::vm_config::{ - host_function_costs::gens::host_function_costs_arb, - message_limits::gens::message_limits_arb, opcode_costs::gens::opcode_costs_arb, - storage_costs::gens::storage_costs_arb, + message_limits::gens::message_limits_arb, wasm_v1_config::gens::wasm_v1_config_arb, }, WasmConfig, }; prop_compose! { pub fn wasm_config_arb() ( - max_memory in num::u32::ANY, - max_stack_height in num::u32::ANY, - opcode_costs in opcode_costs_arb(), - storage_costs in storage_costs_arb(), - host_function_costs in host_function_costs_arb(), + v1 in wasm_v1_config_arb(), messages_limits in message_limits_arb(), ) -> WasmConfig { WasmConfig { - max_memory, - max_stack_height, - opcode_costs, - storage_costs, - host_function_costs, messages_limits, + v1, } } } diff --git a/types/src/chainspec/vm_config/wasm_v1_config.rs b/types/src/chainspec/vm_config/wasm_v1_config.rs new file mode 100644 index 0000000000..019a9e4631 --- /dev/null +++ b/types/src/chainspec/vm_config/wasm_v1_config.rs @@ -0,0 +1,174 @@ +use crate::{ + bytesrepr::{self, FromBytes, ToBytes}, + chainspec::vm_config::{HostFunctionCosts, OpcodeCosts}, +}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(any(feature = "testing", test))] +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; +use serde::{Deserialize, Serialize}; + +/// Default maximum number of pages of the Wasm memory. +pub const DEFAULT_V1_WASM_MAX_MEMORY: u32 = 64; +/// Default maximum stack height. +pub const DEFAULT_V1_MAX_STACK_HEIGHT: u32 = 500; + +/// Configuration of the Wasm execution environment for V1 execution machine. +/// +/// This structure contains various Wasm execution configuration options, such as memory limits, +/// stack limits and costs. +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[serde(deny_unknown_fields)] +pub struct WasmV1Config { + /// Maximum amount of heap memory (represented in 64kB pages) each contract can use. + max_memory: u32, + /// Max stack height (native WebAssembly stack limiter). + max_stack_height: u32, + /// Wasm opcode costs table. + opcode_costs: OpcodeCosts, + /// Host function costs table. + host_function_costs: HostFunctionCosts, +} + +impl WasmV1Config { + /// ctor + pub fn new( + max_memory: u32, + max_stack_height: u32, + opcode_costs: OpcodeCosts, + host_function_costs: HostFunctionCosts, + ) -> Self { + WasmV1Config { + max_memory, + max_stack_height, + opcode_costs, + host_function_costs, + } + } + + /// Returns opcode costs. + pub fn opcode_costs(&self) -> OpcodeCosts { + self.opcode_costs + } + + /// Returns host function costs and consumes this object. + pub fn take_host_function_costs(self) -> HostFunctionCosts { + self.host_function_costs + } + + /// Returns max_memory. + pub fn max_memory(&self) -> u32 { + self.max_memory + } + + /// Returns mutable max_memory reference + #[cfg(any(feature = "testing", test))] + pub fn max_memory_mut(&mut self) -> &mut u32 { + &mut self.max_memory + } + + /// Returns mutable max_stack_height reference + #[cfg(any(feature = "testing", test))] + pub fn max_stack_height_mut(&mut self) -> &mut u32 { + &mut self.max_stack_height + } + + /// Returns max_stack_height. + pub fn max_stack_height(&self) -> u32 { + self.max_stack_height + } +} + +impl Default for WasmV1Config { + fn default() -> Self { + Self { + max_memory: DEFAULT_V1_WASM_MAX_MEMORY, + max_stack_height: DEFAULT_V1_MAX_STACK_HEIGHT, + opcode_costs: OpcodeCosts::default(), + host_function_costs: HostFunctionCosts::default(), + } + } +} + +impl ToBytes for WasmV1Config { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::unchecked_allocate_buffer(self); + ret.append(&mut self.max_memory.to_bytes()?); + ret.append(&mut self.max_stack_height.to_bytes()?); + ret.append(&mut self.opcode_costs.to_bytes()?); + ret.append(&mut self.host_function_costs.to_bytes()?); + Ok(ret) + } + + fn serialized_length(&self) -> usize { + self.max_memory.serialized_length() + + self.max_stack_height.serialized_length() + + self.opcode_costs.serialized_length() + + self.host_function_costs.serialized_length() + } +} + +impl FromBytes for WasmV1Config { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (max_memory, rem) = FromBytes::from_bytes(bytes)?; + let (max_stack_height, rem) = FromBytes::from_bytes(rem)?; + let (opcode_costs, rem) = FromBytes::from_bytes(rem)?; + let (host_function_costs, rem) = FromBytes::from_bytes(rem)?; + Ok(( + WasmV1Config { + max_memory, + max_stack_height, + opcode_costs, + host_function_costs, + }, + rem, + )) + } +} + +#[cfg(any(feature = "testing", test))] +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> WasmV1Config { + WasmV1Config { + max_memory: rng.gen(), + max_stack_height: rng.gen(), + opcode_costs: rng.gen(), + host_function_costs: rng.gen(), + } + } +} + +#[doc(hidden)] +#[cfg(any(feature = "gens", test))] +pub mod gens { + use crate::{ + chainspec::vm_config::{ + host_function_costs::gens::host_function_costs_arb, + opcode_costs::gens::opcode_costs_arb, + }, + gens::example_u32_arb, + }; + use proptest::prop_compose; + + use super::WasmV1Config; + + prop_compose! { + pub fn wasm_v1_config_arb() ( + max_memory in example_u32_arb(), + max_stack_height in example_u32_arb(), + opcode_costs in opcode_costs_arb(), + host_function_costs in host_function_costs_arb(), + ) -> WasmV1Config { + WasmV1Config { + max_memory, + max_stack_height, + opcode_costs, + host_function_costs, + } + } + } +} diff --git a/types/src/cl_value.rs b/types/src/cl_value.rs index d3dd7b17a8..c4440faed6 100644 --- a/types/src/cl_value.rs +++ b/types/src/cl_value.rs @@ -1,21 +1,27 @@ -use alloc::{string::String, vec::Vec}; +use alloc::vec::Vec; use core::fmt::{self, Display, Formatter}; +#[cfg(feature = "json-schema")] +use crate::checksummed_hex; +use crate::{ + bytesrepr::{self, Bytes, FromBytes, ToBytes, U32_SERIALIZED_LENGTH}, + CLType, CLTyped, +}; #[cfg(feature = "datasize")] use datasize::DataSize; #[cfg(feature = "json-schema")] use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; -use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(feature = "json-schema")] +use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(feature = "json-schema")] use serde_json::Value; -use crate::{ - bytesrepr::{self, Bytes, FromBytes, ToBytes, U32_SERIALIZED_LENGTH}, - checksummed_hex, CLType, CLTyped, -}; - mod checksum_registry; mod dictionary; +#[cfg(feature = "json-schema")] pub use jsonrepr::cl_value_to_json; +#[cfg(feature = "json-schema")] mod jsonrepr; mod system_entity_registry; @@ -213,12 +219,14 @@ impl JsonSchema for CLValue { #[serde(deny_unknown_fields)] #[cfg_attr(feature = "json-schema", derive(JsonSchema))] #[cfg_attr(feature = "json-schema", schemars(rename = "CLValue"))] +#[cfg(feature = "json-schema")] struct CLValueJson { cl_type: CLType, bytes: String, parsed: Option, } +#[cfg(feature = "json-schema")] impl Serialize for CLValue { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { @@ -234,6 +242,14 @@ impl Serialize for CLValue { } } +#[cfg(not(feature = "json-schema"))] +impl Serialize for CLValue { + fn serialize(&self, serializer: S) -> Result { + (&self.cl_type, &self.bytes).serialize(serializer) + } +} + +#[cfg(feature = "json-schema")] impl<'de> Deserialize<'de> for CLValue { fn deserialize>(deserializer: D) -> Result { let (cl_type, bytes) = if deserializer.is_human_readable() { @@ -252,6 +268,17 @@ impl<'de> Deserialize<'de> for CLValue { } } +#[cfg(not(feature = "json-schema"))] +impl<'de> Deserialize<'de> for CLValue { + fn deserialize>(deserializer: D) -> Result { + let (cl_type, bytes) = <(CLType, Vec)>::deserialize(deserializer)?; + Ok(CLValue { + cl_type, + bytes: bytes.into(), + }) + } +} + #[cfg(test)] mod tests { use alloc::string::ToString; diff --git a/types/src/crypto/asymmetric_key.rs b/types/src/crypto/asymmetric_key.rs index 713af65529..57ba84f0f2 100644 --- a/types/src/crypto/asymmetric_key.rs +++ b/types/src/crypto/asymmetric_key.rs @@ -503,6 +503,11 @@ impl PublicKey { AccountHash::from(self) } + /// Hexadecimal representation of the key. + pub fn to_hex_string(&self) -> String { + self.to_hex() + } + /// Returns `true` if this public key is of the `System` variant. pub fn is_system(&self) -> bool { matches!(self, PublicKey::System) @@ -967,6 +972,11 @@ impl Signature { Signature::Secp256k1(_) => SECP256K1, } } + + /// Hexadecimal representation of the signature. + pub fn to_hex_string(&self) -> String { + self.to_hex() + } } impl AsymmetricType<'_> for Signature { diff --git a/types/src/crypto/asymmetric_key/tests.rs b/types/src/crypto/asymmetric_key/tests.rs index 545b8dad00..c3163420c5 100644 --- a/types/src/crypto/asymmetric_key/tests.rs +++ b/types/src/crypto/asymmetric_key/tests.rs @@ -82,6 +82,7 @@ fn known_secret_key_to_pem(expected_key: &SecretKey, known_key_pem: &str, expect assert_eq!(expected_tag, decoded.tag()); } +#[cfg(any(feature = "std-fs-io", test))] fn secret_key_file_roundtrip(secret_key: SecretKey) { let tempdir = tempfile::tempdir().unwrap(); let path = tempdir.path().join("test_secret_key.pem"); @@ -140,6 +141,7 @@ fn known_public_key_to_pem(known_key_hex: &str, known_key_pem: &str) { assert_eq!(key_bytes, Into::>::into(decoded)); } +#[cfg(any(feature = "std-fs-io", test))] fn public_key_file_roundtrip(public_key: PublicKey) { let tempdir = tempfile::tempdir().unwrap(); let path = tempdir.path().join("test_public_key.pem"); @@ -214,6 +216,7 @@ fn check_ord_and_hash(low: T, high: T) { } mod system { + #[cfg(any(feature = "std-fs-io", test))] use std::path::Path; use super::{sign, verify}; @@ -229,6 +232,7 @@ mod system { assert!(SecretKey::system().to_pem().is_err()); } + #[cfg(any(feature = "std-fs-io", test))] #[test] fn secret_key_to_file_should_error() { assert!(SecretKey::system().to_file(Path::new("/dev/null")).is_err()); @@ -249,6 +253,7 @@ mod system { assert!(PublicKey::system().to_pem().is_err()); } + #[cfg(any(feature = "std-fs-io", test))] #[test] fn public_key_to_file_should_error() { assert!(PublicKey::system().to_file(Path::new("/dev/null")).is_err()); @@ -341,6 +346,7 @@ MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC super::known_secret_key_to_pem(&expected_key, KNOWN_KEY_PEM, ED25519_TAG); } + #[cfg(any(feature = "std-fs-io", test))] #[test] fn secret_key_to_and_from_file() { let mut rng = TestRng::new(); @@ -397,6 +403,7 @@ MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= super::known_public_key_to_pem(KNOWN_KEY_HEX, KNOWN_KEY_PEM); } + #[cfg(any(feature = "std-fs-io", test))] #[test] fn public_key_to_and_from_file() { let mut rng = TestRng::new(); @@ -584,6 +591,7 @@ Yj9oTB9fx9+vvQdxJOhMtu46kGo0Uw== } #[test] + #[cfg(any(feature = "std-fs-io", test))] fn secret_key_to_and_from_file() { let mut rng = TestRng::new(); let secret_key = SecretKey::random_secp256k1(&mut rng); @@ -640,6 +648,7 @@ kv+kBR5u4ISEAkuc2TFWQHX0Yj9oTB9fx9+vvQdxJOhMtu46kGo0Uw== super::known_public_key_to_pem(KNOWN_KEY_HEX, KNOWN_KEY_PEM); } + #[cfg(any(feature = "std-fs-io", test))] #[test] fn public_key_to_and_from_file() { let mut rng = TestRng::new(); diff --git a/types/src/deploy_info.rs b/types/src/deploy_info.rs index a741cf96fd..6ba9436580 100644 --- a/types/src/deploy_info.rs +++ b/types/src/deploy_info.rs @@ -107,14 +107,13 @@ impl ToBytes for DeployInfo { /// Generators for a `DeployInfo` #[cfg(any(feature = "testing", feature = "gens", test))] pub(crate) mod gens { - use proptest::{collection, prelude::Strategy}; - use crate::{ gens::{account_hash_arb, u512_arb, uref_arb}, transaction::gens::deploy_hash_arb, transfer::gens::transfer_v1_addr_arb, DeployInfo, }; + use proptest::{collection, prelude::Strategy}; pub fn deploy_info_arb() -> impl Strategy { let transfers_length_range = 0..5; diff --git a/types/src/execution/execution_result.rs b/types/src/execution/execution_result.rs index b8a0a00711..4c1aa29ade 100644 --- a/types/src/execution/execution_result.rs +++ b/types/src/execution/execution_result.rs @@ -1,4 +1,4 @@ -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; #[cfg(feature = "datasize")] use datasize::DataSize; @@ -53,6 +53,17 @@ impl ExecutionResult { Self::V2(ExecutionResultV2::random(rng)) } } + + /// Returns the error message, if any. + pub fn error_message(&self) -> Option { + match self { + ExecutionResult::V1(v1) => match v1 { + ExecutionResultV1::Failure { error_message, .. } => Some(error_message.clone()), + ExecutionResultV1::Success { .. } => None, + }, + ExecutionResult::V2(v2) => v2.error_message.clone(), + } + } } impl From for ExecutionResult { @@ -68,19 +79,6 @@ impl From for ExecutionResult { } impl ToBytes for ExecutionResult { - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - match self { - ExecutionResult::V1(result) => { - V1_TAG.write_bytes(writer)?; - result.write_bytes(writer) - } - ExecutionResult::V2(result) => { - V2_TAG.write_bytes(writer)?; - result.write_bytes(writer) - } - } - } - fn to_bytes(&self) -> Result, bytesrepr::Error> { let mut buffer = bytesrepr::allocate_buffer(self)?; self.write_bytes(&mut buffer)?; @@ -94,6 +92,19 @@ impl ToBytes for ExecutionResult { ExecutionResult::V2(result) => result.serialized_length(), } } + + fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { + match self { + ExecutionResult::V1(result) => { + V1_TAG.write_bytes(writer)?; + result.write_bytes(writer) + } + ExecutionResult::V2(result) => { + V2_TAG.write_bytes(writer)?; + result.write_bytes(writer) + } + } + } } impl FromBytes for ExecutionResult { diff --git a/types/src/execution/execution_result_v2.rs b/types/src/execution/execution_result_v2.rs index 5a686b3ee0..ce2f9cb77b 100644 --- a/types/src/execution/execution_result_v2.rs +++ b/types/src/execution/execution_result_v2.rs @@ -146,7 +146,9 @@ impl ExecutionResultV2 { let range = limit.value().as_u64(); // can range from 0 to limit - let consumed = limit - Gas::new(rng.gen_range(0..=range)); + let consumed = limit + .checked_sub(Gas::new(rng.gen_range(0..=range))) + .expect("consumed"); let size_estimate = rng.gen(); let payment = vec![PaymentInfo { source: rng.gen() }]; diff --git a/types/src/gas.rs b/types/src/gas.rs index 7153aaeb1b..12ecb6a0fe 100644 --- a/types/src/gas.rs +++ b/types/src/gas.rs @@ -1,15 +1,10 @@ //! The `gas` module is used for working with Gas including converting to and from Motes. use alloc::vec::Vec; -use core::{ - fmt, - iter::Sum, - ops::{Add, AddAssign, Div, Mul, Sub}, -}; +use core::fmt; #[cfg(feature = "datasize")] use datasize::DataSize; -use num::Zero; #[cfg(any(feature = "testing", test))] use rand::Rng; #[cfg(feature = "json-schema")] @@ -32,6 +27,9 @@ use crate::{ pub struct Gas(U512); impl Gas { + /// The maximum value of `Gas`. + pub const MAX: Gas = Gas(U512::MAX); + /// Constructs a new `Gas`. pub fn new>(value: T) -> Self { Gas(value.into()) @@ -86,6 +84,16 @@ impl Gas { self.0.checked_sub(rhs.value()).map(Self::new) } + /// Checked integer subtraction. Computes `self * rhs`, returning `None` if overflow occurred. + pub fn checked_mul(&self, rhs: Self) -> Option { + self.0.checked_mul(rhs.value()).map(Self::new) + } + + /// Checked integer division. Computes `self / rhs`, returning `None` if overflow occurred. + pub fn checked_div(&self, rhs: Self) -> Option { + self.0.checked_div(rhs.value()).map(Self::new) + } + /// Returns a random `Gas`. #[cfg(any(feature = "testing", test))] pub fn random(rng: &mut TestRng) -> Self { @@ -120,64 +128,6 @@ impl fmt::Display for Gas { } } -impl Add for Gas { - type Output = Gas; - - fn add(self, rhs: Self) -> Self::Output { - let val = self.value() + rhs.value(); - Gas::new(val) - } -} - -impl Sub for Gas { - type Output = Gas; - - fn sub(self, rhs: Self) -> Self::Output { - let val = self.value() - rhs.value(); - Gas::new(val) - } -} - -impl Div for Gas { - type Output = Gas; - - fn div(self, rhs: Self) -> Self::Output { - let val = self.value() / rhs.value(); - Gas::new(val) - } -} - -impl Mul for Gas { - type Output = Gas; - - fn mul(self, rhs: Self) -> Self::Output { - let val = self.value() * rhs.value(); - Gas::new(val) - } -} - -impl AddAssign for Gas { - fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0 - } -} - -impl Zero for Gas { - fn zero() -> Self { - Gas::new(U512::zero()) - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} - -impl Sum for Gas { - fn sum>(iter: I) -> Self { - iter.fold(Gas::zero(), Add::add) - } -} - impl From for Gas { fn from(gas: u32) -> Self { let gas_u512: U512 = gas.into(); @@ -223,7 +173,11 @@ mod tests { let left_gas = Gas::new(U512::from(1)); let right_gas = Gas::new(U512::from(1)); let expected_gas = Gas::new(U512::from(2)); - assert_eq!((left_gas + right_gas), expected_gas, "should be equal") + assert_eq!( + left_gas.checked_add(right_gas), + Some(expected_gas), + "should be equal" + ) } #[test] @@ -231,7 +185,11 @@ mod tests { let left_gas = Gas::new(U512::from(1)); let right_gas = Gas::new(U512::from(1)); let expected_gas = Gas::new(U512::from(0)); - assert_eq!((left_gas - right_gas), expected_gas, "should be equal") + assert_eq!( + left_gas.checked_sub(right_gas), + Some(expected_gas), + "should be equal" + ) } #[test] @@ -239,7 +197,11 @@ mod tests { let left_gas = Gas::new(U512::from(100)); let right_gas = Gas::new(U512::from(10)); let expected_gas = Gas::new(U512::from(1000)); - assert_eq!((left_gas * right_gas), expected_gas, "should be equal") + assert_eq!( + left_gas.checked_mul(right_gas), + Some(expected_gas), + "should be equal" + ) } #[test] @@ -247,7 +209,11 @@ mod tests { let left_gas = Gas::new(U512::from(1000)); let right_gas = Gas::new(U512::from(100)); let expected_gas = Gas::new(U512::from(10)); - assert_eq!((left_gas / right_gas), expected_gas, "should be equal") + assert_eq!( + left_gas.checked_div(right_gas), + Some(expected_gas), + "should be equal" + ) } #[test] diff --git a/types/src/gens.rs b/types/src/gens.rs index ec32a5a023..2c104f63f0 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -9,14 +9,6 @@ use alloc::{ vec, }; -use proptest::{ - array, bits, bool, - collection::{self, vec, SizeRange}, - option, - prelude::*, - result, -}; - use crate::{ account::{ self, action_thresholds::gens::account_action_thresholds_arb, @@ -34,7 +26,10 @@ use crate::{ Contract, ContractHash, ContractPackage, ContractPackageStatus, ContractVersionKey, ContractVersions, EntryPoint as ContractEntryPoint, EntryPoints as ContractEntryPoints, }, - crypto::{self, gens::public_key_arb_no_system}, + crypto::{ + self, + gens::{public_key_arb_no_system, secret_key_arb_no_system}, + }, deploy_info::gens::deploy_info_arb, global_state::{Pointer, TrieMerkleProof, TrieMerkleProofStep}, package::{EntityVersionKey, EntityVersions, Groups, PackageStatus}, @@ -47,7 +42,10 @@ use crate::{ mint::BalanceHoldAddr, SystemEntityType, }, - transaction::{gens::deploy_hash_arb, TransactionRuntime}, + transaction::{ + gens::deploy_hash_arb, FieldsContainer, InitiatorAddrAndSecretKey, TransactionArgs, + TransactionV1Payload, + }, transfer::{ gens::{transfer_v1_addr_arb, transfer_v1_arb}, TransferAddr, @@ -55,9 +53,16 @@ use crate::{ AccessRights, AddressableEntity, AddressableEntityHash, BlockTime, ByteCode, CLType, CLValue, Digest, EntityAddr, EntityKind, EntryPoint, EntryPointAccess, EntryPointPayment, EntryPointType, EntryPoints, EraId, Group, InitiatorAddr, Key, NamedArg, Package, Parameter, - Phase, PricingMode, ProtocolVersion, RuntimeArgs, SemVer, StoredValue, Timestamp, - TransactionEntryPoint, TransactionInvocationTarget, TransactionLane, TransactionScheduling, - TransactionTarget, TransactionV1Body, URef, U128, U256, U512, + Phase, PricingMode, ProtocolVersion, PublicKey, RuntimeArgs, SemVer, StoredValue, TimeDiff, + Timestamp, Transaction, TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntime, + TransactionScheduling, TransactionTarget, TransactionV1, URef, U128, U256, U512, +}; +use proptest::{ + array, bits, bool, + collection::{self, vec, SizeRange}, + option, + prelude::*, + result, }; pub fn u8_slice_32() -> impl Strategy { @@ -921,17 +926,6 @@ pub fn trie_merkle_proof_arb() -> impl Strategy impl Strategy { - prop_oneof![ - Just(TransactionLane::Mint), - Just(TransactionLane::Auction), - Just(TransactionLane::InstallUpgrade), - Just(TransactionLane::Large), - Just(TransactionLane::Medium), - Just(TransactionLane::Small), - ] -} - pub fn transaction_scheduling_arb() -> impl Strategy { prop_oneof![ Just(TransactionScheduling::Standard), @@ -963,6 +957,30 @@ pub fn transaction_invocation_target_arb() -> impl Strategy impl Strategy { + ( + transaction_invocation_target_arb(), + transaction_runtime_arb(), + any::(), + ) + .prop_map(|(target, runtime, transferred_value)| { + TransactionTarget::new_stored(target, runtime, transferred_value) + }) +} + +pub fn session_transaction_target() -> impl Strategy { + ( + any::(), + Just(Bytes::from(vec![1; 10])), + transaction_runtime_arb(), + any::(), + any::>(), + ) + .prop_map(|(target, module_bytes, runtime, transferred_value, seed)| { + TransactionTarget::new_session(target, module_bytes, runtime, transferred_value, seed) + }) +} + pub fn transaction_target_arb() -> impl Strategy { prop_oneof![ Just(TransactionTarget::Native), @@ -980,9 +998,19 @@ pub fn transaction_target_arb() -> impl Strategy { ) ] } -pub fn transaction_entry_point_arb() -> impl Strategy { + +pub fn legal_target_entry_point_calls_arb( +) -> impl Strategy { + prop_oneof![ + native_entry_point_arb().prop_map(|s| (TransactionTarget::Native, s)), + stored_transaction_target() + .prop_map(|s| (s, TransactionEntryPoint::Custom("ABC".to_string()))), + session_transaction_target().prop_map(|s| (s, TransactionEntryPoint::Call)), + ] +} + +pub fn native_entry_point_arb() -> impl Strategy { prop_oneof![ - Just(TransactionEntryPoint::Call), Just(TransactionEntryPoint::Transfer), Just(TransactionEntryPoint::AddBid), Just(TransactionEntryPoint::WithdrawBid), @@ -991,11 +1019,17 @@ pub fn transaction_entry_point_arb() -> impl Strategy impl Strategy { + prop_oneof![ + native_entry_point_arb(), + Just(TransactionEntryPoint::Call), + Just(TransactionEntryPoint::Custom("custom".to_string())), + ] +} pub fn runtime_args_arb() -> impl Strategy { let mut runtime_args_1 = RuntimeArgs::new(); @@ -1010,27 +1044,45 @@ pub fn runtime_args_arb() -> impl Strategy { prop_oneof![Just(runtime_args_1)] } -pub fn v1_transaction_body_arb() -> impl Strategy { +pub fn fields_arb() -> impl Strategy> { + collection::btree_map( + any::(), + any::().prop_map(|s| Bytes::from(s.as_bytes())), + 3..30, + ) +} +pub fn v1_transaction_payload_arb() -> impl Strategy { ( - runtime_args_arb(), - transaction_target_arb(), - transaction_entry_point_arb(), - transaction_category_arb(), - transaction_scheduling_arb(), + any::(), + timestamp_arb(), + any::(), + pricing_mode_arb(), + initiator_addr_arb(), + fields_arb(), ) .prop_map( - |(args, target, entry_point, transaction_category, scheduling)| { - TransactionV1Body::new( - args, - target, - entry_point, - transaction_category as u8, - scheduling, + |(chain_name, timestamp, ttl_millis, pricing_mode, initiator_addr, fields)| { + TransactionV1Payload::new( + chain_name, + timestamp, + TimeDiff::from_millis(ttl_millis), + pricing_mode, + initiator_addr, + fields, ) }, ) } +pub fn fixed_pricing_mode_arb() -> impl Strategy { + (any::(), any::()).prop_map(|(gas_price_tolerance, additional_computation_factor)| { + PricingMode::Fixed { + gas_price_tolerance, + additional_computation_factor, + } + }) +} + pub fn pricing_mode_arb() -> impl Strategy { prop_oneof![ (any::(), any::(), any::()).prop_map( @@ -1042,11 +1094,7 @@ pub fn pricing_mode_arb() -> impl Strategy { } } ), - any::().prop_map(|gas_price_tolerance| { - PricingMode::Fixed { - gas_price_tolerance, - } - }), + fixed_pricing_mode_arb(), u8_slice_32().prop_map(|receipt| { PricingMode::Reserved { receipt: receipt.into(), @@ -1061,3 +1109,113 @@ pub fn initiator_addr_arb() -> impl Strategy { u2_slice_32().prop_map(|hash| InitiatorAddr::AccountHash(AccountHash::new(hash))), ] } + +pub fn timestamp_arb() -> impl Strategy { + //The weird u64 value is the max milliseconds that are bofeore year 10000. 5 digit years are + // not rfc3339 compliant and will cause an error + prop_oneof![Just(0_u64), Just(1_u64), Just(253_402_300_799_999_u64)].prop_map(Timestamp::from) +} + +pub fn legal_v1_transaction_arb() -> impl Strategy { + ( + any::(), + timestamp_arb(), + any::(), + pricing_mode_arb(), + secret_key_arb_no_system(), + runtime_args_arb(), + transaction_scheduling_arb(), + legal_target_entry_point_calls_arb(), + ) + .prop_map( + |( + chain_name, + timestamp, + ttl, + pricing_mode, + secret_key, + args, + scheduling, + (target, entry_point), + )| { + let public_key = PublicKey::from(&secret_key); + let initiator_addr = InitiatorAddr::PublicKey(public_key); + let initiator_addr_with_secret = InitiatorAddrAndSecretKey::Both { + initiator_addr, + secret_key: &secret_key, + }; + let container = FieldsContainer::new( + TransactionArgs::Named(args), + target, + entry_point, + scheduling, + ); + TransactionV1::build( + chain_name, + timestamp, + TimeDiff::from_seconds(ttl), + pricing_mode, + container.to_map().unwrap(), + initiator_addr_with_secret, + ) + }, + ) +} +pub fn v1_transaction_arb() -> impl Strategy { + ( + any::(), + timestamp_arb(), + any::(), + pricing_mode_arb(), + secret_key_arb_no_system(), + runtime_args_arb(), + transaction_target_arb(), + transaction_entry_point_arb(), + transaction_scheduling_arb(), + ) + .prop_map( + |( + chain_name, + timestamp, + ttl, + pricing_mode, + secret_key, + args, + target, + entry_point, + scheduling, + )| { + let public_key = PublicKey::from(&secret_key); + let initiator_addr = InitiatorAddr::PublicKey(public_key); + let initiator_addr_with_secret = InitiatorAddrAndSecretKey::Both { + initiator_addr, + secret_key: &secret_key, + }; + let container = FieldsContainer::new( + TransactionArgs::Named(args), + target, + entry_point, + scheduling, + ); + TransactionV1::build( + chain_name, + timestamp, + TimeDiff::from_seconds(ttl), + pricing_mode, + container.to_map().unwrap(), + initiator_addr_with_secret, + ) + }, + ) +} + +pub fn transaction_arb() -> impl Strategy { + (v1_transaction_arb()).prop_map(Transaction::V1) +} + +pub fn legal_transaction_arb() -> impl Strategy { + (legal_v1_transaction_arb()).prop_map(Transaction::V1) +} +pub fn example_u32_arb() -> impl Strategy { + prop_oneof![Just(0), Just(1), Just(u32::MAX / 2), Just(u32::MAX)] +} diff --git a/types/src/json_pretty_printer.rs b/types/src/json_pretty_printer.rs index 3648d38c91..ce9e8b3127 100644 --- a/types/src/json_pretty_printer.rs +++ b/types/src/json_pretty_printer.rs @@ -1,8 +1,11 @@ +#![cfg(feature = "json-schema")] + extern crate alloc; use alloc::{format, string::String, vec::Vec}; use serde::Serialize; + use serde_json::{json, Value}; const MAX_STRING_LEN: usize = 150; diff --git a/types/src/lib.rs b/types/src/lib.rs index 1e75369ea3..67722049a0 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -51,6 +51,7 @@ mod gas; #[cfg(any(feature = "testing", feature = "gens", test))] pub mod gens; pub mod global_state; +#[cfg(feature = "json-schema")] mod json_pretty_printer; mod key; mod motes; @@ -73,7 +74,7 @@ mod uint; mod uref; mod validator_change; -#[cfg(feature = "std")] +#[cfg(all(feature = "std", any(feature = "std-fs-io", test)))] use libc::{c_long, sysconf, _SC_PAGESIZE}; #[cfg(feature = "std")] use once_cell::sync::Lazy; @@ -110,9 +111,11 @@ pub use block::{TestBlockBuilder, TestBlockV1Builder}; pub use block_time::{BlockTime, HoldsEpoch, BLOCKTIME_SERIALIZED_LENGTH}; pub use byte_code::{ByteCode, ByteCodeAddr, ByteCodeHash, ByteCodeKind}; pub use cl_type::{named_key_type, CLType, CLTyped}; +#[cfg(feature = "json-schema")] +pub use cl_value::cl_value_to_json; pub use cl_value::{ - cl_value_to_json, handle_stored_dictionary_value, CLTypeMismatch, CLValue, CLValueError, - ChecksumRegistry, DictionaryValue as CLValueDictionary, SystemEntityRegistry, + handle_stored_dictionary_value, CLTypeMismatch, CLValue, CLValueError, ChecksumRegistry, + DictionaryValue as CLValueDictionary, SystemEntityRegistry, }; pub use global_state::Pointer; @@ -126,9 +129,9 @@ pub use chainspec::{ HoldBalanceHandling, HostFunction, HostFunctionCost, HostFunctionCosts, LegacyRequiredFinality, MessageLimits, MintCosts, NetworkConfig, NextUpgrade, OpcodeCosts, PricingHandling, ProtocolConfig, ProtocolUpgradeConfig, RefundHandling, StandardPaymentCosts, StorageCosts, - SystemConfig, TransactionConfig, TransactionV1Config, VacancyConfig, ValidatorConfig, - WasmConfig, DEFAULT_GAS_HOLD_INTERVAL, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, - DEFAULT_REFUND_HANDLING, + SystemConfig, TransactionConfig, TransactionLimitsDefinition, TransactionV1Config, + VacancyConfig, ValidatorConfig, WasmConfig, WasmV1Config, DEFAULT_GAS_HOLD_INTERVAL, + DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, DEFAULT_MINIMUM_BID_AMOUNT, DEFAULT_REFUND_HANDLING, }; #[cfg(any(all(feature = "std", feature = "testing"), test))] pub use chainspec::{ @@ -142,11 +145,10 @@ pub use chainspec::{ DEFAULT_CONTROL_FLOW_RETURN_OPCODE, DEFAULT_CONTROL_FLOW_SELECT_OPCODE, DEFAULT_CONVERSION_COST, DEFAULT_CURRENT_MEMORY_COST, DEFAULT_DELEGATE_COST, DEFAULT_DIV_COST, DEFAULT_FEE_HANDLING, DEFAULT_GLOBAL_COST, DEFAULT_GROW_MEMORY_COST, - DEFAULT_INSTALL_UPGRADE_GAS_LIMIT, DEFAULT_INTEGER_COMPARISON_COST, - DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, DEFAULT_LOAD_COST, DEFAULT_LOCAL_COST, - DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_MIN_TRANSFER_MOTES, - DEFAULT_MUL_COST, DEFAULT_NEW_DICTIONARY_COST, DEFAULT_NOP_COST, DEFAULT_STORE_COST, - DEFAULT_TRANSFER_COST, DEFAULT_UNREACHABLE_COST, DEFAULT_WASM_MAX_MEMORY, + DEFAULT_INTEGER_COMPARISON_COST, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, DEFAULT_LOAD_COST, + DEFAULT_LOCAL_COST, DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES, DEFAULT_MUL_COST, + DEFAULT_NEW_DICTIONARY_COST, DEFAULT_NOP_COST, DEFAULT_STORE_COST, DEFAULT_TRANSFER_COST, + DEFAULT_UNREACHABLE_COST, DEFAULT_V1_MAX_STACK_HEIGHT, DEFAULT_V1_WASM_MAX_MEMORY, }; pub use contract_wasm::{ContractWasm, ContractWasmHash}; #[doc(inline)] @@ -160,6 +162,7 @@ pub use digest::{ pub use display_iter::DisplayIter; pub use era_id::EraId; pub use gas::Gas; +#[cfg(feature = "json-schema")] pub use json_pretty_printer::json_pretty_print; #[doc(inline)] pub use key::{ @@ -186,15 +189,15 @@ pub use timestamp::{TimeDiff, Timestamp}; #[cfg(any(feature = "std", test))] pub use transaction::GasLimited; pub use transaction::{ - AddressableEntityIdentifier, Approval, ApprovalsHash, Deploy, DeployDecodeFromJsonError, - DeployError, DeployExcessiveSizeError, DeployHash, DeployHeader, DeployId, - ExecutableDeployItem, ExecutableDeployItemIdentifier, ExecutionInfo, InitiatorAddr, + arg_handling, AddressableEntityIdentifier, Approval, ApprovalsHash, Deploy, + DeployDecodeFromJsonError, DeployError, DeployExcessiveSizeError, DeployHash, DeployHeader, + DeployId, ExecutableDeployItem, ExecutableDeployItemIdentifier, ExecutionInfo, InitiatorAddr, InvalidDeploy, InvalidTransaction, InvalidTransactionV1, NamedArg, PackageIdentifier, - PricingMode, RuntimeArgs, Transaction, TransactionArgs, TransactionEntryPoint, TransactionHash, - TransactionHeader, TransactionId, TransactionInvocationTarget, TransactionLane, - TransactionRuntime, TransactionScheduling, TransactionTarget, TransactionV1, TransactionV1Body, + PricingMode, PricingModeError, RuntimeArgs, Transaction, TransactionArgs, + TransactionEntryPoint, TransactionHash, TransactionId, TransactionInvocationTarget, + TransactionRuntime, TransactionScheduling, TransactionTarget, TransactionV1, TransactionV1DecodeFromJsonError, TransactionV1Error, TransactionV1ExcessiveSizeError, - TransactionV1Hash, TransactionV1Header, TransferTarget, + TransactionV1Hash, TransferTarget, }; #[cfg(any(feature = "std", test))] pub use transaction::{ @@ -213,8 +216,14 @@ pub use validator_change::ValidatorChange; pub const MINT_LANE_ID: u8 = 0; /// The lane identifier for the native auction interaction. pub const AUCTION_LANE_ID: u8 = 1; -/// The lane identifier for the special Wasm `install_upgrade` lane. +/// The lane identifier for the install/upgrade auction interaction. pub const INSTALL_UPGRADE_LANE_ID: u8 = 2; +/// The lane identifier for large wasms. +pub const LARGE_WASM_LANE_ID: u8 = 3; +/// The lane identifier for medium wasms. +pub const MEDIUM_WASM_LANE_ID: u8 = 4; +/// The lane identifier for small wasms. +pub const SMALL_WASM_LANE_ID: u8 = 5; /// OS page size. #[cfg(feature = "std")] @@ -222,8 +231,13 @@ pub static OS_PAGE_SIZE: Lazy = Lazy::new(|| { /// Sensible default for many if not all systems. const DEFAULT_PAGE_SIZE: usize = 4096; + #[cfg(any(feature = "std-fs-io", test))] // https://www.gnu.org/software/libc/manual/html_node/Sysconf.html let value: c_long = unsafe { sysconf(_SC_PAGESIZE) }; + + #[cfg(not(any(feature = "std-fs-io", test)))] + let value = 0; + if value <= 0 { DEFAULT_PAGE_SIZE } else { diff --git a/types/src/motes.rs b/types/src/motes.rs index 761c815561..7e03d9d7be 100644 --- a/types/src/motes.rs +++ b/types/src/motes.rs @@ -1,15 +1,10 @@ //! The `motes` module is used for working with Motes. use alloc::vec::Vec; -use core::{ - fmt, - iter::Sum, - ops::{Add, Div, Mul, Sub}, -}; +use core::fmt; #[cfg(feature = "datasize")] use datasize::DataSize; -use num::Zero; use serde::{Deserialize, Serialize}; use crate::{ @@ -23,6 +18,9 @@ use crate::{ pub struct Motes(U512); impl Motes { + /// The maximum value of `Motes`. + pub const MAX: Motes = Motes(U512::MAX); + /// Constructs a new `Motes`. pub fn new>(value: T) -> Self { Motes(value.into()) @@ -43,6 +41,17 @@ impl Motes { self.0.checked_sub(rhs.value()).map(Self::new) } + /// Checked integer multiplication. Computes `self * rhs`, returning `None` if overflow + /// occurred. + pub fn checked_mul(&self, rhs: Self) -> Option { + self.0.checked_mul(rhs.value()).map(Self::new) + } + + /// Checked integer division. Computes `self / rhs`, returning `None` if `rhs == 0`. + pub fn checked_div(&self, rhs: Self) -> Option { + self.0.checked_div(rhs.value()).map(Self::new) + } + /// Returns the inner `U512` value. pub fn value(&self) -> U512 { self.0 @@ -71,58 +80,6 @@ impl fmt::Display for Motes { } } -impl Add for Motes { - type Output = Motes; - - fn add(self, rhs: Self) -> Self::Output { - let val = self.value() + rhs.value(); - Motes::new(val) - } -} - -impl Sub for Motes { - type Output = Motes; - - fn sub(self, rhs: Self) -> Self::Output { - let val = self.value() - rhs.value(); - Motes::new(val) - } -} - -impl Div for Motes { - type Output = Motes; - - fn div(self, rhs: Self) -> Self::Output { - let val = self.value() / rhs.value(); - Motes::new(val) - } -} - -impl Mul for Motes { - type Output = Motes; - - fn mul(self, rhs: Self) -> Self::Output { - let val = self.value() * rhs.value(); - Motes::new(val) - } -} - -impl Zero for Motes { - fn zero() -> Self { - Motes::zero() - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} - -impl Sum for Motes { - fn sum>(iter: I) -> Self { - iter.fold(Motes::zero(), Add::add) - } -} - impl ToBytes for Motes { fn to_bytes(&self) -> Result, bytesrepr::Error> { self.0.to_bytes() @@ -172,8 +129,8 @@ mod tests { let right_motes = Motes::new(1); let expected_motes = Motes::new(2); assert_eq!( - (left_motes + right_motes), - expected_motes, + left_motes.checked_add(right_motes), + Some(expected_motes), "should be equal" ) } @@ -184,8 +141,8 @@ mod tests { let right_motes = Motes::new(1); let expected_motes = Motes::new(0); assert_eq!( - (left_motes - right_motes), - expected_motes, + left_motes.checked_sub(right_motes), + Some(expected_motes), "should be equal" ) } @@ -196,8 +153,8 @@ mod tests { let right_motes = Motes::new(10); let expected_motes = Motes::new(1000); assert_eq!( - (left_motes * right_motes), - expected_motes, + left_motes.checked_mul(right_motes), + Some(expected_motes), "should be equal" ) } @@ -208,8 +165,8 @@ mod tests { let right_motes = Motes::new(100); let expected_motes = Motes::new(10); assert_eq!( - (left_motes / right_motes), - expected_motes, + left_motes.checked_div(right_motes), + Some(expected_motes), "should be equal" ) } diff --git a/types/src/stored_value.rs b/types/src/stored_value.rs index 075dae4e9d..361c283a5c 100644 --- a/types/src/stored_value.rs +++ b/types/src/stored_value.rs @@ -865,22 +865,8 @@ impl FromBytes for StoredValue { } mod serde_helpers { - use core::fmt; - use super::*; - pub struct InvalidHumanReadableDeser(String); - - impl fmt::Display for InvalidHumanReadableDeser { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Couldn't deserialize StoredValue. Underlying error: {}", - self.0 - ) - } - } - #[derive(Serialize)] pub(super) enum HumanReadableSerHelper<'a> { CLValue(&'a CLValue), diff --git a/types/src/transaction.rs b/types/src/transaction.rs index 872f476390..893160a8f6 100644 --- a/types/src/transaction.rs +++ b/types/src/transaction.rs @@ -5,7 +5,7 @@ mod deploy; mod error; mod execution_info; mod initiator_addr; -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std", test, feature = "testing"))] mod initiator_addr_and_secret_key; mod package_identifier; mod pricing_mode; @@ -13,21 +13,25 @@ mod runtime_args; mod serialization; mod transaction_entry_point; mod transaction_hash; -mod transaction_header; mod transaction_id; mod transaction_invocation_target; -mod transaction_lane; mod transaction_runtime; mod transaction_scheduling; mod transaction_target; mod transaction_v1; mod transfer_target; -use alloc::{collections::BTreeSet, vec::Vec}; +use alloc::{ + collections::BTreeSet, + string::{String, ToString}, + vec::Vec, +}; use core::fmt::{self, Debug, Display, Formatter}; #[cfg(any(feature = "std", test))] use std::hash::Hash; +#[cfg(feature = "json-schema")] +use crate::URef; #[cfg(feature = "datasize")] use datasize::DataSize; #[cfg(feature = "json-schema")] @@ -42,8 +46,6 @@ use tracing::error; #[cfg(any(all(feature = "std", feature = "testing"), test))] use crate::testing::TestRng; -#[cfg(feature = "json-schema")] -use crate::URef; use crate::{ account::AccountHash, bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, @@ -63,23 +65,26 @@ pub use deploy::{DeployBuilder, DeployBuilderError}; pub use error::InvalidTransaction; pub use execution_info::ExecutionInfo; pub use initiator_addr::InitiatorAddr; -#[cfg(any(feature = "std", test))] -use initiator_addr_and_secret_key::InitiatorAddrAndSecretKey; +#[cfg(any(feature = "std", feature = "testing", test))] +pub(crate) use initiator_addr_and_secret_key::InitiatorAddrAndSecretKey; pub use package_identifier::PackageIdentifier; -pub use pricing_mode::PricingMode; +pub use pricing_mode::{PricingMode, PricingModeError}; pub use runtime_args::{NamedArg, RuntimeArgs}; pub use transaction_entry_point::TransactionEntryPoint; pub use transaction_hash::TransactionHash; -pub use transaction_header::TransactionHeader; pub use transaction_id::TransactionId; pub use transaction_invocation_target::TransactionInvocationTarget; pub use transaction_runtime::TransactionRuntime; pub use transaction_scheduling::TransactionScheduling; pub use transaction_target::TransactionTarget; +#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))] +pub(crate) use transaction_v1::fields_container::FieldsContainer; +#[cfg(any(feature = "testing", feature = "gens", test))] +pub use transaction_v1::TransactionV1Payload; pub use transaction_v1::{ - InvalidTransactionV1, TransactionArgs, TransactionLane, TransactionV1, TransactionV1Body, + arg_handling, InvalidTransactionV1, TransactionArgs, TransactionV1, TransactionV1DecodeFromJsonError, TransactionV1Error, TransactionV1ExcessiveSizeError, - TransactionV1Hash, TransactionV1Header, + TransactionV1Hash, }; #[cfg(any(feature = "std", test))] pub use transaction_v1::{TransactionV1Builder, TransactionV1BuilderError}; @@ -138,14 +143,6 @@ impl Transaction { } } - /// Body hash. - pub fn body_hash(&self) -> Digest { - match self { - Transaction::Deploy(deploy) => *deploy.header().body_hash(), - Transaction::V1(v1) => *v1.header().body_hash(), - } - } - /// Size estimate. pub fn size_estimate(&self) -> usize { match self { @@ -158,7 +155,7 @@ impl Transaction { pub fn timestamp(&self) -> Timestamp { match self { Transaction::Deploy(deploy) => deploy.header().timestamp(), - Transaction::V1(v1) => v1.header().timestamp(), + Transaction::V1(v1) => v1.payload().timestamp(), } } @@ -166,7 +163,7 @@ impl Transaction { pub fn ttl(&self) -> TimeDiff { match self { Transaction::Deploy(deploy) => deploy.header().ttl(), - Transaction::V1(v1) => v1.header().ttl(), + Transaction::V1(v1) => v1.payload().ttl(), } } @@ -195,14 +192,6 @@ impl Transaction { } } - /// Returns the header. - pub fn header(&self) -> TransactionHeader { - match self { - Transaction::Deploy(deploy) => TransactionHeader::Deploy(deploy.header().clone()), - Transaction::V1(transaction) => TransactionHeader::V1(transaction.header().clone()), - } - } - /// Returns the computed approvals hash identifying this transaction's approvals. pub fn compute_approvals_hash(&self) -> Result { let approvals_hash = match self { @@ -212,13 +201,28 @@ impl Transaction { Ok(approvals_hash) } - /// Turns `self` into an invalid `Transaction` by clearing the `chain_name`, invalidating the - /// transaction hash. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn invalidate(&mut self) { + /// Returns the chain name for the transaction, whether it's a `Deploy` or `V1` transaction. + pub fn chain_name(&self) -> String { + match self { + Transaction::Deploy(txn) => txn.chain_name().to_string(), + Transaction::V1(txn) => txn.chain_name().to_string(), + } + } + + /// Checks if the transaction is a standard payment. + /// + /// For `Deploy` transactions, it checks if the session is a standard payment + /// in the payment phase. For `V1` transactions, it returns the value of + /// `standard_payment` if the pricing mode is `Classic`, otherwise it returns `true`. + pub fn is_standard_payment(&self) -> bool { match self { - Transaction::Deploy(deploy) => deploy.invalidate(), - Transaction::V1(v1) => v1.invalidate(), + Transaction::Deploy(txn) => txn.session().is_standard_payment(Phase::Payment), + Transaction::V1(txn) => match txn.pricing_mode() { + PricingMode::PaymentLimited { + standard_payment, .. + } => *standard_payment, + _ => true, + }, } } @@ -265,7 +269,7 @@ impl Transaction { pub fn expires(&self) -> Timestamp { match self { Transaction::Deploy(deploy) => deploy.header().expires(), - Transaction::V1(txn) => txn.header().expires(), + Transaction::V1(txn) => txn.payload().expires(), } } @@ -299,30 +303,6 @@ impl Transaction { } } - /// Returns `true` if `self` represents a native transfer deploy or a native V1 transaction. - pub fn is_native(&self) -> bool { - match self { - Transaction::Deploy(deploy) => deploy.is_transfer(), - Transaction::V1(v1_txn) => *v1_txn.target() == TransactionTarget::Native, - } - } - - /// Is this a transaction that should be sent to the v1 execution engine? - pub fn is_v1_wasm(&self) -> bool { - match self { - Transaction::Deploy(deploy) => !deploy.is_transfer(), - Transaction::V1(v1) => v1.is_v1_wasm(), - } - } - - /// Is this a transaction that should be sent to the v2 execution engine? - pub fn is_v2_wasm(&self) -> bool { - match self { - Transaction::Deploy(_deploy) => false, - Transaction::V1(v1) => v1.is_v2_wasm(), - } - } - /// Get [`TransactionV1`] pub fn as_transaction_v1(&self) -> Option<&TransactionV1> { match self { @@ -331,50 +311,6 @@ impl Transaction { } } - /// Should this transaction use standard payment processing? - pub fn is_standard_payment(&self) -> bool { - match self { - Transaction::Deploy(deploy) => deploy.payment().is_standard_payment(Phase::Payment), - Transaction::V1(v1) => v1.is_standard_payment(), - } - } - - /// Should this transaction start in the initiating accounts context? - pub fn is_account_session(&self) -> bool { - match self { - Transaction::Deploy(deploy) => deploy.is_account_session(), - Transaction::V1(v1) => v1.is_account_session(), - } - } - - /// Checks if the transaction is compliant with the given configuration. - /// - /// This function verifies whether the transaction adheres to the specified - /// `chainspec`, considering the `timestamp_leeway` and the current `at` timestamp. - /// It returns `Ok(())` if the transaction is compliant, or an `Err(InvalidTransaction)` - /// if it is not. The function handles two types of transactions: `Deploy` and `V1`, - /// delegating the compliance check to their respective `is_config_compliant` methods - /// and mapping any errors to `InvalidTransaction` variants. - /// - /// This function is only available when either the `std` and `testing` features - /// are enabled, or during testing. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn is_config_compliant( - &self, - chainspec: &Chainspec, - timestamp_leeway: TimeDiff, - at: Timestamp, - ) -> Result<(), InvalidTransaction> { - match self { - Transaction::Deploy(deploy) => deploy - .is_config_compliant(&chainspec, timestamp_leeway, at) - .map_err(|error| InvalidTransaction::Deploy(error)), - Transaction::V1(txn) => txn - .is_config_compliant(&chainspec, timestamp_leeway, at) - .map_err(|error| InvalidTransaction::V1(error)), - } - } - /// Authorization keys. pub fn authorization_keys(&self) -> BTreeSet { match self { @@ -391,41 +327,55 @@ impl Transaction { } } - /// The session args. - pub fn session_args(&self) -> Option<&RuntimeArgs> { + /// Is the transaction the legacy deploy variant. + pub fn is_legacy_transaction(&self) -> bool { match self { - Transaction::Deploy(deploy) => Some(deploy.session().args()), - Transaction::V1(transaction_v1) => transaction_v1.body().args().as_named(), - } - } - - /// The entry point. - pub fn entry_point(&self) -> TransactionEntryPoint { - match self { - Transaction::Deploy(deploy) => deploy.session().entry_point_name().into(), - Transaction::V1(transaction_v1) => transaction_v1.entry_point().clone(), + Transaction::Deploy(_) => true, + Transaction::V1(_) => false, } } - /// The transaction category. - pub fn transaction_lane(&self) -> u8 { + #[cfg(any(all(feature = "std", feature = "testing"), test))] + /// Calcualates the gas limit for the transaction. + pub fn gas_limit(&self, chainspec: &Chainspec, lane_id: u8) -> Result { match self { - Transaction::Deploy(deploy) => { - if deploy.is_transfer() { - TransactionLane::Mint as u8 - } else { - TransactionLane::Large as u8 - } + Transaction::Deploy(deploy) => deploy + .gas_limit(chainspec) + .map_err(InvalidTransaction::from), + Transaction::V1(v1) => { + let pricing_mode = v1.pricing_mode(); + let entry_point = v1 + .get_transaction_entry_point() + .map_err(InvalidTransaction::from)?; + pricing_mode + .gas_limit(chainspec, &entry_point, lane_id) + .map_err(InvalidTransaction::from) } - Transaction::V1(v1) => v1.transaction_lane(), } } - /// Is the transaction the deploy variant. - pub fn is_deploy(&self) -> bool { + #[cfg(any(all(feature = "std", feature = "testing"), test))] + /// Returns a gas cost based upon the gas_limit, the gas price, + /// and the chainspec settings. + pub fn gas_cost( + &self, + chainspec: &Chainspec, + lane_id: u8, + gas_price: u8, + ) -> Result { match self { - Transaction::Deploy(_) => true, - Transaction::V1(_) => false, + Transaction::Deploy(deploy) => deploy + .gas_cost(chainspec, gas_price) + .map_err(InvalidTransaction::from), + Transaction::V1(v1) => { + let pricing_mode = v1.pricing_mode(); + let entry_point = v1 + .get_transaction_entry_point() + .map_err(InvalidTransaction::from)?; + pricing_mode + .gas_cost(chainspec, &entry_point, lane_id, gas_price) + .map_err(InvalidTransaction::from) + } } } @@ -445,22 +395,6 @@ impl Transaction { Transaction::V1(TransactionV1::random(rng)) } } - - /// Returns true if this transaction is a smart contract installer or upgrader. - pub fn is_install_or_upgrade(&self) -> bool { - match self { - Transaction::Deploy(deploy) => false, - Transaction::V1(v1) => v1.is_install_or_upgrade(), - } - } - - /// Returns the package identifier. - pub fn seed(&self) -> Option<[u8; 32]> { - match self { - Transaction::Deploy(deploy) => None, - Transaction::V1(transaction_v1) => transaction_v1.seed(), - } - } } /// Calculates gas limit. @@ -483,40 +417,6 @@ pub trait GasLimited { fn gas_price_tolerance(&self) -> Result; } -#[cfg(any(feature = "std", test))] -impl GasLimited for Transaction { - type Error = InvalidTransaction; - - fn gas_cost(&self, chainspec: &Chainspec, gas_price: u8) -> Result { - match self { - Transaction::Deploy(deploy) => deploy - .gas_cost(chainspec, gas_price) - .map_err(InvalidTransaction::from), - Transaction::V1(v1) => v1 - .gas_cost(chainspec, gas_price) - .map_err(InvalidTransaction::from), - } - } - - fn gas_limit(&self, chainspec: &Chainspec) -> Result { - match self { - Transaction::Deploy(deploy) => deploy - .gas_limit(chainspec) - .map_err(InvalidTransaction::from), - Transaction::V1(v1) => v1.gas_limit(chainspec).map_err(InvalidTransaction::from), - } - } - - fn gas_price_tolerance(&self) -> Result { - match self { - Transaction::Deploy(deploy) => deploy - .gas_price_tolerance() - .map_err(InvalidTransaction::from), - Transaction::V1(v1) => v1.gas_price_tolerance().map_err(InvalidTransaction::from), - } - } -} - impl From for Transaction { fn from(deploy: Deploy) -> Self { Self::Deploy(deploy) @@ -587,13 +487,17 @@ impl Display for Transaction { /// Proptest generators for [`Transaction`]. #[cfg(any(feature = "testing", feature = "gens", test))] pub mod gens { + use super::*; use proptest::{ array, prelude::{Arbitrary, Strategy}, }; - use super::*; - + /// Generates a random `DeployHash` for testing purposes. + /// + /// This function is used to generate random `DeployHash` values for testing purposes. + /// It produces a proptest `Strategy` that can be used to generate arbitrary `DeployHash` + /// values. pub fn deploy_hash_arb() -> impl Strategy { array::uniform32(::arbitrary()).prop_map(DeployHash::from_raw) } @@ -645,3 +549,24 @@ mod tests { bytesrepr::test_serialization_roundtrip(&transaction); } } + +#[cfg(test)] +mod proptests { + use super::*; + use crate::{bytesrepr, gens::transaction_arb}; + use proptest::prelude::*; + + proptest! { + #[test] + fn bytesrepr_roundtrip(transaction in transaction_arb()) { + bytesrepr::test_serialization_roundtrip(&transaction); + } + + #[test] + fn json_roundtrip(transaction in transaction_arb()) { + let json_string = serde_json::to_string_pretty(&transaction).unwrap(); + let decoded = serde_json::from_str::(&json_string).unwrap(); + assert_eq!(transaction, decoded); + } + } +} diff --git a/types/src/transaction/deploy.rs b/types/src/transaction/deploy.rs index b9c1555032..5cd09b440c 100644 --- a/types/src/transaction/deploy.rs +++ b/types/src/transaction/deploy.rs @@ -62,12 +62,11 @@ use crate::{ }; #[cfg(any(feature = "std", test))] -use crate::{chainspec::PricingHandling, transaction::TransactionLane, Chainspec}; +use crate::{chainspec::PricingHandling, Chainspec, LARGE_WASM_LANE_ID}; #[cfg(any(feature = "std", test))] use crate::{system::auction::ARG_AMOUNT, transaction::GasLimited, Gas, Motes, U512}; #[cfg(any(feature = "std", test))] pub use deploy_builder::{DeployBuilder, DeployBuilderError}; -pub use deploy_category::DeployCategory; pub use deploy_hash::DeployHash; pub use deploy_header::DeployHeader; pub use deploy_id::DeployId; @@ -410,9 +409,12 @@ impl Deploy { return Err(InvalidDeploy::InvalidRuntime); } + // We're assuming that Deploy can have a maximum size of an InstallUpgrade transaction. + // We're passing 0 as transaction size since determining max transaction size for + // InstallUpgrade doesn't rely on the size of transaction let max_transaction_size = config .transaction_v1_config - .get_max_serialized_length(TransactionLane::Large as u8); + .get_max_serialized_length(LARGE_WASM_LANE_ID); self.is_valid_size(max_transaction_size as u32)?; let header = self.header(); @@ -1326,7 +1328,7 @@ impl GasLimited for Deploy { let computation_limit = if self.is_transfer() { costs.mint_costs().transfer as u64 } else { - chainspec.get_max_gas_limit_by_lane(TransactionLane::Large as u8) + chainspec.get_max_gas_limit_by_category(LARGE_WASM_LANE_ID) }; Gas::new(computation_limit) } // legacy deploys do not support reservations @@ -1764,7 +1766,7 @@ mod tests { let deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, &chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -1789,7 +1791,7 @@ mod tests { let deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, &wrong_chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -1821,12 +1823,10 @@ mod tests { chainspec.with_chain_name(chain_name.to_string()); let config = chainspec.transaction_config.clone(); - let dependency_count = usize::from(config.deploy_config.max_dependencies + 1); - let deploy = create_deploy( &mut rng, config.max_ttl, - dependency_count, + 1, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -1857,13 +1857,7 @@ mod tests { let ttl = config.max_ttl + TimeDiff::from(Duration::from_secs(1)); - let deploy = create_deploy( - &mut rng, - ttl, - config.deploy_config.max_dependencies.into(), - chain_name, - GAS_PRICE_TOLERANCE as u64, - ); + let deploy = create_deploy(&mut rng, ttl, 0, chain_name, GAS_PRICE_TOLERANCE as u64); let expected_error = InvalidDeploy::ExcessiveTimeToLive { max_ttl: config.max_ttl, @@ -1896,7 +1890,7 @@ mod tests { let deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -1933,7 +1927,7 @@ mod tests { let deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -1971,7 +1965,7 @@ mod tests { let mut deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2020,7 +2014,7 @@ mod tests { let mut deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2070,7 +2064,7 @@ mod tests { let mut deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2156,7 +2150,7 @@ mod tests { let deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies as usize, + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2187,7 +2181,7 @@ mod tests { let mut deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies as usize, + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2218,7 +2212,7 @@ mod tests { let mut deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies as usize, + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2253,7 +2247,7 @@ mod tests { let deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies as usize, + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2283,7 +2277,7 @@ mod tests { let mut deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies as usize, + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2343,7 +2337,7 @@ mod tests { let mut deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); @@ -2404,7 +2398,7 @@ mod tests { let mut deploy = create_deploy( &mut rng, config.max_ttl, - config.deploy_config.max_dependencies.into(), + 0, chain_name, GAS_PRICE_TOLERANCE as u64, ); diff --git a/types/src/transaction/deploy/deploy_builder.rs b/types/src/transaction/deploy/deploy_builder.rs index 33d9fc68a1..0308d0131c 100644 --- a/types/src/transaction/deploy/deploy_builder.rs +++ b/types/src/transaction/deploy/deploy_builder.rs @@ -37,10 +37,15 @@ impl<'a> DeployBuilder<'a> { /// [`with_standard_payment`](Self::with_standard_payment) or /// [`with_payment`](Self::with_payment) pub fn new>(chain_name: C, session: ExecutableDeployItem) -> Self { + #[cfg(any(feature = "std-fs-io", test))] + let timestamp = Timestamp::now(); + #[cfg(not(any(feature = "std-fs-io", test)))] + let timestamp = Timestamp::zero(); + DeployBuilder { account: None, secret_key: None, - timestamp: Timestamp::now(), + timestamp, ttl: Self::DEFAULT_TTL, gas_price: Self::DEFAULT_GAS_PRICE, dependencies: vec![], diff --git a/types/src/transaction/deploy/deploy_hash.rs b/types/src/transaction/deploy/deploy_hash.rs index 0b38d6dec8..11692945c3 100644 --- a/types/src/transaction/deploy/deploy_hash.rs +++ b/types/src/transaction/deploy/deploy_hash.rs @@ -1,4 +1,4 @@ -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::fmt::{self, Display, Formatter}; #[cfg(feature = "datasize")] @@ -45,6 +45,11 @@ impl DeployHash { &self.0 } + /// Hexadecimal representation of the hash. + pub fn to_hex_string(&self) -> String { + base16::encode_lower(self.inner()) + } + /// Returns a new `DeployHash` directly initialized with the provided bytes; no hashing is done. #[cfg(any(feature = "testing", test))] pub const fn from_raw(raw_digest: [u8; Self::LENGTH]) -> Self { diff --git a/types/src/transaction/initiator_addr.rs b/types/src/transaction/initiator_addr.rs index f8b879d904..b9e6c40046 100644 --- a/types/src/transaction/initiator_addr.rs +++ b/types/src/transaction/initiator_addr.rs @@ -28,7 +28,7 @@ const PUBLIC_KEY_FIELD_INDEX: u16 = 1; const ACCOUNT_HASH_VARIANT_TAG: u8 = 1; const ACCOUNT_HASH_FIELD_INDEX: u16 = 1; -/// The address of the initiator of a [`TransactionV1`]. +/// The address of the initiator of a [`crate::Transaction`]. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[cfg_attr( diff --git a/types/src/transaction/initiator_addr_and_secret_key.rs b/types/src/transaction/initiator_addr_and_secret_key.rs index 20b565b401..d174a81d87 100644 --- a/types/src/transaction/initiator_addr_and_secret_key.rs +++ b/types/src/transaction/initiator_addr_and_secret_key.rs @@ -2,7 +2,7 @@ use crate::{InitiatorAddr, PublicKey, SecretKey}; /// Used when constructing a deploy or transaction. #[derive(Debug)] -pub(super) enum InitiatorAddrAndSecretKey<'a> { +pub(crate) enum InitiatorAddrAndSecretKey<'a> { /// Provides both the initiator address and the secret key (not necessarily for the same /// initiator address) used to sign the deploy or transaction. Both { diff --git a/types/src/transaction/pricing_mode.rs b/types/src/transaction/pricing_mode.rs index bfcbfd24e1..0901db3768 100644 --- a/types/src/transaction/pricing_mode.rs +++ b/types/src/transaction/pricing_mode.rs @@ -9,9 +9,12 @@ use rand::Rng; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use super::serialization::CalltableSerializationEnvelope; #[cfg(doc)] use super::Transaction; +use super::{ + serialization::CalltableSerializationEnvelope, InvalidTransaction, InvalidTransactionV1, + TransactionEntryPoint, +}; #[cfg(any(feature = "testing", test))] use crate::testing::TestRng; use crate::{ @@ -22,6 +25,8 @@ use crate::{ transaction::serialization::CalltableSerializationEnvelopeBuilder, Digest, }; +#[cfg(any(feature = "std", test))] +use crate::{Chainspec, Gas, Motes, AUCTION_LANE_ID, MINT_LANE_ID, U512}; /// The pricing mode of a [`Transaction`]. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)] @@ -48,6 +53,14 @@ pub enum PricingMode { /// The cost of the transaction is determined by the cost table, per the /// transaction category. Fixed { + /// User-specified additional computation factor (minimum 0). If "0" is provided, + /// no additional logic is applied to the computation limit. Each value above "0" + /// tells the node that it needs to treat the transaction as if it uses more gas + /// than it's serialized size indicates. Each "1" will increase the "wasm lane" + /// size bucket for this transaction by 1. So if the size of the transaction + /// indicates bucket "0" and "additional_computation_factor = 2", the transaction + /// will be treated as a "2". + additional_computation_factor: u8, /// User-specified gas_price tolerance (minimum 1). /// This is interpreted to mean "do not include this transaction in a block /// if the current gas price is greater than this number" @@ -73,6 +86,7 @@ impl PricingMode { }, 1 => PricingMode::Fixed { gas_price_tolerance: rng.gen(), + additional_computation_factor: 1, }, 2 => PricingMode::Reserved { receipt: rng.gen() }, _ => unreachable!(), @@ -106,10 +120,12 @@ impl PricingMode { } PricingMode::Fixed { gas_price_tolerance, + additional_computation_factor, } => { vec![ crate::bytesrepr::U8_SERIALIZED_LENGTH, gas_price_tolerance.serialized_length(), + additional_computation_factor.serialized_length(), ] } PricingMode::Reserved { receipt } => { @@ -120,6 +136,155 @@ impl PricingMode { } } } + + #[cfg(any(feature = "std", test))] + /// Returns the gas limit. + pub fn gas_limit( + &self, + chainspec: &Chainspec, + entry_point: &TransactionEntryPoint, + lane_id: u8, + ) -> Result { + let costs = chainspec.system_costs_config; + let gas = match self { + PricingMode::PaymentLimited { payment_amount, .. } => Gas::new(*payment_amount), + PricingMode::Fixed { .. } => { + let computation_limit = { + if lane_id == MINT_LANE_ID { + // Because we currently only support one native mint interaction, + // native transfer, we can short circuit to return that value. + // However if other direct mint interactions are supported + // in the future (such as the upcoming burn feature), + // this logic will need to be expanded to self.mint_costs().field? + // for the value for each verb...see how auction is set up below. + costs.mint_costs().transfer as u64 + } else if lane_id == AUCTION_LANE_ID { + let amount = match entry_point { + TransactionEntryPoint::Call => { + return Err(PricingModeError::EntryPointCannotBeCall) + } + TransactionEntryPoint::Custom(_) | TransactionEntryPoint::Transfer => { + return Err(PricingModeError::EntryPointCannotBeCustom { + entry_point: entry_point.clone(), + }); + } + TransactionEntryPoint::AddBid | TransactionEntryPoint::ActivateBid => { + costs.auction_costs().add_bid + } + TransactionEntryPoint::WithdrawBid => { + costs.auction_costs().withdraw_bid.into() + } + TransactionEntryPoint::Delegate => { + costs.auction_costs().delegate.into() + } + TransactionEntryPoint::Undelegate => { + costs.auction_costs().undelegate.into() + } + TransactionEntryPoint::Redelegate => { + costs.auction_costs().redelegate.into() + } + TransactionEntryPoint::ChangeBidPublicKey => { + costs.auction_costs().change_bid_public_key + } + TransactionEntryPoint::AddReservations => { + costs.auction_costs().add_reservations.into() + } + TransactionEntryPoint::CancelReservations => { + costs.auction_costs().cancel_reservations.into() + } + }; + amount + } else { + chainspec.get_max_gas_limit_by_category(lane_id) + } + }; + Gas::new(U512::from(computation_limit)) + } + PricingMode::Reserved { receipt } => { + return Err(PricingModeError::InvalidPricingMode { + price_mode: PricingMode::Reserved { receipt: *receipt }, + }); + } + }; + Ok(gas) + } + + #[cfg(any(feature = "std", test))] + /// Returns gas cost. + pub fn gas_cost( + &self, + chainspec: &Chainspec, + entry_point: &TransactionEntryPoint, + lane_id: u8, + gas_price: u8, + ) -> Result { + let gas_limit = self.gas_limit(chainspec, entry_point, lane_id)?; + let motes = match self { + PricingMode::PaymentLimited { .. } | PricingMode::Fixed { .. } => { + Motes::from_gas(gas_limit, gas_price) + .ok_or(PricingModeError::UnableToCalculateGasCost)? + } + PricingMode::Reserved { .. } => { + Motes::zero() // prepaid + } + }; + Ok(motes) + } + + /// Returns gas cost. + pub fn additional_computation_factor(&self) -> u8 { + match self { + PricingMode::PaymentLimited { .. } => 0, + PricingMode::Fixed { + additional_computation_factor, + .. + } => *additional_computation_factor, + PricingMode::Reserved { .. } => 0, + } + } +} + +///Errors that can occur when calling PricingMode functions +pub enum PricingModeError { + /// The entry point for this transaction target cannot be `call`. + EntryPointCannotBeCall, + /// The entry point for this transaction target cannot be `TransactionEntryPoint::Custom`. + EntryPointCannotBeCustom { + /// The invalid entry point. + entry_point: TransactionEntryPoint, + }, + /// Invalid combination of pricing handling and pricing mode. + InvalidPricingMode { + /// The pricing mode as specified by the transaction. + price_mode: PricingMode, + }, + /// Unable to calculate gas cost. + UnableToCalculateGasCost, +} + +impl From for InvalidTransaction { + fn from(err: PricingModeError) -> Self { + InvalidTransaction::V1(err.into()) + } +} + +impl From for InvalidTransactionV1 { + fn from(err: PricingModeError) -> Self { + match err { + PricingModeError::EntryPointCannotBeCall => { + InvalidTransactionV1::EntryPointCannotBeCall + } + PricingModeError::EntryPointCannotBeCustom { entry_point } => { + InvalidTransactionV1::EntryPointCannotBeCustom { entry_point } + } + PricingModeError::InvalidPricingMode { price_mode } => { + InvalidTransactionV1::InvalidPricingMode { price_mode } + } + PricingModeError::UnableToCalculateGasCost => { + InvalidTransactionV1::UnableToCalculateGasCost + } + } + } } const TAG_FIELD_INDEX: u16 = 0; @@ -130,6 +295,7 @@ const PAYMENT_LIMITED_STANDARD_PAYMENT_INDEX: u16 = 3; const FIXED_VARIANT_TAG: u8 = 1; const FIXED_GAS_PRICE_TOLERANCE_INDEX: u16 = 1; +const FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX: u16 = 2; const RESERVED_VARIANT_TAG: u8 = 2; const RESERVED_RECEIPT_INDEX: u16 = 1; @@ -152,9 +318,14 @@ impl ToBytes for PricingMode { .binary_payload_bytes(), PricingMode::Fixed { gas_price_tolerance, + additional_computation_factor, } => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? .add_field(TAG_FIELD_INDEX, &FIXED_VARIANT_TAG)? .add_field(FIXED_GAS_PRICE_TOLERANCE_INDEX, &gas_price_tolerance)? + .add_field( + FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX, + &additional_computation_factor, + )? .binary_payload_bytes(), PricingMode::Reserved { receipt } => { CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? @@ -199,11 +370,16 @@ impl FromBytes for PricingMode { let window = window.ok_or(Formatting)?; window.verify_index(FIXED_GAS_PRICE_TOLERANCE_INDEX)?; let (gas_price_tolerance, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX)?; + let (additional_computation_factor, window) = + window.deserialize_and_maybe_next::()?; if window.is_some() { return Err(Formatting); } Ok(PricingMode::Fixed { gas_price_tolerance, + additional_computation_factor, }) } RESERVED_VARIANT_TAG => { @@ -238,7 +414,12 @@ impl Display for PricingMode { PricingMode::Reserved { receipt } => write!(formatter, "reserved: {}", receipt), PricingMode::Fixed { gas_price_tolerance, - } => write!(formatter, "fixed pricing {}", gas_price_tolerance), + additional_computation_factor, + } => write!( + formatter, + "fixed pricing {} {}", + gas_price_tolerance, additional_computation_factor + ), } } } diff --git a/types/src/transaction/transaction_entry_point.rs b/types/src/transaction/transaction_entry_point.rs index e9ad2b7ab8..f99e430107 100644 --- a/types/src/transaction/transaction_entry_point.rs +++ b/types/src/transaction/transaction_entry_point.rs @@ -22,7 +22,7 @@ use rand::Rng; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// The entry point of a [`Transaction`]. +/// The entry point of a [`crate::Transaction`]. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[cfg_attr( diff --git a/types/src/transaction/transaction_hash.rs b/types/src/transaction/transaction_hash.rs index f7d65d3275..948be894da 100644 --- a/types/src/transaction/transaction_hash.rs +++ b/types/src/transaction/transaction_hash.rs @@ -1,4 +1,4 @@ -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::fmt::{self, Display, Formatter}; #[cfg(feature = "datasize")] @@ -45,6 +45,11 @@ impl TransactionHash { } } + /// Hexadecimal representation of the hash. + pub fn to_hex_string(&self) -> String { + base16::encode_lower(&self.digest()) + } + /// Returns a random `TransactionHash`. #[cfg(any(feature = "testing", test))] pub fn random(rng: &mut TestRng) -> Self { diff --git a/types/src/transaction/transaction_header.rs b/types/src/transaction/transaction_header.rs deleted file mode 100644 index 68b5d7f3bd..0000000000 --- a/types/src/transaction/transaction_header.rs +++ /dev/null @@ -1,129 +0,0 @@ -use alloc::vec::Vec; -use core::fmt::{self, Display, Formatter}; - -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -#[cfg(any(feature = "std", test))] -use serde::{Deserialize, Serialize}; - -use super::{DeployHeader, TransactionV1Header}; -use crate::{ - bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - InitiatorAddr, -}; - -const DEPLOY_TAG: u8 = 0; -const V1_TAG: u8 = 1; - -/// A versioned wrapper for a transaction header or deploy header. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] -#[cfg_attr( - any(feature = "std", test), - derive(Serialize, Deserialize), - serde(deny_unknown_fields) -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr(feature = "json-schema", derive(JsonSchema))] -pub enum TransactionHeader { - /// A deploy header. - Deploy(DeployHeader), - /// A version 1 transaction header. - #[cfg_attr(any(feature = "std", test), serde(rename = "Version1"))] - V1(TransactionV1Header), -} - -impl TransactionHeader { - /// Return the initiator addr of this transaction. - pub fn initiator_addr(&self) -> InitiatorAddr { - match self { - TransactionHeader::Deploy(header) => header.account().clone().into(), - TransactionHeader::V1(header) => header.initiator_addr().clone(), - } - } -} - -impl From for TransactionHeader { - fn from(header: DeployHeader) -> Self { - Self::Deploy(header) - } -} - -impl From for TransactionHeader { - fn from(header: TransactionV1Header) -> Self { - Self::V1(header) - } -} - -impl Display for TransactionHeader { - fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { - match self { - TransactionHeader::Deploy(hash) => Display::fmt(hash, formatter), - TransactionHeader::V1(hash) => Display::fmt(hash, formatter), - } - } -} - -impl ToBytes for TransactionHeader { - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - match self { - TransactionHeader::Deploy(header) => { - DEPLOY_TAG.write_bytes(writer)?; - header.write_bytes(writer) - } - TransactionHeader::V1(header) => { - V1_TAG.write_bytes(writer)?; - header.write_bytes(writer) - } - } - } - - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - self.write_bytes(&mut buffer)?; - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - U8_SERIALIZED_LENGTH - + match self { - TransactionHeader::Deploy(header) => header.serialized_length(), - TransactionHeader::V1(header) => header.serialized_length(), - } - } -} - -impl FromBytes for TransactionHeader { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (tag, remainder) = u8::from_bytes(bytes)?; - match tag { - DEPLOY_TAG => { - let (header, remainder) = DeployHeader::from_bytes(remainder)?; - Ok((TransactionHeader::Deploy(header), remainder)) - } - V1_TAG => { - let (header, remainder) = TransactionV1Header::from_bytes(remainder)?; - Ok((TransactionHeader::V1(header), remainder)) - } - _ => Err(bytesrepr::Error::Formatting), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{testing::TestRng, Deploy, TransactionV1}; - - #[test] - fn bytesrepr_roundtrip() { - let rng = &mut TestRng::new(); - - let header = TransactionHeader::from(Deploy::random(rng).take_header()); - bytesrepr::test_serialization_roundtrip(&header); - - let header = TransactionHeader::from(TransactionV1::random(rng).take_header()); - bytesrepr::test_serialization_roundtrip(&header); - } -} diff --git a/types/src/transaction/transaction_invocation_target.rs b/types/src/transaction/transaction_invocation_target.rs index b9d5a03244..e33130d8bb 100644 --- a/types/src/transaction/transaction_invocation_target.rs +++ b/types/src/transaction/transaction_invocation_target.rs @@ -22,7 +22,7 @@ use rand::Rng; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// The identifier of a [`TransactionTarget::Stored`]. +/// The identifier of a [`crate::TransactionTarget::Stored`]. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[cfg_attr( diff --git a/types/src/transaction/transaction_lane.rs b/types/src/transaction/transaction_lane.rs deleted file mode 100644 index f6bda225b0..0000000000 --- a/types/src/transaction/transaction_lane.rs +++ /dev/null @@ -1,71 +0,0 @@ -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -// 1.x of the protocol had two implicit categories...standard and native transfer / mint -// 2.x and onwards the protocol has explicit categories. -// For legacy deploy support purposes, 1.x native transfers map to Mint, and all other deploys -// map to Standard. - -// NOTE: there is a direct correlation between the block body structure and the transaction -// categories. A given block structure defines some number of lanes for transactions. -// Thus, a given transaction explicitly specifies which lane within the block -// structure it is intended to go into. - -// Conceptually, the enum could just as easily be flipped around to be defined by the Block -// variant and be called something like BlockLane or BlockTransactionCategory, etc. It's only -// a matter of perspective. - -use crate::{ - transaction::{deploy::DeployCategory, transaction_v1::TransactionLane as V1}, - Deploy, -}; - -/// The category of a [`Transaction`]. -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "Session kind of a Transaction.") -)] -#[serde(deny_unknown_fields)] -#[repr(u8)] -pub(crate) enum TransactionLane { - /// The supported categories of transactions. This was not explicit in protocol 1.x - /// but was made explicit in protocol 2.x. Thus V1 is introduced in protocol 2.0 - /// Older deploys are retroactively mapped into the corresponding variants to - /// allow retro-compatibility. Think of it as a retcon. - V1(V1), -} - -impl From for TransactionLane { - fn from(value: Deploy) -> Self { - // To hand waive away legacy issues, we just curry the implicit categories from protocol 1.x - // forward to the corresponding protocol 2.x explicit categories. - if value.is_transfer() { - TransactionLane::V1(V1::Mint) - } else { - TransactionLane::V1(V1::Large) - } - } -} - -impl From for TransactionLane { - fn from(value: DeployCategory) -> Self { - // To hand waive away legacy issues, we just curry the implicit categories from protocol 1.x - // forward to the corresponding protocol 2.x explicit categories. - match value { - DeployCategory::Standard => TransactionLane::V1(V1::Large), - DeployCategory::Transfer => TransactionLane::V1(V1::Mint), - } - } -} - -impl From for TransactionLane { - fn from(value: V1) -> Self { - TransactionLane::V1(value) - } -} diff --git a/types/src/transaction/transaction_scheduling.rs b/types/src/transaction/transaction_scheduling.rs index 45b7b90c9c..a25db3afc4 100644 --- a/types/src/transaction/transaction_scheduling.rs +++ b/types/src/transaction/transaction_scheduling.rs @@ -20,7 +20,7 @@ use schemars::JsonSchema; #[cfg(any(feature = "std", test))] use serde::{Deserialize, Serialize}; -/// The scheduling mode of a [`Transaction`]. +/// The scheduling mode of a [`crate::Transaction`]. #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] #[cfg_attr( any(feature = "std", test), diff --git a/types/src/transaction/transaction_target.rs b/types/src/transaction/transaction_target.rs index 5f2d4fce3b..113a228ef8 100644 --- a/types/src/transaction/transaction_target.rs +++ b/types/src/transaction/transaction_target.rs @@ -13,7 +13,6 @@ use crate::{ FromBytes, ToBytes, }, transaction::serialization::CalltableSerializationEnvelopeBuilder, - transfer, }; #[cfg(feature = "datasize")] use datasize::DataSize; @@ -23,7 +22,7 @@ use rand::{Rng, RngCore}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// The execution target of a [`Transaction`]. +/// The execution target of a [`crate::Transaction`]. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "datasize", derive(DataSize))] #[cfg_attr( @@ -46,10 +45,12 @@ pub enum TransactionTarget { }, /// The execution target is the included module bytes, i.e. compiled Wasm. Session { - /// The compiled Wasm. - module_bytes: Bytes, + /// Flag determining if the Wasm is an install/upgrade. + is_install_upgrade: bool, /// The execution runtime to use. runtime: TransactionRuntime, + /// The compiled Wasm. + module_bytes: Bytes, /// The amount of motes to transfer before code is executed. /// /// This is for protection against phishing attack where a malicious session code drains @@ -82,12 +83,14 @@ impl TransactionTarget { /// Returns a new `TransactionTarget::Session`. pub fn new_session( + is_install_upgrade: bool, module_bytes: Bytes, runtime: TransactionRuntime, transferred_value: u64, seed: Option<[u8; 32]>, ) -> Self { TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, transferred_value, @@ -113,15 +116,17 @@ impl TransactionTarget { ] } TransactionTarget::Session { - module_bytes, + is_install_upgrade, runtime, transferred_value, seed, + module_bytes, } => { vec![ crate::bytesrepr::U8_SERIALIZED_LENGTH, - module_bytes.serialized_length(), + is_install_upgrade.serialized_length(), runtime.serialized_length(), + module_bytes.serialized_length(), transferred_value.serialized_length(), seed.serialized_length(), ] @@ -142,7 +147,9 @@ impl TransactionTarget { 2 => { let mut buffer = vec![0u8; rng.gen_range(0..100)]; rng.fill_bytes(buffer.as_mut()); + let is_install_upgrade = rng.gen(); TransactionTarget::new_session( + is_install_upgrade, Bytes::from(buffer), TransactionRuntime::VmCasperV1, rng.gen(), @@ -172,10 +179,11 @@ const STORED_RUNTIME_INDEX: u16 = 2; const STORED_TRANSFERRED_VALUE_INDEX: u16 = 3; const SESSION_VARIANT: u8 = 2; -const SESSION_MODULE_BYTES_INDEX: u16 = 1; +const SESSION_IS_INSTALL_INDEX: u16 = 1; const SESSION_RUNTIME_INDEX: u16 = 2; -const SESSION_TRANSFERRED_VALUE_INDEX: u16 = 3; -const SESSION_SEED_INDEX: u16 = 4; +const SESSION_MODULE_BYTES_INDEX: u16 = 3; +const SESSION_TRANSFERRED_VALUE_INDEX: u16 = 4; +const SESSION_SEED_INDEX: u16 = 5; impl ToBytes for TransactionTarget { fn to_bytes(&self) -> Result, Error> { @@ -196,14 +204,16 @@ impl ToBytes for TransactionTarget { .add_field(STORED_TRANSFERRED_VALUE_INDEX, transferred_value)? .binary_payload_bytes(), TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, transferred_value, seed, } => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? .add_field(TAG_FIELD_INDEX, &SESSION_VARIANT)? - .add_field(SESSION_MODULE_BYTES_INDEX, &module_bytes)? + .add_field(SESSION_IS_INSTALL_INDEX, &is_install_upgrade)? .add_field(SESSION_RUNTIME_INDEX, &runtime)? + .add_field(SESSION_MODULE_BYTES_INDEX, &module_bytes)? .add_field(SESSION_TRANSFERRED_VALUE_INDEX, transferred_value)? .add_field(SESSION_SEED_INDEX, seed)? .binary_payload_bytes(), @@ -251,22 +261,27 @@ impl FromBytes for TransactionTarget { } SESSION_VARIANT => { let window = window.ok_or(Formatting)?; - window.verify_index(SESSION_MODULE_BYTES_INDEX)?; - let (module_bytes, window) = window.deserialize_and_maybe_next::()?; + window.verify_index(SESSION_IS_INSTALL_INDEX)?; + let (is_install_upgrade, window) = window.deserialize_and_maybe_next::()?; let window = window.ok_or(Formatting)?; window.verify_index(SESSION_RUNTIME_INDEX)?; let (runtime, window) = window.deserialize_and_maybe_next::()?; let window = window.ok_or(Formatting)?; + window.verify_index(SESSION_MODULE_BYTES_INDEX)?; + let (module_bytes, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; window.verify_index(SESSION_TRANSFERRED_VALUE_INDEX)?; let (transferred_value, window) = window.deserialize_and_maybe_next::()?; let window = window.ok_or(Formatting)?; window.verify_index(SESSION_SEED_INDEX)?; let (seed, window) = window.deserialize_and_maybe_next::>()?; + if window.is_some() { return Err(Formatting); } Ok(TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, transferred_value, @@ -295,15 +310,17 @@ impl Display for TransactionTarget { ) } TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, transferred_value, seed, } => write!( formatter, - "session({} module bytes, {}, {}, {:?})", + "session({} module bytes, runtime: {}, is_install_upgrade: {}, transferred_value: {}, seed: {:?})", module_bytes.len(), runtime, + is_install_upgrade, transferred_value, seed, ), @@ -333,17 +350,28 @@ impl Debug for TransactionTarget { .field("transferred_value", transferred_value) .finish(), TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, transferred_value, seed, - } => formatter - .debug_struct("Session") - .field("module_bytes", &BytesLen(module_bytes.len())) - .field("runtime", runtime) - .field("transferred_value", transferred_value) - .field("seed", seed) - .finish(), + } => { + struct BytesLen(usize); + impl Debug for BytesLen { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + write!(formatter, "{} bytes", self.0) + } + } + + formatter + .debug_struct("Session") + .field("module_bytes", &BytesLen(module_bytes.len())) + .field("runtime", runtime) + .field("is_install_upgrade", is_install_upgrade) + .field("transferred_value", transferred_value) + .field("seed", seed) + .finish() + } } } } diff --git a/types/src/transaction/transaction_v1.rs b/types/src/transaction/transaction_v1.rs index c665fe04ec..73de08e76a 100644 --- a/types/src/transaction/transaction_v1.rs +++ b/types/src/transaction/transaction_v1.rs @@ -1,20 +1,33 @@ +pub mod arg_handling; mod errors_v1; -mod transaction_v1_body; +pub mod fields_container; +mod transaction_args; #[cfg(any(feature = "std", test))] mod transaction_v1_builder; mod transaction_v1_hash; -mod transaction_v1_header; -mod transaction_v1_lane; +pub mod transaction_v1_payload; +use crate::{ + bytesrepr::{self, Error, FromBytes, ToBytes}, + crypto, +}; #[cfg(any(all(feature = "std", feature = "testing"), test))] -use alloc::string::ToString; -use alloc::{collections::BTreeSet, vec::Vec}; -use core::{ - cmp, - fmt::{self, Debug, Display, Formatter}, - hash, +use crate::{ + testing::TestRng, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, LARGE_WASM_LANE_ID, MINT_LANE_ID, }; +#[cfg(any(feature = "std", test, feature = "testing"))] +use alloc::collections::BTreeMap; +use alloc::{collections::BTreeSet, vec::Vec}; +use errors_v1::FieldDeserializationError; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use fields_container::{ENTRY_POINT_MAP_KEY, TARGET_MAP_KEY}; +use tracing::debug; +pub use transaction_v1_payload::TransactionV1Payload; +#[cfg(any(feature = "std", feature = "testing", test))] +use super::InitiatorAddrAndSecretKey; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use super::{TransactionEntryPoint, TransactionTarget}; #[cfg(feature = "datasize")] use datasize::DataSize; #[cfg(any(feature = "once_cell", test))] @@ -23,58 +36,37 @@ use once_cell::sync::OnceCell; use schemars::JsonSchema; #[cfg(any(feature = "std", test))] use serde::{Deserialize, Serialize}; -use tracing::debug; use super::{ serialization::{CalltableSerializationEnvelope, CalltableSerializationEnvelopeBuilder}, - Approval, ApprovalsHash, InitiatorAddr, PricingMode, TransactionEntryPoint, - TransactionScheduling, TransactionTarget, -}; -#[cfg(any(feature = "std", test))] -use super::{GasLimited, InitiatorAddrAndSecretKey}; -#[cfg(any(feature = "std", test))] -use crate::chainspec::Chainspec; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::chainspec::PricingHandling; -use crate::{ - bytesrepr::{ - Error::{self, Formatting}, - FromBytes, ToBytes, - }, - crypto, Digest, DisplayIter, SecretKey, TimeDiff, Timestamp, TransactionRuntime, + Approval, ApprovalsHash, InitiatorAddr, PricingMode, }; +#[cfg(any(feature = "std", feature = "testing", test))] +use crate::bytesrepr::Bytes; +use crate::{Digest, DisplayIter, SecretKey, TimeDiff, Timestamp}; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::TransactionConfig; -#[cfg(any(feature = "std", test))] -use crate::{Gas, Motes, U512}; pub use errors_v1::{ DecodeFromJsonErrorV1 as TransactionV1DecodeFromJsonError, ErrorV1 as TransactionV1Error, ExcessiveSizeErrorV1 as TransactionV1ExcessiveSizeError, InvalidTransaction as InvalidTransactionV1, }; -pub use transaction_v1_body::{TransactionArgs, TransactionV1Body}; +pub use transaction_args::TransactionArgs; #[cfg(any(feature = "std", test))] pub use transaction_v1_builder::{TransactionV1Builder, TransactionV1BuilderError}; pub use transaction_v1_hash::TransactionV1Hash; -pub use transaction_v1_header::TransactionV1Header; -pub use transaction_v1_lane::TransactionLane; - -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::testing::TestRng; -const TRANSACTION_V1_SERIALIZATION_VERSION: u8 = 1; +use core::{ + cmp, + fmt::{self, Debug, Display, Formatter}, + hash, +}; -const SERIALIZATION_VERSION_INDEX: u16 = 0; -const HASH_FIELD_META_INDEX: u16 = 1; -const HEADER_FIELD_META_INDEX: u16 = 2; -const BODY_FIELD_META_INDEX: u16 = 3; -const APPROVALS_FIELD_META_INDEX: u16 = 4; +const HASH_FIELD_INDEX: u16 = 0; +const PAYLOAD_FIELD_INDEX: u16 = 1; +const APPROVALS_FIELD_INDEX: u16 = 2; /// A unit of work sent by a client to the network, which when executed can cause global state to /// be altered. -/// -/// To construct a new `TransactionV1`, use a [`TransactionV1Builder`]. #[derive(Clone, Eq, Debug)] #[cfg_attr( any(feature = "std", test), @@ -91,10 +83,8 @@ const APPROVALS_FIELD_META_INDEX: u16 = 4; ) )] pub struct TransactionV1 { - serialization_version: u8, hash: TransactionV1Hash, - header: TransactionV1Header, - body: TransactionV1Body, + payload: TransactionV1Payload, approvals: BTreeSet, #[cfg_attr(any(all(feature = "std", feature = "once_cell"), test), serde(skip))] #[cfg_attr( @@ -106,50 +96,32 @@ pub struct TransactionV1 { } impl TransactionV1 { - fn serialized_field_lengths(&self) -> Vec { - let serialization_version_len = TRANSACTION_V1_SERIALIZATION_VERSION.serialized_length(); - let approvals_len = self.approvals.serialized_length(); - let hash_len = self.hash.serialized_length(); - let header_len = self.header.serialized_length(); - let body_len = self.body.serialized_length(); - vec![ - serialization_version_len, - hash_len, - header_len, - body_len, - approvals_len, - ] - } - /// Called by the `TransactionV1Builder` to construct a new `TransactionV1`. - #[cfg(any(feature = "std", test))] - pub(super) fn build( + #[cfg(any(feature = "std", test, feature = "testing"))] + pub(crate) fn build( chain_name: String, timestamp: Timestamp, ttl: TimeDiff, - body: TransactionV1Body, pricing_mode: PricingMode, + fields: BTreeMap, initiator_addr_and_secret_key: InitiatorAddrAndSecretKey, ) -> TransactionV1 { let initiator_addr = initiator_addr_and_secret_key.initiator_addr(); - let body_hash = Digest::hash( - body.to_bytes() - .unwrap_or_else(|error| panic!("should serialize body: {}", error)), - ); - let header = TransactionV1Header::new( + let transaction_v1_payload = TransactionV1Payload::new( chain_name, timestamp, ttl, - body_hash, pricing_mode, initiator_addr, + fields, + ); + let hash = Digest::hash( + transaction_v1_payload + .to_bytes() + .unwrap_or_else(|error| panic!("should serialize body: {}", error)), ); - - let hash = header.compute_hash(); let mut transaction = TransactionV1 { - serialization_version: TRANSACTION_V1_SERIALIZATION_VERSION, - hash, - header, - body, + hash: hash.into(), + payload: transaction_v1_payload, approvals: BTreeSet::new(), #[cfg(any(feature = "once_cell", test))] is_verified: OnceCell::new(), @@ -161,390 +133,76 @@ impl TransactionV1 { transaction } - /// Returns the hash identifying this transaction. + /// Adds a signature of this transaction's hash to its approvals. + pub fn sign(&mut self, secret_key: &SecretKey) { + let approval = Approval::create(&self.hash.into(), secret_key); + self.approvals.insert(approval); + } + + /// Returns the `ApprovalsHash` of this transaction's approvals. pub fn hash(&self) -> &TransactionV1Hash { &self.hash } + /// Returns the internal payload of this transaction. + pub fn payload(&self) -> &TransactionV1Payload { + &self.payload + } + + /// Returns transactions approvals. + pub fn approvals(&self) -> &BTreeSet { + &self.approvals + } + + /// Returns the address of the initiator of the transaction. + pub fn initiator_addr(&self) -> &InitiatorAddr { + self.payload.initiator_addr() + } + /// Returns the name of the chain the transaction should be executed on. pub fn chain_name(&self) -> &str { - self.header.chain_name() + self.payload.chain_name() } /// Returns the creation timestamp of the transaction. pub fn timestamp(&self) -> Timestamp { - self.header.timestamp() + self.payload.timestamp() } /// Returns the duration after the creation timestamp for which the transaction will stay valid. /// /// After this duration has ended, the transaction will be considered expired. pub fn ttl(&self) -> TimeDiff { - self.header.ttl() + self.payload.ttl() } /// Returns `true` if the transaction has expired. pub fn expired(&self, current_instant: Timestamp) -> bool { - self.header.expired(current_instant) + self.payload.expired(current_instant) } /// Returns the pricing mode for the transaction. pub fn pricing_mode(&self) -> &PricingMode { - self.header.pricing_mode() - } - - /// Returns the address of the initiator of the transaction. - pub fn initiator_addr(&self) -> &InitiatorAddr { - self.header.initiator_addr() - } - - /// Returns a reference to the header of this transaction. - pub fn header(&self) -> &TransactionV1Header { - &self.header - } - - /// Consumes `self`, returning the header of this transaction. - pub fn take_header(self) -> TransactionV1Header { - self.header - } - - /// Returns the runtime args of the transaction. - pub fn args(&self) -> &TransactionArgs { - self.body.args() - } - - /// Consumes `self`, returning the runtime args of the transaction. - pub fn take_args(self) -> TransactionArgs { - self.body.take_args() - } - - /// Returns the target of the transaction. - pub fn target(&self) -> &TransactionTarget { - self.body.target() - } - - /// Returns the entry point of the transaction. - pub fn entry_point(&self) -> &TransactionEntryPoint { - self.body.entry_point() - } - - /// Returns the scheduling kind of the transaction. - pub fn scheduling(&self) -> &TransactionScheduling { - self.body.scheduling() - } - - /// Returns the body of this transaction. - pub fn body(&self) -> &TransactionV1Body { - &self.body - } - - /// Returns true if this transaction is a native mint interaction. - pub fn is_native_mint(&self) -> bool { - self.body().is_native_mint() - } - - /// Returns true if this transaction is a native auction interaction. - pub fn is_native_auction(&self) -> bool { - self.body().is_native_auction() - } - - /// Returns true if this transaction is a smart contract installer or upgrader. - pub fn is_install_or_upgrade(&self) -> bool { - self.body().is_install_or_upgrade() - } - - /// Returns true if this transaction is utilizing a standard payment. - #[inline] - pub fn is_standard_payment(&self) -> bool { - self.header().pricing_mode().is_standard_payment() - } - - /// Returns the transaction category. - pub fn transaction_lane(&self) -> u8 { - self.body.transaction_lane() - } - - /// Does this transaction have wasm targeting the v1 vm. - pub fn is_v1_wasm(&self) -> bool { - match self.target() { - TransactionTarget::Native => false, - TransactionTarget::Stored { runtime, .. } - | TransactionTarget::Session { runtime, .. } => { - matches!(runtime, TransactionRuntime::VmCasperV1) - && (!self.is_native_mint() && !self.is_native_auction()) - } - } - } - - /// Does this transaction have wasm targeting the v2 vm. - pub fn is_v2_wasm(&self) -> bool { - match self.target() { - TransactionTarget::Native => false, - TransactionTarget::Stored { runtime, .. } - | TransactionTarget::Session { runtime, .. } => { - matches!(runtime, TransactionRuntime::VmCasperV2) - && (!self.is_native_mint() && !self.is_native_auction()) - } - } - } - - /// Should this transaction start in the initiating accounts context? - pub fn is_account_session(&self) -> bool { - let target_is_stored_contract = matches!(self.target(), TransactionTarget::Stored { .. }); - !target_is_stored_contract - } - - /// Returns the approvals for this transaction. - pub fn approvals(&self) -> &BTreeSet { - &self.approvals - } - - /// Consumes `self`, returning a tuple of its constituent parts. - pub fn destructure( - self, - ) -> ( - TransactionV1Hash, - TransactionV1Header, - TransactionV1Body, - BTreeSet, - ) { - (self.hash, self.header, self.body, self.approvals) - } - - /// Adds a signature of this transaction's hash to its approvals. - pub fn sign(&mut self, secret_key: &SecretKey) { - let approval = Approval::create(&self.hash.into(), secret_key); - self.approvals.insert(approval); + self.payload.pricing_mode() } /// Returns the `ApprovalsHash` of this transaction's approvals. - pub fn compute_approvals_hash(&self) -> Result { + pub fn compute_approvals_hash(&self) -> Result { ApprovalsHash::compute(&self.approvals) } - /// Returns `true` if the serialized size of the transaction is not greater than - /// `max_transaction_size`. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn is_valid_size( - &self, - max_transaction_size: u32, - ) -> Result<(), TransactionV1ExcessiveSizeError> { - let actual_transaction_size = self.serialized_length(); - if actual_transaction_size > max_transaction_size as usize { - return Err(TransactionV1ExcessiveSizeError { - max_transaction_size, - actual_transaction_size, - }); - } - Ok(()) - } - - /// Returns `Ok` if and only if this transaction's body hashes to the value of `body_hash()`, - /// and if this transaction's header hashes to the value claimed as the transaction hash. - pub fn has_valid_hash(&self) -> Result<(), InvalidTransactionV1> { - let body_hash = Digest::hash( - self.body - .to_bytes() - .unwrap_or_else(|error| panic!("should serialize body: {}", error)), - ); - if body_hash != *self.header.body_hash() { - debug!(?self, ?body_hash, "invalid transaction body hash"); - return Err(InvalidTransactionV1::InvalidBodyHash); - } - - let hash = TransactionV1Hash::new(Digest::hash( - self.header - .to_bytes() - .unwrap_or_else(|error| panic!("should serialize header: {}", error)), - )); - if hash != self.hash { - debug!(?self, ?hash, "invalid transaction hash"); - return Err(InvalidTransactionV1::InvalidTransactionHash); - } - Ok(()) - } - - /// Returns `Ok` if and only if: - /// * the transaction hash is correct (see [`TransactionV1::has_valid_hash`] for details) - /// * approvals are non empty, and - /// * all approvals are valid signatures of the signed hash - pub fn verify(&self) -> Result<(), InvalidTransactionV1> { - #[cfg(any(feature = "once_cell", test))] - return self.is_verified.get_or_init(|| self.do_verify()).clone(); - - #[cfg(not(any(feature = "once_cell", test)))] - self.do_verify() - } - - fn do_verify(&self) -> Result<(), InvalidTransactionV1> { - if self.approvals.is_empty() { - debug!(?self, "transaction has no approvals"); - return Err(InvalidTransactionV1::EmptyApprovals); - } - - self.has_valid_hash()?; - - for (index, approval) in self.approvals.iter().enumerate() { - if let Err(error) = crypto::verify(self.hash, approval.signature(), approval.signer()) { - debug!( - ?self, - "failed to verify transaction approval {}: {}", index, error - ); - return Err(InvalidTransactionV1::InvalidApproval { index, error }); - } - } - - Ok(()) - } - - /// Returns `Ok` if and only if: - /// * the chain_name is correct, - /// * the configured parameters are complied with at the given timestamp - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn is_config_compliant( - &self, - chainspec: &Chainspec, - timestamp_leeway: TimeDiff, - at: Timestamp, - ) -> Result<(), InvalidTransactionV1> { - let transaction_config = chainspec.transaction_config.clone(); - - match self.body().transaction_runtime() { - Some(expected_runtime @ TransactionRuntime::VmCasperV1) => { - if !transaction_config.runtime_config.vm_casper_v1 { - // NOTE: In current implementation native transactions should be executed on - // both VmCasperV1 and VmCasperV2. This may change once we - // have a more stable VmCasperV2 that can also process calls - // to system contracts in VM2 chunked args style. - - return Err(InvalidTransactionV1::InvalidTransactionRuntime { - expected: expected_runtime, - }); - } - } - Some(expected_runtime @ TransactionRuntime::VmCasperV2) => { - if !transaction_config.runtime_config.vm_casper_v2 { - // NOTE: In current implementation native transactions should be executed on - // both VmCasperV1 and VmCasperV2. This may change once we - // have a more stable VmCasperV2 that can also process calls - // to system contracts in VM2 chunked args style. - - return Err(InvalidTransactionV1::InvalidTransactionRuntime { - expected: expected_runtime, - }); - } - } - None => { - // Native transactions are config compliant by default - } - } - - self.is_valid_size( - transaction_config - .transaction_v1_config - .get_max_serialized_length(self.body.transaction_lane) as u32, - )?; - - let chain_name = chainspec.network_config.name.clone(); - - let header = self.header(); - if header.chain_name() != chain_name { - debug!( - transaction_hash = %self.hash(), - transaction_header = %header, - chain_name = %header.chain_name(), - "invalid chain identifier" - ); - return Err(InvalidTransactionV1::InvalidChainName { - expected: chain_name, - got: header.chain_name().to_string(), - }); - } - - let price_handling = chainspec.core_config.pricing_handling; - let price_mode = header.pricing_mode(); - - match price_mode { - PricingMode::PaymentLimited { .. } => { - if let PricingHandling::Classic = price_handling { - } else { - return Err(InvalidTransactionV1::InvalidPricingMode { - price_mode: price_mode.clone(), - }); - } - } - PricingMode::Fixed { .. } => { - if let PricingHandling::Fixed = price_handling { - } else { - return Err(InvalidTransactionV1::InvalidPricingMode { - price_mode: price_mode.clone(), - }); - } - } - PricingMode::Reserved { .. } => { - if !chainspec.core_config.allow_reservations { - // Currently Reserved isn't implemented and we should - // not be accepting transactions with this mode. - return Err(InvalidTransactionV1::InvalidPricingMode { - price_mode: price_mode.clone(), - }); - } - } - } - - let min_gas_price = chainspec.vacancy_config.min_gas_price; - let gas_price_tolerance = self.header.gas_price_tolerance(); - if gas_price_tolerance < min_gas_price { - return Err(InvalidTransactionV1::GasPriceToleranceTooLow { - min_gas_price_tolerance: min_gas_price, - provided_gas_price_tolerance: gas_price_tolerance, - }); - } - - header.is_valid(&transaction_config, timestamp_leeway, at, &self.hash)?; - - let max_associated_keys = chainspec.core_config.max_associated_keys; - - if self.approvals.len() > max_associated_keys as usize { - debug!( - transaction_hash = %self.hash(), - number_of_approvals = %self.approvals.len(), - max_associated_keys = %max_associated_keys, - "number of transaction approvals exceeds the limit" - ); - return Err(InvalidTransactionV1::ExcessiveApprovals { - got: self.approvals.len() as u32, - max_associated_keys, - }); - } - - let gas_limit = self.gas_limit(chainspec)?; - let block_gas_limit = Gas::new(U512::from(transaction_config.block_gas_limit)); - if gas_limit > block_gas_limit { - debug!( - amount = %gas_limit, - %block_gas_limit, - "transaction gas limit exceeds block gas limit" - ); - return Err(InvalidTransactionV1::ExceedsBlockGasLimit { - block_gas_limit: transaction_config.block_gas_limit, - got: Box::new(gas_limit.value()), - }); - } - - self.body.is_valid(&transaction_config) - } - - // This method is not intended to be used by third party crates. - // - // It is required to allow finalized approvals to be injected after reading a transaction from - // storage. #[doc(hidden)] pub fn with_approvals(mut self, approvals: BTreeSet) -> Self { self.approvals = approvals; self } + /// Used by the `TestTransactionV1Builder` to inject invalid approvals for testing purposes. + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub(super) fn apply_approvals(&mut self, approvals: Vec) { + self.approvals.extend(approvals); + } + /// Returns a random, valid but possibly expired transaction. /// /// Note that the [`TransactionV1Builder`] can be used to create a random transaction with @@ -564,20 +222,15 @@ impl TransactionV1 { timestamp: Option, ttl: Option, ) -> Self { - let transaction = TransactionV1Builder::new_random_with_lane_and_timestamp_and_ttl( + let transaction_v1 = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( rng, - TransactionLane::Mint as u8, + MINT_LANE_ID, timestamp, ttl, ) .build() .unwrap(); - assert_eq!( - transaction.transaction_lane(), - TransactionLane::Mint as u8, - "Required mint, incorrect category" - ); - transaction + transaction_v1 } /// Returns a random transaction with "standard" category. @@ -590,19 +243,14 @@ impl TransactionV1 { timestamp: Option, ttl: Option, ) -> Self { - let transaction = TransactionV1Builder::new_random_with_lane_and_timestamp_and_ttl( + let transaction = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( rng, - TransactionLane::Large as u8, + LARGE_WASM_LANE_ID, timestamp, ttl, ) .build() .unwrap(); - assert_eq!( - transaction.transaction_lane(), - TransactionLane::Large as u8, - "Required large, incorrect category" - ); transaction } @@ -611,25 +259,19 @@ impl TransactionV1 { /// Note that the [`TransactionV1Builder`] can be used to create a random transaction with /// more specific values. #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn random_install_upgrade( + pub fn random_auction( rng: &mut TestRng, timestamp: Option, ttl: Option, ) -> Self { - let transaction = TransactionV1Builder::new_random_with_lane_and_timestamp_and_ttl( + TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( rng, - TransactionLane::InstallUpgrade as u8, + AUCTION_LANE_ID, timestamp, ttl, ) .build() - .unwrap(); - assert_eq!( - transaction.transaction_lane(), - TransactionLane::InstallUpgrade as u8, - "Required install/upgrade, incorrect category" - ); - transaction + .unwrap() } /// Returns a random transaction with "install/upgrade" category. @@ -637,199 +279,137 @@ impl TransactionV1 { /// Note that the [`TransactionV1Builder`] can be used to create a random transaction with /// more specific values. #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn random_auction( + pub fn random_install_upgrade( rng: &mut TestRng, timestamp: Option, ttl: Option, ) -> Self { - let transaction = TransactionV1Builder::new_random_with_lane_and_timestamp_and_ttl( + let transaction = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( rng, - TransactionLane::Auction as u8, + INSTALL_UPGRADE_LANE_ID, timestamp, ttl, ) .build() .unwrap(); - assert_eq!( - transaction.transaction_lane(), - TransactionLane::Auction as u8, - "Required auction, incorrect category" - ); transaction } - /// Turns `self` into an invalid transaction by clearing the `chain_name`, invalidating the - /// transaction header hash. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn invalidate(&mut self) { - self.header.invalidate(); + /// Returns result of attempting to deserailize a field from the amorphic `fields` container. + pub fn deserialize_field( + &self, + index: u16, + ) -> Result { + self.payload.deserialize_field(index) } - /// Used by the `TestTransactionV1Builder` to inject invalid approvals for testing purposes. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub(super) fn apply_approvals(&mut self, approvals: Vec) { - self.approvals.extend(approvals); + /// Returns number of fields in the amorphic `fields` container. + pub fn number_of_fields(&self) -> usize { + self.payload.number_of_fields() } - /// Seed to be used for hash generation. - pub(crate) fn seed(&self) -> Option<[u8; 32]> { - self.body.seed() + /// Checks if the declared hash of the transaction matches calculated hash. + pub fn has_valid_hash(&self) -> Result<(), InvalidTransactionV1> { + let computed_hash = Digest::hash( + self.payload + .to_bytes() + .unwrap_or_else(|error| panic!("should serialize body: {}", error)), + ); + if TransactionV1Hash::new(computed_hash) != self.hash { + debug!(?self, ?computed_hash, "invalid transaction hash"); + return Err(InvalidTransactionV1::InvalidTransactionHash); + } + Ok(()) } -} -#[cfg(any(feature = "std", test))] -impl GasLimited for TransactionV1 { - type Error = InvalidTransactionV1; + /// Returns `Ok` if and only if: + /// * the transaction hash is correct (see [`TransactionV1::has_valid_hash`] for details) + /// * approvals are non empty, and + /// * all approvals are valid signatures of the signed hash + pub fn verify(&self) -> Result<(), InvalidTransactionV1> { + #[cfg(any(feature = "once_cell", test))] + return self.is_verified.get_or_init(|| self.do_verify()).clone(); - fn gas_cost(&self, chainspec: &Chainspec, gas_price: u8) -> Result { - let gas_limit = self.gas_limit(chainspec)?; - let motes = match self.header().pricing_mode() { - PricingMode::PaymentLimited { .. } | PricingMode::Fixed { .. } => { - Motes::from_gas(gas_limit, gas_price) - .ok_or(InvalidTransactionV1::UnableToCalculateGasCost)? - } - PricingMode::Reserved { .. } => { - Motes::zero() // prepaid - } - }; - Ok(motes) + #[cfg(not(any(feature = "once_cell", test)))] + self.do_verify() } - fn gas_limit(&self, chainspec: &Chainspec) -> Result { - let costs = chainspec.system_costs_config; - let gas = match self.header().pricing_mode() { - PricingMode::PaymentLimited { payment_amount, .. } => Gas::new(*payment_amount), - PricingMode::Fixed { .. } => { - let computation_limit = { - if self.is_native_mint() { - // Because we currently only support one native mint interaction, - // native transfer, we can short circuit to return that value. - // However if other direct mint interactions are supported - // in the future (such as the upcoming burn feature), - // this logic will need to be expanded to self.mint_costs().field? - // for the value for each verb...see how auction is set up below. - costs.mint_costs().transfer as u64 - } else if self.is_native_auction() { - let entry_point = self.body().entry_point(); - let amount = match entry_point { - TransactionEntryPoint::Call => { - return Err(InvalidTransactionV1::EntryPointCannotBeCall) - } - TransactionEntryPoint::Custom(_) | TransactionEntryPoint::Transfer => { - return Err(InvalidTransactionV1::EntryPointCannotBeCustom { - entry_point: entry_point.clone(), - }); - } - TransactionEntryPoint::AddBid | TransactionEntryPoint::ActivateBid => { - costs.auction_costs().add_bid.into() - } - TransactionEntryPoint::WithdrawBid => { - costs.auction_costs().withdraw_bid.into() - } - TransactionEntryPoint::Delegate => { - costs.auction_costs().delegate.into() - } - TransactionEntryPoint::Undelegate => { - costs.auction_costs().undelegate.into() - } - TransactionEntryPoint::Redelegate => { - costs.auction_costs().redelegate.into() - } - TransactionEntryPoint::ChangeBidPublicKey => { - costs.auction_costs().change_bid_public_key - } - TransactionEntryPoint::AddReservations => { - costs.auction_costs().add_reservations.into() - } - TransactionEntryPoint::CancelReservations => { - costs.auction_costs().cancel_reservations.into() - } - }; - amount - } else { - chainspec.get_max_gas_limit_by_lane(self.body.transaction_lane) - } - }; - Gas::new(U512::from(computation_limit)) - } - PricingMode::Reserved { receipt } => { - return Err(InvalidTransactionV1::InvalidPricingMode { - price_mode: PricingMode::Reserved { receipt: *receipt }, - }); + fn do_verify(&self) -> Result<(), InvalidTransactionV1> { + if self.approvals.is_empty() { + debug!(?self, "transaction has no approvals"); + return Err(InvalidTransactionV1::EmptyApprovals); + } + + self.has_valid_hash()?; + + for (index, approval) in self.approvals.iter().enumerate() { + if let Err(error) = crypto::verify(self.hash, approval.signature(), approval.signer()) { + debug!( + ?self, + "failed to verify transaction approval {}: {}", index, error + ); + return Err(InvalidTransactionV1::InvalidApproval { index, error }); } - }; - Ok(gas) + } + + Ok(()) } - fn gas_price_tolerance(&self) -> Result { - Ok(self.header.gas_price_tolerance()) + /// Returns the hash of the transaction's payload. + pub fn payload_hash(&self) -> Result { + let bytes = self + .payload + .fields() + .to_bytes() + .map_err(|_| InvalidTransactionV1::CannotCalculateFieldsHash)?; + Ok(Digest::hash(bytes)) } -} -impl hash::Hash for TransactionV1 { - fn hash(&self, state: &mut H) { - // Destructure to make sure we don't accidentally omit fields. - let TransactionV1 { - serialization_version, - hash, - header, - body, - approvals, - #[cfg(any(feature = "once_cell", test))] - is_verified: _, - } = self; - serialization_version.hash(state); - hash.hash(state); - header.hash(state); - body.hash(state); - approvals.hash(state); + fn serialized_field_lengths(&self) -> Vec { + vec![ + self.hash.serialized_length(), + self.payload.serialized_length(), + self.approvals.serialized_length(), + ] } -} -impl PartialEq for TransactionV1 { - fn eq(&self, other: &TransactionV1) -> bool { - // Destructure to make sure we don't accidentally omit fields. - let TransactionV1 { - serialization_version, - hash, - header, - body, - approvals, - #[cfg(any(feature = "once_cell", test))] - is_verified: _, - } = self; - *serialization_version == other.serialization_version - && *hash == other.hash - && *header == other.header - && *body == other.body - && *approvals == other.approvals + /// Turns `self` into an invalid `TransactionV1` by clearing the `chain_name`, invalidating the + /// transaction hash + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn invalidate(&mut self) { + self.payload.invalidate(); } -} -impl Ord for TransactionV1 { - fn cmp(&self, other: &TransactionV1) -> cmp::Ordering { - // Destructure to make sure we don't accidentally omit fields. - let TransactionV1 { - serialization_version, - hash, - header, - body, - approvals, - #[cfg(any(feature = "once_cell", test))] - is_verified: _, - } = self; - serialization_version - .cmp(&other.serialization_version) - .then_with(|| hash.cmp(&other.hash)) - .then_with(|| header.cmp(&other.header)) - .then_with(|| body.cmp(&other.body)) - .then_with(|| approvals.cmp(&other.approvals)) + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub(crate) fn get_transaction_target(&self) -> Result { + self.deserialize_field::(TARGET_MAP_KEY) + .map_err(|error| InvalidTransactionV1::CouldNotDeserializeField { error }) } -} -impl PartialOrd for TransactionV1 { - fn partial_cmp(&self, other: &TransactionV1) -> Option { - Some(self.cmp(other)) + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub(crate) fn get_transaction_entry_point( + &self, + ) -> Result { + self.deserialize_field::(ENTRY_POINT_MAP_KEY) + .map_err(|error| InvalidTransactionV1::CouldNotDeserializeField { error }) + } + + /// Returns the gas price tolerance for the given transaction. + pub fn gas_price_tolerance(&self) -> u8 { + match self.pricing_mode() { + PricingMode::PaymentLimited { + gas_price_tolerance, + .. + } => *gas_price_tolerance, + PricingMode::Fixed { + gas_price_tolerance, + .. + } => *gas_price_tolerance, + PricingMode::Reserved { .. } => { + // TODO: Change this when reserve gets implemented. + 0u8 + } + } } } @@ -837,11 +417,9 @@ impl ToBytes for TransactionV1 { fn to_bytes(&self) -> Result, crate::bytesrepr::Error> { let expected_payload_sizes = self.serialized_field_lengths(); CalltableSerializationEnvelopeBuilder::new(expected_payload_sizes)? - .add_field(SERIALIZATION_VERSION_INDEX, &self.serialization_version)? - .add_field(HASH_FIELD_META_INDEX, &self.hash)? - .add_field(HEADER_FIELD_META_INDEX, &self.header)? - .add_field(BODY_FIELD_META_INDEX, &self.body)? - .add_field(APPROVALS_FIELD_META_INDEX, &self.approvals)? + .add_field(HASH_FIELD_INDEX, &self.hash)? + .add_field(PAYLOAD_FIELD_INDEX, &self.payload)? + .add_field(APPROVALS_FIELD_INDEX, &self.approvals)? .binary_payload_bytes() } @@ -852,39 +430,26 @@ impl ToBytes for TransactionV1 { impl FromBytes for TransactionV1 { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { - let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(5, bytes)?; - let window = binary_payload.start_consuming()?.ok_or(Formatting)?; - window.verify_index(SERIALIZATION_VERSION_INDEX)?; - - let (serialization_version, window) = window.deserialize_and_maybe_next::()?; - if serialization_version != TRANSACTION_V1_SERIALIZATION_VERSION { - return Err(Formatting); - } - let window = window.ok_or(Formatting)?; - window.verify_index(HASH_FIELD_META_INDEX)?; + let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(3, bytes)?; + let window = binary_payload.start_consuming()?.ok_or(Error::Formatting)?; + window.verify_index(HASH_FIELD_INDEX)?; let (hash, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(HEADER_FIELD_META_INDEX)?; - let (header, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(BODY_FIELD_META_INDEX)?; - let (body, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(APPROVALS_FIELD_META_INDEX)?; + let window = window.ok_or(Error::Formatting)?; + window.verify_index(PAYLOAD_FIELD_INDEX)?; + let (payload, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Error::Formatting)?; + window.verify_index(APPROVALS_FIELD_INDEX)?; let (approvals, window) = window.deserialize_and_maybe_next::>()?; if window.is_some() { - return Err(Formatting); + return Err(Error::Formatting); } let from_bytes = TransactionV1 { - serialization_version, hash, - header, - body, + payload, approvals, #[cfg(any(feature = "once_cell", test))] is_verified: OnceCell::new(), }; - Ok((from_bytes, remainder)) } } @@ -894,565 +459,61 @@ impl Display for TransactionV1 { write!( formatter, "transaction-v1[{}, {}, approvals: {}]", - self.header, - self.body, + self.hash, + self.payload, DisplayIter::new(self.approvals.iter()) ) } } -#[cfg(test)] -mod tests { - use std::time::Duration; - - use crate::bytesrepr; - - use super::*; - - const MAX_ASSOCIATED_KEYS: u32 = 5; - - #[test] - fn json_roundtrip() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1::random(rng); - let json_string = serde_json::to_string_pretty(&transaction).unwrap(); - let decoded = serde_json::from_str(&json_string).unwrap(); - assert_eq!(transaction, decoded); - } - - #[test] - fn bincode_roundtrip() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1::random(rng); - let serialized = bincode::serialize(&transaction).unwrap(); - let deserialized = bincode::deserialize(&serialized).unwrap(); - assert_eq!(transaction, deserialized); - } - - #[test] - fn bytesrepr_roundtrip() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1::random(rng); - bytesrepr::test_serialization_roundtrip(transaction.header()); - bytesrepr::test_serialization_roundtrip(&transaction); - } - - #[test] - fn is_valid() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1::random(rng); - assert_eq!( - transaction.is_verified.get(), - None, - "is_verified should initially be None" - ); - transaction.verify().expect("should verify"); - assert_eq!( - transaction.is_verified.get(), - Some(&Ok(())), - "is_verified should be true" - ); - } - - fn check_is_not_valid( - invalid_transaction: TransactionV1, - expected_error: InvalidTransactionV1, - ) { - assert!( - invalid_transaction.is_verified.get().is_none(), - "is_verified should initially be None" - ); - let actual_error = invalid_transaction.verify().unwrap_err(); - - // Ignore the `error_msg` field of `InvalidApproval` when comparing to expected error, as - // this makes the test too fragile. Otherwise expect the actual error should exactly match - // the expected error. - match expected_error { - InvalidTransactionV1::InvalidApproval { - index: expected_index, - .. - } => match actual_error { - InvalidTransactionV1::InvalidApproval { - index: actual_index, - .. - } => { - assert_eq!(actual_index, expected_index); - } - _ => panic!("expected {}, got: {}", expected_error, actual_error), - }, - _ => { - assert_eq!(actual_error, expected_error,); - } - } - - // The actual error should have been lazily initialized correctly. - assert_eq!( - invalid_transaction.is_verified.get(), - Some(&Err(actual_error)), - "is_verified should now be Some" - ); - } - - #[test] - fn not_valid_due_to_invalid_transaction_hash() { - let rng = &mut TestRng::new(); - let mut transaction = TransactionV1::random(rng); - - transaction.invalidate(); - check_is_not_valid(transaction, InvalidTransactionV1::InvalidTransactionHash); - } - - #[test] - fn not_valid_due_to_empty_approvals() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1Builder::new_random(rng) - .with_no_secret_key() - .build() - .unwrap(); - assert!(transaction.approvals.is_empty()); - check_is_not_valid(transaction, InvalidTransactionV1::EmptyApprovals) - } - - #[test] - fn not_valid_due_to_invalid_approval() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1Builder::new_random(rng) - .with_invalid_approval(rng) - .build() - .unwrap(); - - // The expected index for the invalid approval will be the first index at which there is an - // approval where the signer is not the account holder. - let account_holder = match transaction.initiator_addr() { - InitiatorAddr::PublicKey(public_key) => public_key.clone(), - InitiatorAddr::AccountHash(_) => unreachable!(), - }; - let expected_index = transaction - .approvals - .iter() - .enumerate() - .find(|(_, approval)| approval.signer() != &account_holder) - .map(|(index, _)| index) - .unwrap(); - check_is_not_valid( - transaction, - InvalidTransactionV1::InvalidApproval { - index: expected_index, - error: crypto::Error::SignatureError, // This field is ignored in the check. - }, - ); - } - - #[test] - fn is_config_compliant() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - let transaction = TransactionV1Builder::new_random_with_lane_and_timestamp_and_ttl( - rng, - TransactionLane::Large as u8, - None, - None, - ) - .with_chain_name(chain_name) - .build() - .unwrap(); - let current_timestamp = transaction.timestamp(); - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret - }; - transaction - .is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp) - .expect("should be acceptable"); - } - - #[test] - fn not_acceptable_due_to_invalid_chain_name() { - let rng = &mut TestRng::new(); - let expected_chain_name = "net-1"; - let wrong_chain_name = "net-2"; - let transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(wrong_chain_name) - .build() - .unwrap(); - - let expected_error = InvalidTransactionV1::InvalidChainName { - expected: expected_chain_name.to_string(), - got: wrong_chain_name.to_string(), - }; - - let current_timestamp = transaction.timestamp(); - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = expected_chain_name.to_string(); - ret - }; - assert_eq!( - transaction.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp), - Err(expected_error) - ); - assert!( - transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - } - - #[test] - fn not_acceptable_due_to_excessive_ttl() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - let transaction_config = TransactionConfig::default(); - let ttl = transaction_config.max_ttl + TimeDiff::from(Duration::from_secs(1)); - let transaction = TransactionV1Builder::new_random(rng) - .with_ttl(ttl) - .with_chain_name(chain_name) - .build() - .unwrap(); - - let expected_error = InvalidTransactionV1::ExcessiveTimeToLive { - max_ttl: transaction_config.max_ttl, - got: ttl, - }; - - let current_timestamp = transaction.timestamp(); - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret - }; - assert_eq!( - transaction.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp), - Err(expected_error) - ); - assert!( - transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - } - - #[test] - fn not_acceptable_due_to_timestamp_in_future() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - let leeway = TimeDiff::from_seconds(2); - - let transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .build() - .unwrap(); - let current_timestamp = transaction.timestamp() - leeway - TimeDiff::from_seconds(1); - - let expected_error = InvalidTransactionV1::TimestampInFuture { - validation_timestamp: current_timestamp, - timestamp_leeway: leeway, - got: transaction.timestamp(), - }; - - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret - }; - - assert_eq!( - transaction.is_config_compliant(&chainspec, leeway, current_timestamp), - Err(expected_error) - ); - assert!( - transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - } - - #[test] - fn not_acceptable_due_to_excessive_approvals() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - let mut transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .build() - .unwrap(); - - for _ in 0..MAX_ASSOCIATED_KEYS { - transaction.sign(&SecretKey::random(rng)); - } - - let current_timestamp = transaction.timestamp(); - - let expected_error = InvalidTransactionV1::ExcessiveApprovals { - got: MAX_ASSOCIATED_KEYS + 1, - max_associated_keys: MAX_ASSOCIATED_KEYS, - }; - - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret.core_config.max_associated_keys = MAX_ASSOCIATED_KEYS; - ret - }; - - assert_eq!( - transaction.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp), - Err(expected_error) - ); - assert!( - transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - } - - #[test] - fn not_acceptable_due_to_invalid_pricing_modes() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - - let reserved_mode = PricingMode::Reserved { - receipt: Default::default(), - }; - - let reserved_transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(reserved_mode.clone()) - .build() - .expect("must be able to create a reserved transaction"); - - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret - }; - - let current_timestamp = reserved_transaction.timestamp(); - let expected_error = InvalidTransactionV1::InvalidPricingMode { - price_mode: reserved_mode, - }; - assert_eq!( - reserved_transaction.is_config_compliant( - &chainspec, - TimeDiff::default(), - current_timestamp, - ), - Err(expected_error) - ); - assert!( - reserved_transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - - let fixed_mode_transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(PricingMode::Fixed { - gas_price_tolerance: 1u8, - }) - .build() - .expect("must create fixed mode transaction"); - - let fixed_handling_chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret.core_config.pricing_handling = PricingHandling::Fixed; - ret - }; - - let classic_handling_chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret.core_config.pricing_handling = PricingHandling::Classic; - ret - }; - - let current_timestamp = fixed_mode_transaction.timestamp(); - let expected_error = InvalidTransactionV1::InvalidPricingMode { - price_mode: fixed_mode_transaction.pricing_mode().clone(), - }; - - assert_eq!( - fixed_mode_transaction.is_config_compliant( - &classic_handling_chainspec, - TimeDiff::default(), - current_timestamp, - ), - Err(expected_error) - ); - assert!( - fixed_mode_transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - - assert!(fixed_mode_transaction - .is_config_compliant( - &fixed_handling_chainspec, - TimeDiff::default(), - current_timestamp, - ) - .is_ok()); - - let classic_mode_transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(PricingMode::PaymentLimited { - payment_amount: 100000, - gas_price_tolerance: 1, - standard_payment: true, - }) - .build() - .expect("must create classic transaction"); - - let current_timestamp = classic_mode_transaction.timestamp(); - let expected_error = InvalidTransactionV1::InvalidPricingMode { - price_mode: classic_mode_transaction.pricing_mode().clone(), - }; - - assert_eq!( - classic_mode_transaction.is_config_compliant( - &fixed_handling_chainspec, - TimeDiff::default(), - current_timestamp, - ), - Err(expected_error) - ); - assert!( - classic_mode_transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - - assert!(classic_mode_transaction - .is_config_compliant( - &classic_handling_chainspec, - TimeDiff::default(), - current_timestamp, - ) - .is_ok()); - } - - #[test] - fn should_use_payment_amount_for_classic_payment() { - let payment_amount = 500u64; - let mut chainspec = Chainspec::default(); - let chain_name = "net-1"; - chainspec - .with_chain_name(chain_name.to_string()) - .with_pricing_handling(PricingHandling::Classic); - - let rng = &mut TestRng::new(); - let builder = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(PricingMode::PaymentLimited { - payment_amount, - gas_price_tolerance: 1, - standard_payment: true, - }); - let transaction = builder.build().expect("should build"); - let mut gas_price = 1; - let cost = transaction - .gas_cost(&chainspec, gas_price) - .expect("should cost") - .value(); - assert_eq!( - cost, - U512::from(payment_amount), - "in classic pricing, the user selected amount should be the cost if gas price is 1" - ); - gas_price += 1; - let cost = transaction - .gas_cost(&chainspec, gas_price) - .expect("should cost") - .value(); - assert_eq!( - cost, - U512::from(payment_amount) * gas_price, - "in classic pricing, the cost should == user selected amount * gas_price" - ); +impl hash::Hash for TransactionV1 { + fn hash(&self, state: &mut H) { + // Destructure to make sure we don't accidentally omit fields. + let TransactionV1 { + hash, + payload, + approvals, + #[cfg(any(feature = "once_cell", test))] + is_verified: _, + } = self; + hash.hash(state); + payload.hash(state); + approvals.hash(state); } +} - #[test] - fn should_use_cost_table_for_fixed_payment() { - let mut chainspec = Chainspec::default(); - let chain_name = "net-1"; - chainspec - .with_chain_name(chain_name.to_string()) - .with_pricing_handling(PricingHandling::Fixed); - - let rng = &mut TestRng::new(); - let builder = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(PricingMode::Fixed { - gas_price_tolerance: 5, - }); - let transaction = builder.build().expect("should build"); - let mut gas_price = 1; - let limit = transaction - .gas_limit(&chainspec) - .expect("should limit") - .value(); - let cost = transaction - .gas_cost(&chainspec, gas_price) - .expect("should cost") - .value(); - assert_eq!( - cost, limit, - "in fixed pricing, the cost & limit should == if gas price is 1" - ); - gas_price += 1; - let cost = transaction - .gas_cost(&chainspec, gas_price) - .expect("should cost") - .value(); - assert_eq!( - cost, - limit * gas_price, - "in fixed pricing, the cost should == limit * gas_price" - ); +impl PartialEq for TransactionV1 { + fn eq(&self, other: &TransactionV1) -> bool { + // Destructure to make sure we don't accidentally omit fields. + let TransactionV1 { + hash, + payload, + approvals, + #[cfg(any(feature = "once_cell", test))] + is_verified: _, + } = self; + *hash == other.hash && *payload == other.payload && *approvals == other.approvals } } -#[cfg(test)] -mod proptests { - use super::gens::v1_transaction_arb; - use crate::bytesrepr::test_serialization_roundtrip; - use proptest::prelude::*; - - proptest! { - #[test] - fn bytesrepr_roundtrip(v1_transaction in v1_transaction_arb()) { - test_serialization_roundtrip(&v1_transaction); - } +impl Ord for TransactionV1 { + fn cmp(&self, other: &TransactionV1) -> cmp::Ordering { + // Destructure to make sure we don't accidentally omit fields. + let TransactionV1 { + hash, + payload, + approvals, + #[cfg(any(feature = "once_cell", test))] + is_verified: _, + } = self; + hash.cmp(&other.hash) + .then_with(|| payload.cmp(&other.payload)) + .then_with(|| approvals.cmp(&other.approvals)) } } -#[cfg(test)] -pub(crate) mod gens { - use super::*; - use crate::{gens::*, PublicKey}; - use crypto::gens::secret_key_arb_no_system; - use proptest::prelude::*; - - pub fn v1_transaction_arb() -> impl Strategy { - ( - any::(), - any::(), - any::(), - v1_transaction_body_arb(), - pricing_mode_arb(), - secret_key_arb_no_system(), - ) - .prop_map( - |(chain_name, timestamp, ttl, body, pricing_mode, secret_key)| { - let public_key = PublicKey::from(&secret_key); - let initiator_addr = InitiatorAddr::PublicKey(public_key); - let initiator_addr_with_secret = InitiatorAddrAndSecretKey::Both { - initiator_addr, - secret_key: &secret_key, - }; - TransactionV1::build( - chain_name, - Timestamp::from(timestamp), - TimeDiff::from_seconds(ttl), - body, - pricing_mode, - initiator_addr_with_secret, - ) - }, - ) +impl PartialOrd for TransactionV1 { + fn partial_cmp(&self, other: &TransactionV1) -> Option { + Some(self.cmp(other)) } } diff --git a/types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs b/types/src/transaction/transaction_v1/arg_handling.rs similarity index 95% rename from types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs rename to types/src/transaction/transaction_v1/arg_handling.rs index 51c6bc8fce..4cbd257911 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs +++ b/types/src/transaction/transaction_v1/arg_handling.rs @@ -1,15 +1,20 @@ +//! Collection of helper functions and structures to reason about amorphic RuntimeArgs. use core::marker::PhantomData; +#[cfg(any(all(feature = "std", feature = "testing"), test))] use tracing::debug; use super::TransactionArgs; #[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::{account::AccountHash, system::auction::ARG_VALIDATOR, CLType}; use crate::{ - bytesrepr::{FromBytes, ToBytes}, - CLTyped, CLValue, CLValueError, InvalidTransactionV1, PublicKey, RuntimeArgs, TransferTarget, - URef, U512, + account::AccountHash, bytesrepr::FromBytes, system::auction::ARG_VALIDATOR, CLType, CLValue, + InvalidTransactionV1, +}; +use crate::{ + bytesrepr::ToBytes, CLTyped, CLValueError, PublicKey, RuntimeArgs, TransferTarget, URef, U512, }; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use alloc::string::ToString; const TRANSFER_ARG_AMOUNT: RequiredArg = RequiredArg::new("amount"); const TRANSFER_ARG_SOURCE: OptionalArg = OptionalArg::new("source"); @@ -43,7 +48,9 @@ const REDELEGATE_ARG_NEW_VALIDATOR: RequiredArg = RequiredArg::new("n #[cfg(any(all(feature = "std", feature = "testing"), test))] const ACTIVATE_BID_ARG_VALIDATOR: RequiredArg = RequiredArg::new(ARG_VALIDATOR); +#[cfg(any(all(feature = "std", feature = "testing"), test))] const CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY: RequiredArg = RequiredArg::new("public_key"); +#[cfg(any(all(feature = "std", feature = "testing"), test))] const CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY: RequiredArg = RequiredArg::new("new_public_key"); @@ -60,6 +67,7 @@ impl RequiredArg { } } + #[cfg(any(all(feature = "std", feature = "testing"), test))] fn get(&self, args: &RuntimeArgs) -> Result where T: CLTyped + FromBytes, @@ -115,6 +123,7 @@ impl OptionalArg { } } +#[cfg(any(all(feature = "std", feature = "testing"), test))] fn parse_cl_value( cl_value: &CLValue, arg_name: &str, @@ -137,10 +146,7 @@ fn parse_cl_value( } /// Creates a `RuntimeArgs` suitable for use in a transfer transaction. -pub(in crate::transaction::transaction_v1) fn new_transfer_args< - A: Into, - T: Into, ->( +pub fn new_transfer_args, T: Into>( amount: A, maybe_source: Option, target: T, @@ -166,7 +172,7 @@ pub(in crate::transaction::transaction_v1) fn new_transfer_args< /// Checks the given `RuntimeArgs` are suitable for use in a transfer transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_transfer_args( +pub fn has_valid_transfer_args( args: &TransactionArgs, native_transfer_minimum_motes: u64, ) -> Result<(), InvalidTransactionV1> { @@ -226,7 +232,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_transfer_args( } /// Creates a `RuntimeArgs` suitable for use in an add_bid transaction. -pub(in crate::transaction::transaction_v1) fn new_add_bid_args>( +pub fn new_add_bid_args>( public_key: PublicKey, delegation_rate: u8, amount: A, @@ -244,9 +250,7 @@ pub(in crate::transaction::transaction_v1) fn new_add_bid_args>( /// Checks the given `RuntimeArgs` are suitable for use in an add_bid transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_add_bid_args( - args: &TransactionArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_add_bid_args(args: &TransactionArgs) -> Result<(), InvalidTransactionV1> { let args = args .as_named() .ok_or(InvalidTransactionV1::ExpectedNamedArguments)?; @@ -257,7 +261,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_add_bid_args( } /// Creates a `RuntimeArgs` suitable for use in a withdraw_bid transaction. -pub(in crate::transaction::transaction_v1) fn new_withdraw_bid_args>( +pub fn new_withdraw_bid_args>( public_key: PublicKey, amount: A, ) -> Result { @@ -269,9 +273,7 @@ pub(in crate::transaction::transaction_v1) fn new_withdraw_bid_args Result<(), InvalidTransactionV1> { +pub fn has_valid_withdraw_bid_args(args: &TransactionArgs) -> Result<(), InvalidTransactionV1> { let args = args .as_named() .ok_or(InvalidTransactionV1::ExpectedNamedArguments)?; @@ -281,7 +283,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_withdraw_bid_args( } /// Creates a `RuntimeArgs` suitable for use in a delegate transaction. -pub(in crate::transaction::transaction_v1) fn new_delegate_args>( +pub fn new_delegate_args>( delegator: PublicKey, validator: PublicKey, amount: A, @@ -295,9 +297,7 @@ pub(in crate::transaction::transaction_v1) fn new_delegate_args>( /// Checks the given `RuntimeArgs` are suitable for use in a delegate transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_delegate_args( - args: &TransactionArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_delegate_args(args: &TransactionArgs) -> Result<(), InvalidTransactionV1> { let args = args .as_named() .ok_or(InvalidTransactionV1::ExpectedNamedArguments)?; @@ -308,7 +308,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_delegate_args( } /// Creates a `RuntimeArgs` suitable for use in an undelegate transaction. -pub(in crate::transaction::transaction_v1) fn new_undelegate_args>( +pub fn new_undelegate_args>( delegator: PublicKey, validator: PublicKey, amount: A, @@ -322,9 +322,7 @@ pub(in crate::transaction::transaction_v1) fn new_undelegate_args> /// Checks the given `RuntimeArgs` are suitable for use in an undelegate transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_undelegate_args( - args: &TransactionArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_undelegate_args(args: &TransactionArgs) -> Result<(), InvalidTransactionV1> { let args = args .as_named() .ok_or(InvalidTransactionV1::ExpectedNamedArguments)?; @@ -335,7 +333,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_undelegate_args( } /// Creates a `RuntimeArgs` suitable for use in a redelegate transaction. -pub(in crate::transaction::transaction_v1) fn new_redelegate_args>( +pub fn new_redelegate_args>( delegator: PublicKey, validator: PublicKey, amount: A, @@ -351,9 +349,7 @@ pub(in crate::transaction::transaction_v1) fn new_redelegate_args> /// Checks the given `RuntimeArgs` are suitable for use in a redelegate transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_redelegate_args( - args: &TransactionArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_redelegate_args(args: &TransactionArgs) -> Result<(), InvalidTransactionV1> { let args = args .as_named() .ok_or(InvalidTransactionV1::ExpectedNamedArguments)?; @@ -366,9 +362,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_redelegate_args( /// Checks the given `RuntimeArgs` are suitable for use in an activate bid transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_activate_bid_args( - args: &TransactionArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_activate_bid_args(args: &TransactionArgs) -> Result<(), InvalidTransactionV1> { let args = args .as_named() .ok_or(InvalidTransactionV1::ExpectedNamedArguments)?; @@ -378,7 +372,8 @@ pub(in crate::transaction::transaction_v1) fn has_valid_activate_bid_args( /// Checks the given `RuntimeArgs` are suitable for use in a change bid public key transaction. #[allow(dead_code)] -pub(super) fn has_valid_change_bid_public_key_args( +#[cfg(any(all(feature = "std", feature = "testing"), test))] +pub fn has_valid_change_bid_public_key_args( args: &TransactionArgs, ) -> Result<(), InvalidTransactionV1> { let args = args diff --git a/types/src/transaction/transaction_v1/errors_v1.rs b/types/src/transaction/transaction_v1/errors_v1.rs index 2ad7dfe0bf..aa48151e3a 100644 --- a/types/src/transaction/transaction_v1/errors_v1.rs +++ b/types/src/transaction/transaction_v1/errors_v1.rs @@ -10,14 +10,22 @@ use std::error::Error as StdError; use datasize::DataSize; use serde::Serialize; -use super::super::TransactionEntryPoint; #[cfg(doc)] use super::TransactionV1; use crate::{ - bytesrepr, crypto, transaction::PricingMode, CLType, DisplayIter, TimeDiff, Timestamp, - TransactionRuntime, U512, + bytesrepr, crypto, CLType, DisplayIter, PricingMode, TimeDiff, Timestamp, + TransactionEntryPoint, TransactionRuntime, U512, }; +#[derive(Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "std", derive(Serialize))] +#[cfg_attr(feature = "datasize", derive(DataSize))] +pub enum FieldDeserializationError { + IndexNotExists { index: u16 }, + FromBytesError { index: u16, error: bytesrepr::Error }, + LingeringBytesInField { index: u16 }, +} + /// Returned when a [`TransactionV1`] fails validation. #[derive(Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "std", derive(Serialize))] @@ -138,6 +146,11 @@ pub enum InvalidTransaction { /// The invalid entry point. entry_point: TransactionEntryPoint, }, + /// The entry point for this transaction target must be `TransactionEntryPoint::Call`. + EntryPointMustBeCall { + /// The invalid entry point. + entry_point: TransactionEntryPoint, + }, /// The transaction has empty module bytes. EmptyModuleBytes, /// Attempt to factor the amount over the gas_price failed. @@ -157,7 +170,9 @@ pub enum InvalidTransaction { price_mode: PricingMode, }, /// The transaction provided is not supported. - InvalidTransactionKind(u8), + InvalidTransactionLane(u8), + /// No wasm lane matches transaction + NoWasmLaneMatchesTransaction(), /// Gas price tolerance too low. GasPriceToleranceTooLow { /// The minimum gas price tolerance. @@ -165,6 +180,17 @@ pub enum InvalidTransaction { /// The provided gas price tolerance. provided_gas_price_tolerance: u8, }, + /// Error when trying to deserialize one of the transactionV1 payload fields. + CouldNotDeserializeField { + /// Underlying reason why the deserialization failed + error: FieldDeserializationError, + }, + + /// Unable to calculate hash for payloads transaction. + CannotCalculateFieldsHash, + + /// The transactions field map had entries that were unexpected + UnexpectedTransactionFieldEntries, /// The transaction requires named arguments. ExpectedNamedArguments, /// The transaction runtime is invalid. @@ -172,6 +198,8 @@ pub enum InvalidTransaction { /// The expected runtime as specified by the chainspec. expected: TransactionRuntime, }, + /// The transaction is missing a seed field. + MissingSeed, } impl Display for InvalidTransaction { @@ -304,7 +332,7 @@ impl Display for InvalidTransaction { "received a transaction with an invalid mode {price_mode}" ) } - InvalidTransaction::InvalidTransactionKind(kind) => { + InvalidTransaction::InvalidTransactionLane(kind) => { write!( formatter, "received a transaction with an invalid kind {kind}" @@ -320,6 +348,35 @@ impl Display for InvalidTransaction { provided_gas_price_tolerance, min_gas_price_tolerance ) } + InvalidTransaction::CouldNotDeserializeField { error } => { + match error { + FieldDeserializationError::IndexNotExists { index } => write!( + formatter, + "tried to deserialize a field under index {} but it is not present in the payload", + index + ), + FieldDeserializationError::FromBytesError { index, error } => write!( + formatter, + "tried to deserialize a field under index {} but it failed with error: {}", + index, + error + ), + FieldDeserializationError::LingeringBytesInField { index } => write!( + formatter, + "tried to deserialize a field under index {} but after deserialization there were still bytes left", + index, + ), + } + }, + InvalidTransaction::CannotCalculateFieldsHash => write!( + formatter, + "cannot calculate a hash digest for the transaction" + ), + InvalidTransaction::EntryPointMustBeCall { entry_point } => { + write!(formatter, "entry point must be call: {entry_point}") + }, + InvalidTransaction::NoWasmLaneMatchesTransaction() => write!(formatter, "Could not match any generic wasm lane to the specified transaction"), + InvalidTransaction::UnexpectedTransactionFieldEntries => write!(formatter, "There were entries in the fields map of the payload that could not be matched"), InvalidTransaction::ExpectedNamedArguments => { write!(formatter, "transaction requires named arguments") } @@ -329,6 +386,9 @@ impl Display for InvalidTransaction { "invalid transaction runtime: expected {expected}" ) } + InvalidTransaction::MissingSeed => { + write!(formatter, "missing seed for install or upgrade") + } } } } @@ -361,15 +421,25 @@ impl StdError for InvalidTransaction { | InvalidTransaction::EntryPointCannotBeCall | InvalidTransaction::EntryPointCannotBeCustom { .. } | InvalidTransaction::EntryPointMustBeCustom { .. } + | InvalidTransaction::EntryPointMustBeCall { .. } | InvalidTransaction::EmptyModuleBytes | InvalidTransaction::GasPriceConversion { .. } | InvalidTransaction::UnableToCalculateGasLimit | InvalidTransaction::UnableToCalculateGasCost | InvalidTransaction::InvalidPricingMode { .. } | InvalidTransaction::GasPriceToleranceTooLow { .. } - | InvalidTransaction::InvalidTransactionKind(_) - | InvalidTransaction::ExpectedNamedArguments - | InvalidTransaction::InvalidTransactionRuntime { .. } => None, + | InvalidTransaction::InvalidTransactionLane(_) + | InvalidTransaction::CannotCalculateFieldsHash + | InvalidTransaction::NoWasmLaneMatchesTransaction() + | InvalidTransaction::UnexpectedTransactionFieldEntries => None, + InvalidTransaction::CouldNotDeserializeField { error } => match error { + FieldDeserializationError::IndexNotExists { .. } + | FieldDeserializationError::LingeringBytesInField { .. } => None, + FieldDeserializationError::FromBytesError { error, .. } => Some(error), + }, + InvalidTransaction::ExpectedNamedArguments + | InvalidTransaction::InvalidTransactionRuntime { .. } + | InvalidTransaction::MissingSeed => None, } } } diff --git a/types/src/transaction/transaction_v1/fields_container.rs b/types/src/transaction/transaction_v1/fields_container.rs new file mode 100644 index 0000000000..16649825d4 --- /dev/null +++ b/types/src/transaction/transaction_v1/fields_container.rs @@ -0,0 +1,287 @@ +#[cfg(any(feature = "testing", test))] +use crate::testing::TestRng; +#[cfg(any(feature = "std", feature = "testing", test))] +use crate::{ + bytesrepr::{Bytes, ToBytes}, + transaction::transaction_v1::*, + RuntimeArgs, TransactionEntryPoint, TransactionScheduling, TransactionTarget, +}; +#[cfg(any(feature = "testing", test))] +use crate::{ + PublicKey, TransactionInvocationTarget, TransactionRuntime, TransferTarget, AUCTION_LANE_ID, + INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, +}; +#[cfg(any(feature = "std", feature = "testing", test))] +use alloc::collections::BTreeMap; +#[cfg(any(feature = "testing", test))] +use rand::{Rng, RngCore}; + +#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))] +pub(crate) const ARGS_MAP_KEY: u16 = 0; +#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))] +pub(crate) const TARGET_MAP_KEY: u16 = 1; +#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))] +pub(crate) const ENTRY_POINT_MAP_KEY: u16 = 2; +#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))] +pub(crate) const SCHEDULING_MAP_KEY: u16 = 3; + +#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))] +#[derive(Clone, Eq, PartialEq, Debug)] +pub(crate) enum FieldsContainerError { + CouldNotSerializeField { field_index: u16 }, +} + +#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))] +pub(crate) struct FieldsContainer { + pub(super) args: TransactionArgs, + pub(super) target: TransactionTarget, + pub(super) entry_point: TransactionEntryPoint, + pub(super) scheduling: TransactionScheduling, +} + +#[cfg(any(feature = "std", feature = "testing", feature = "gens", test))] +impl FieldsContainer { + pub(crate) fn new( + args: TransactionArgs, + target: TransactionTarget, + entry_point: TransactionEntryPoint, + scheduling: TransactionScheduling, + ) -> Self { + FieldsContainer { + args, + target, + entry_point, + scheduling, + } + } + + pub(crate) fn to_map(&self) -> Result, FieldsContainerError> { + let mut map: BTreeMap = BTreeMap::new(); + map.insert( + ARGS_MAP_KEY, + self.args.to_bytes().map(Into::into).map_err(|_| { + FieldsContainerError::CouldNotSerializeField { + field_index: ARGS_MAP_KEY, + } + })?, + ); + map.insert( + TARGET_MAP_KEY, + self.target.to_bytes().map(Into::into).map_err(|_| { + FieldsContainerError::CouldNotSerializeField { + field_index: TARGET_MAP_KEY, + } + })?, + ); + map.insert( + ENTRY_POINT_MAP_KEY, + self.entry_point.to_bytes().map(Into::into).map_err(|_| { + FieldsContainerError::CouldNotSerializeField { + field_index: ENTRY_POINT_MAP_KEY, + } + })?, + ); + map.insert( + SCHEDULING_MAP_KEY, + self.scheduling.to_bytes().map(Into::into).map_err(|_| { + FieldsContainerError::CouldNotSerializeField { + field_index: SCHEDULING_MAP_KEY, + } + })?, + ); + Ok(map) + } + + /// Returns a random `FieldsContainer`. + #[cfg(any(feature = "testing", test))] + pub(crate) fn random(rng: &mut TestRng) -> Self { + match rng.gen_range(0..8) { + 0 => { + let amount = rng.gen_range(2_500_000_000..=u64::MAX); + let maybe_source = if rng.gen() { Some(rng.gen()) } else { None }; + let target = TransferTarget::random(rng); + let maybe_id = rng.gen::().then(|| rng.gen()); + let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id) + .unwrap(); + FieldsContainer::new( + TransactionArgs::Named(args), + TransactionTarget::Native, + TransactionEntryPoint::Transfer, + TransactionScheduling::random(rng), + ) + } + 1 => { + let public_key = PublicKey::random(rng); + let delegation_rate = rng.gen(); + let amount = rng.gen::(); + let minimum_delegation_amount = rng.gen::() as u64; + let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; + let args = arg_handling::new_add_bid_args( + public_key, + delegation_rate, + amount, + minimum_delegation_amount, + maximum_delegation_amount, + ) + .unwrap(); + FieldsContainer::new( + TransactionArgs::Named(args), + TransactionTarget::Native, + TransactionEntryPoint::AddBid, + TransactionScheduling::random(rng), + ) + } + 2 => { + let public_key = PublicKey::random(rng); + let amount = rng.gen::(); + let args = arg_handling::new_withdraw_bid_args(public_key, amount).unwrap(); + FieldsContainer::new( + TransactionArgs::Named(args), + TransactionTarget::Native, + TransactionEntryPoint::WithdrawBid, + TransactionScheduling::random(rng), + ) + } + 3 => { + let delegator = PublicKey::random(rng); + let validator = PublicKey::random(rng); + let amount = rng.gen::(); + let args = arg_handling::new_delegate_args(delegator, validator, amount).unwrap(); + FieldsContainer::new( + TransactionArgs::Named(args), + TransactionTarget::Native, + TransactionEntryPoint::Delegate, + TransactionScheduling::random(rng), + ) + } + 4 => { + let delegator = PublicKey::random(rng); + let validator = PublicKey::random(rng); + let amount = rng.gen::(); + let args = arg_handling::new_undelegate_args(delegator, validator, amount).unwrap(); + FieldsContainer::new( + TransactionArgs::Named(args), + TransactionTarget::Native, + TransactionEntryPoint::Undelegate, + TransactionScheduling::random(rng), + ) + } + 5 => { + let delegator = PublicKey::random(rng); + let validator = PublicKey::random(rng); + let amount = rng.gen::(); + let new_validator = PublicKey::random(rng); + let args = + arg_handling::new_redelegate_args(delegator, validator, amount, new_validator) + .unwrap(); + FieldsContainer::new( + TransactionArgs::Named(args), + TransactionTarget::Native, + TransactionEntryPoint::Redelegate, + TransactionScheduling::random(rng), + ) + } + 6 => Self::random_standard(rng), + 7 => { + let mut buffer = vec![0u8; rng.gen_range(1..100)]; + rng.fill_bytes(buffer.as_mut()); + let is_install_upgrade = rng.gen(); + let target = TransactionTarget::Session { + is_install_upgrade, + module_bytes: Bytes::from(buffer), + runtime: TransactionRuntime::VmCasperV1, + transferred_value: rng.gen(), + seed: rng.gen(), + }; + FieldsContainer::new( + TransactionArgs::Named(RuntimeArgs::random(rng)), + target, + TransactionEntryPoint::Call, + TransactionScheduling::random(rng), + ) + } + _ => unreachable!(), + } + } + + /// Returns a random `FieldsContainer`. + #[cfg(any(feature = "testing", test))] + pub fn random_of_lane(rng: &mut TestRng, lane_id: u8) -> Self { + match lane_id { + MINT_LANE_ID => Self::random_transfer(rng), + AUCTION_LANE_ID => Self::random_staking(rng), + INSTALL_UPGRADE_LANE_ID => Self::random_install_upgrade(rng), + _ => Self::random_standard(rng), + } + } + + #[cfg(any(feature = "testing", test))] + fn random_transfer(rng: &mut TestRng) -> Self { + let amount = rng.gen_range(2_500_000_000..=u64::MAX); + let maybe_source = if rng.gen() { Some(rng.gen()) } else { None }; + let target = TransferTarget::random(rng); + let maybe_id = rng.gen::().then(|| rng.gen()); + let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id).unwrap(); + FieldsContainer::new( + TransactionArgs::Named(args), + TransactionTarget::Native, + TransactionEntryPoint::Transfer, + TransactionScheduling::random(rng), + ) + } + + #[cfg(any(feature = "testing", test))] + fn random_install_upgrade(rng: &mut TestRng) -> Self { + let target = TransactionTarget::Session { + module_bytes: Bytes::from(rng.random_vec(0..100)), + runtime: TransactionRuntime::VmCasperV1, + is_install_upgrade: true, + transferred_value: 0, + seed: None, + }; + FieldsContainer::new( + TransactionArgs::Named(RuntimeArgs::random(rng)), + target, + TransactionEntryPoint::Call, + TransactionScheduling::random(rng), + ) + } + + #[cfg(any(feature = "testing", test))] + fn random_staking(rng: &mut TestRng) -> Self { + let public_key = PublicKey::random(rng); + let delegation_rate = rng.gen(); + let amount = rng.gen::(); + let minimum_delegation_amount = rng.gen::() as u64; + let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; + let args = arg_handling::new_add_bid_args( + public_key, + delegation_rate, + amount, + minimum_delegation_amount, + maximum_delegation_amount, + ) + .unwrap(); + FieldsContainer::new( + TransactionArgs::Named(args), + TransactionTarget::Native, + TransactionEntryPoint::AddBid, + TransactionScheduling::random(rng), + ) + } + + #[cfg(any(feature = "testing", test))] + fn random_standard(rng: &mut TestRng) -> Self { + let target = TransactionTarget::Stored { + id: TransactionInvocationTarget::random(rng), + runtime: TransactionRuntime::VmCasperV1, + transferred_value: rng.gen(), + }; + FieldsContainer::new( + TransactionArgs::Named(RuntimeArgs::random(rng)), + target, + TransactionEntryPoint::Custom(rng.random_string(1..11)), + TransactionScheduling::random(rng), + ) + } +} diff --git a/types/src/transaction/transaction_v1/transaction_args.rs b/types/src/transaction/transaction_v1/transaction_args.rs new file mode 100644 index 0000000000..d4ffb32c13 --- /dev/null +++ b/types/src/transaction/transaction_v1/transaction_args.rs @@ -0,0 +1,119 @@ +use crate::{ + bytesrepr::{self, Bytes, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + CLTyped, CLValueError, RuntimeArgs, +}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; +#[cfg(any(feature = "std", test))] +use serde::{Deserialize, Serialize}; + +/// The arguments of a transaction, which can be either a named set of runtime arguments or a +/// chunked bytes. +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +#[cfg_attr( + any(feature = "std", test), + derive(Serialize, Deserialize), + serde(deny_unknown_fields) +)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr( + feature = "json-schema", + derive(JsonSchema), + schemars(description = "Body of a `TransactionArgs`.") +)] +pub enum TransactionArgs { + /// Named runtime arguments. + Named(RuntimeArgs), + /// Bytesrepr bytes. + Bytesrepr(Bytes), +} + +impl TransactionArgs { + /// Returns `RuntimeArgs` if the transaction arguments are named. + pub fn as_named(&self) -> Option<&RuntimeArgs> { + match self { + TransactionArgs::Named(args) => Some(args), + TransactionArgs::Bytesrepr(_) => None, + } + } + + /// Returns `RuntimeArgs` if the transaction arguments are mnamed. + pub fn into_named(self) -> Option { + match self { + TransactionArgs::Named(args) => Some(args), + TransactionArgs::Bytesrepr(_) => None, + } + } + + /// Returns `Bytes` if the transaction arguments are chunked. + pub fn into_bytesrepr(self) -> Option { + match self { + TransactionArgs::Named(_) => None, + TransactionArgs::Bytesrepr(bytes) => Some(bytes), + } + } + + /// Inserts a key-value pair into the named runtime arguments. + pub fn insert(&mut self, key: K, value: V) -> Result<(), CLValueError> + where + K: Into, + V: CLTyped + ToBytes, + { + match self { + TransactionArgs::Named(args) => { + args.insert(key, value)?; + Ok(()) + } + TransactionArgs::Bytesrepr(_) => { + Err(CLValueError::Serialization(bytesrepr::Error::Formatting)) + } + } + } +} + +impl FromBytes for TransactionArgs { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder) = u8::from_bytes(bytes)?; + match tag { + 0 => { + let (args, remainder) = RuntimeArgs::from_bytes(remainder)?; + Ok((TransactionArgs::Named(args), remainder)) + } + 1 => { + let (bytes, remainder) = Bytes::from_bytes(remainder)?; + Ok((TransactionArgs::Bytesrepr(bytes), remainder)) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +impl ToBytes for TransactionArgs { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut buffer = bytesrepr::allocate_buffer(self)?; + self.write_bytes(&mut buffer)?; + Ok(buffer) + } + + fn serialized_length(&self) -> usize { + match self { + TransactionArgs::Named(args) => args.serialized_length() + U8_SERIALIZED_LENGTH, + TransactionArgs::Bytesrepr(bytes) => bytes.serialized_length() + U8_SERIALIZED_LENGTH, + } + } + + fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { + match self { + TransactionArgs::Named(args) => { + writer.push(0); + args.write_bytes(writer) + } + TransactionArgs::Bytesrepr(bytes) => { + writer.push(1); + bytes.write_bytes(writer) + } + } + } +} diff --git a/types/src/transaction/transaction_v1/transaction_v1_body.rs b/types/src/transaction/transaction_v1/transaction_v1_body.rs deleted file mode 100644 index 2619db7dee..0000000000 --- a/types/src/transaction/transaction_v1/transaction_v1_body.rs +++ /dev/null @@ -1,831 +0,0 @@ -#[cfg(any(feature = "std", test))] -pub(super) mod arg_handling; - -use alloc::{string::String, vec::Vec}; -use core::fmt::{self, Display, Formatter}; - -use super::super::{RuntimeArgs, TransactionEntryPoint, TransactionScheduling, TransactionTarget}; -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use rand::{Rng, RngCore}; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -#[cfg(any(feature = "std", test))] -use serde::{Deserialize, Serialize}; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use tracing::debug; - -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use super::TransactionConfig; -use super::TransactionLane; -#[cfg(doc)] -use super::TransactionV1; -#[cfg(any(feature = "std", test))] -use crate::InvalidTransactionV1; -use crate::TransactionRuntime; - -use crate::{ - bytesrepr::{self, Bytes, Error, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - transaction::serialization::{ - CalltableSerializationEnvelope, CalltableSerializationEnvelopeBuilder, - }, - CLTyped, CLValueError, -}; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::{testing::TestRng, PublicKey}; -/// The body of a [`TransactionV1`]. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] -#[cfg_attr( - any(feature = "std", test), - derive(Serialize, Deserialize), - serde(deny_unknown_fields) -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "Body of a `TransactionV1`.") -)] -pub struct TransactionV1Body { - pub(crate) args: TransactionArgs, - pub(crate) target: TransactionTarget, - pub(crate) entry_point: TransactionEntryPoint, - pub(crate) transaction_lane: u8, - pub(crate) scheduling: TransactionScheduling, -} - -/// The arguments of a transaction, which can be either a named set of runtime arguments or a -/// chunked bytes. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] -#[cfg_attr( - any(feature = "std", test), - derive(Serialize, Deserialize), - serde(deny_unknown_fields) -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "Body of a `TransactionArgs`.") -)] -pub enum TransactionArgs { - /// Named runtime arguments. - Named(RuntimeArgs), - /// Bytesrepr bytes. - Bytesrepr(Bytes), -} - -impl TransactionArgs { - /// Returns `RuntimeArgs` if the transaction arguments are named. - pub fn as_named(&self) -> Option<&RuntimeArgs> { - match self { - TransactionArgs::Named(args) => Some(args), - TransactionArgs::Bytesrepr(_) => None, - } - } - - /// Returns `RuntimeArgs` if the transaction arguments are mnamed. - pub fn into_named(self) -> Option { - match self { - TransactionArgs::Named(args) => Some(args), - TransactionArgs::Bytesrepr(_) => None, - } - } - - /// Returns `Bytes` if the transaction arguments are chunked. - pub fn into_bytesrepr(self) -> Option { - match self { - TransactionArgs::Named(_) => None, - TransactionArgs::Bytesrepr(bytes) => Some(bytes), - } - } - - /// Inserts a key-value pair into the named runtime arguments. - pub fn insert(&mut self, key: K, value: V) -> Result<(), CLValueError> - where - K: Into, - V: CLTyped + ToBytes, - { - match self { - TransactionArgs::Named(args) => { - args.insert(key, value)?; - Ok(()) - } - TransactionArgs::Bytesrepr(_) => { - Err(CLValueError::Serialization(bytesrepr::Error::Formatting)) - } - } - } -} - -impl FromBytes for TransactionArgs { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (tag, remainder) = u8::from_bytes(bytes)?; - match tag { - 0 => { - let (args, remainder) = RuntimeArgs::from_bytes(remainder)?; - Ok((TransactionArgs::Named(args), remainder)) - } - 1 => { - let (bytes, remainder) = Bytes::from_bytes(remainder)?; - Ok((TransactionArgs::Bytesrepr(bytes), remainder)) - } - _ => Err(bytesrepr::Error::Formatting), - } - } -} - -impl ToBytes for TransactionArgs { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - self.write_bytes(&mut buffer)?; - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - match self { - TransactionArgs::Named(args) => args.serialized_length() + U8_SERIALIZED_LENGTH, - TransactionArgs::Bytesrepr(bytes) => bytes.serialized_length() + U8_SERIALIZED_LENGTH, - } - } - - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - match self { - TransactionArgs::Named(args) => { - writer.push(0); - args.write_bytes(writer) - } - TransactionArgs::Bytesrepr(bytes) => { - writer.push(1); - bytes.write_bytes(writer) - } - } - } -} - -impl TransactionV1Body { - /// Returns a new `TransactionV1Body`. - pub fn new( - args: RuntimeArgs, - target: TransactionTarget, - entry_point: TransactionEntryPoint, - transaction_lane: u8, - scheduling: TransactionScheduling, - ) -> Self { - TransactionV1Body { - args: TransactionArgs::Named(args), - target, - entry_point, - transaction_lane, - scheduling, - } - } - - /// Returns a new `TransactionV1Body`. - pub fn new_v2( - args: TransactionArgs, - target: TransactionTarget, - entry_point: TransactionEntryPoint, - transaction_lane: u8, - scheduling: TransactionScheduling, - ) -> Self { - TransactionV1Body { - args, - target, - entry_point, - transaction_lane, - scheduling, - } - } - - /// Returns the runtime args of the transaction. - pub fn args(&self) -> &TransactionArgs { - &self.args - } - - /// Consumes `self`, returning the runtime args of the transaction. - pub fn take_args(self) -> TransactionArgs { - self.args - } - - /// Returns the target of the transaction. - pub fn target(&self) -> &TransactionTarget { - &self.target - } - - /// Returns the entry point of the transaction. - pub fn entry_point(&self) -> &TransactionEntryPoint { - &self.entry_point - } - - /// Returns the scheduling kind of the transaction. - pub fn scheduling(&self) -> &TransactionScheduling { - &self.scheduling - } - - /// Returns true if this transaction is a native mint interaction. - pub fn is_native_mint(&self) -> bool { - self.transaction_lane == TransactionLane::Mint as u8 - } - - /// Returns true if this transaction is a native auction interaction. - pub fn is_native_auction(&self) -> bool { - self.transaction_lane == TransactionLane::Auction as u8 - } - - /// Returns true if this transaction is a smart contract installer or upgrader. - pub fn is_install_or_upgrade(&self) -> bool { - self.transaction_lane == TransactionLane::InstallUpgrade as u8 - } - - /// Returns the transaction category. - pub fn transaction_lane(&self) -> u8 { - self.transaction_lane - } - - /// Returns the transaction runtime of the transaction. - pub fn transaction_runtime(&self) -> Option { - match self.target { - TransactionTarget::Native => None, - TransactionTarget::Stored { runtime, .. } => Some(runtime), - TransactionTarget::Session { runtime, .. } => Some(runtime), - } - } - - /// Consumes `self`, returning its constituent parts. - pub fn destructure( - self, - ) -> ( - TransactionArgs, - TransactionTarget, - TransactionEntryPoint, - TransactionScheduling, - ) { - (self.args, self.target, self.entry_point, self.scheduling) - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub(super) fn is_valid(&self, config: &TransactionConfig) -> Result<(), InvalidTransactionV1> { - use super::TransactionV1ExcessiveSizeError; - - let kind = self.transaction_lane; - if !config.transaction_v1_config.is_supported(kind) { - return Err(InvalidTransactionV1::InvalidTransactionKind( - self.transaction_lane, - )); - } - - let max_serialized_length = config.transaction_v1_config.get_max_serialized_length(kind); - let actual_length = self.serialized_length(); - if actual_length > max_serialized_length as usize { - return Err(InvalidTransactionV1::ExcessiveSize( - TransactionV1ExcessiveSizeError { - max_transaction_size: max_serialized_length as u32, - actual_transaction_size: actual_length, - }, - )); - } - - let max_args_length = config.transaction_v1_config.get_max_args_length(kind); - - let args_length = self.args.serialized_length(); - if args_length > max_args_length as usize { - debug!( - args_length, - max_args_length = max_args_length, - "transaction runtime args excessive size" - ); - return Err(InvalidTransactionV1::ExcessiveArgsLength { - max_length: max_args_length as usize, - got: args_length, - }); - } - - match &self.target { - TransactionTarget::Native => match self.entry_point { - TransactionEntryPoint::Call => { - debug!( - entry_point = %self.entry_point, - "native transaction cannot have call entry point" - ); - Err(InvalidTransactionV1::EntryPointCannotBeCall) - } - TransactionEntryPoint::Custom(_) => { - debug!( - entry_point = %self.entry_point, - "native transaction cannot have custom entry point" - ); - Err(InvalidTransactionV1::EntryPointCannotBeCustom { - entry_point: self.entry_point.clone(), - }) - } - TransactionEntryPoint::Transfer => arg_handling::has_valid_transfer_args( - &self.args, - config.native_transfer_minimum_motes, - ), - TransactionEntryPoint::AddBid => arg_handling::has_valid_add_bid_args(&self.args), - TransactionEntryPoint::WithdrawBid => { - arg_handling::has_valid_withdraw_bid_args(&self.args) - } - TransactionEntryPoint::Delegate => { - arg_handling::has_valid_delegate_args(&self.args) - } - TransactionEntryPoint::Undelegate => { - arg_handling::has_valid_undelegate_args(&self.args) - } - TransactionEntryPoint::Redelegate => { - arg_handling::has_valid_redelegate_args(&self.args) - } - TransactionEntryPoint::ActivateBid => { - arg_handling::has_valid_activate_bid_args(&self.args) - } - TransactionEntryPoint::ChangeBidPublicKey => { - arg_handling::has_valid_change_bid_public_key_args(&self.args) - } - TransactionEntryPoint::AddReservations => { - todo!() - } - TransactionEntryPoint::CancelReservations => { - todo!() - } - }, - TransactionTarget::Stored { .. } => match &self.entry_point { - TransactionEntryPoint::Custom(_) => Ok(()), - TransactionEntryPoint::Call - | TransactionEntryPoint::Transfer - | TransactionEntryPoint::AddBid - | TransactionEntryPoint::WithdrawBid - | TransactionEntryPoint::Delegate - | TransactionEntryPoint::Undelegate - | TransactionEntryPoint::Redelegate - | TransactionEntryPoint::ActivateBid - | TransactionEntryPoint::ChangeBidPublicKey - | TransactionEntryPoint::AddReservations - | TransactionEntryPoint::CancelReservations => { - debug!( - entry_point = %self.entry_point, - "transaction targeting stored entity/package must have custom entry point" - ); - Err(InvalidTransactionV1::EntryPointMustBeCustom { - entry_point: self.entry_point.clone(), - }) - } - }, - TransactionTarget::Session { module_bytes, .. } => match &self.entry_point { - TransactionEntryPoint::Call | TransactionEntryPoint::Custom(_) => { - if module_bytes.is_empty() { - debug!("transaction with session code must not have empty module bytes"); - return Err(InvalidTransactionV1::EmptyModuleBytes); - } - Ok(()) - } - TransactionEntryPoint::Transfer - | TransactionEntryPoint::AddBid - | TransactionEntryPoint::WithdrawBid - | TransactionEntryPoint::Delegate - | TransactionEntryPoint::Undelegate - | TransactionEntryPoint::Redelegate - | TransactionEntryPoint::ActivateBid - | TransactionEntryPoint::ChangeBidPublicKey - | TransactionEntryPoint::AddReservations - | TransactionEntryPoint::CancelReservations => { - debug!( - entry_point = %self.entry_point, - "transaction with session code must use custom or default 'call' entry point" - ); - Err(InvalidTransactionV1::EntryPointMustBeCustom { - entry_point: self.entry_point.clone(), - }) - } - }, - } - } - - fn serialized_field_lengths(&self) -> Vec { - vec![ - self.args.serialized_length(), - self.target.serialized_length(), - self.entry_point.serialized_length(), - self.transaction_lane.serialized_length(), - self.scheduling.serialized_length(), - ] - } - - /// Returns a random `TransactionV1Body`. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn random_of_lane(rng: &mut TestRng, lane: u8) -> Self { - match lane { - 0 => Self::random_transfer(rng), - 1 => Self::random_staking(rng), - 2 => Self::random_install_upgrade(rng), - _ => Self::random_standard(rng), - } - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn random_transfer(rng: &mut TestRng) -> Self { - use crate::transaction::TransferTarget; - - let amount = - rng.gen_range(TransactionConfig::default().native_transfer_minimum_motes..=u64::MAX); - let maybe_source = if rng.gen() { Some(rng.gen()) } else { None }; - let target = TransferTarget::random(rng); - let maybe_id = rng.gen::().then(|| rng.gen()); - let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id).unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Transfer, - TransactionLane::Mint as u8, - TransactionScheduling::random(rng), - ) - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn random_standard(rng: &mut TestRng) -> Self { - use crate::transaction::TransactionInvocationTarget; - - let target = TransactionTarget::Stored { - id: TransactionInvocationTarget::random(rng), - runtime: TransactionRuntime::VmCasperV1, - transferred_value: rng.gen(), - }; - TransactionV1Body::new( - RuntimeArgs::random(rng), - target, - TransactionEntryPoint::Custom(rng.random_string(1..11)), - TransactionLane::Large as u8, - TransactionScheduling::random(rng), - ) - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn random_install_upgrade(rng: &mut TestRng) -> Self { - let target = TransactionTarget::Session { - module_bytes: Bytes::from(rng.random_vec(0..100)), - runtime: TransactionRuntime::VmCasperV1, - transferred_value: rng.gen(), - seed: rng.gen(), - }; - TransactionV1Body::new( - RuntimeArgs::random(rng), - target, - TransactionEntryPoint::Custom(rng.random_string(1..11)), - TransactionLane::InstallUpgrade as u8, - TransactionScheduling::random(rng), - ) - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn random_staking(rng: &mut TestRng) -> Self { - let public_key = PublicKey::random(rng); - let delegation_rate = rng.gen(); - let amount = rng.gen::(); - let minimum_delegation_amount = rng.gen::() as u64; - let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; - let args = arg_handling::new_add_bid_args( - public_key, - delegation_rate, - amount, - minimum_delegation_amount, - maximum_delegation_amount, - ) - .unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::AddBid, - TransactionLane::Auction as u8, - TransactionScheduling::random(rng), - ) - } - - /// Returns a random `TransactionV1Body`. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn random(rng: &mut TestRng) -> Self { - use crate::{transaction::TransferTarget, transfer}; - - match rng.gen_range(0..8) { - 0 => { - let amount = rng.gen_range( - TransactionConfig::default().native_transfer_minimum_motes..=u64::MAX, - ); - let maybe_source = if rng.gen() { Some(rng.gen()) } else { None }; - let target = TransferTarget::random(rng); - let maybe_id = rng.gen::().then(|| rng.gen()); - let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id) - .unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Transfer, - TransactionLane::Mint as u8, - TransactionScheduling::random(rng), - ) - } - 1 => { - let public_key = PublicKey::random(rng); - let delegation_rate = rng.gen(); - let amount = rng.gen::(); - let minimum_delegation_amount = rng.gen::() as u64; - let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; - let args = arg_handling::new_add_bid_args( - public_key, - delegation_rate, - amount, - minimum_delegation_amount, - maximum_delegation_amount, - ) - .unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::AddBid, - TransactionLane::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 2 => { - let public_key = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_withdraw_bid_args(public_key, amount).unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::WithdrawBid, - TransactionLane::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 3 => { - let delegator = PublicKey::random(rng); - let validator = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_delegate_args(delegator, validator, amount).unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Delegate, - TransactionLane::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 4 => { - let delegator = PublicKey::random(rng); - let validator = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_undelegate_args(delegator, validator, amount).unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Undelegate, - TransactionLane::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 5 => { - let delegator = PublicKey::random(rng); - let validator = PublicKey::random(rng); - let amount = rng.gen::(); - let new_validator = PublicKey::random(rng); - let args = - arg_handling::new_redelegate_args(delegator, validator, amount, new_validator) - .unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Redelegate, - TransactionLane::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 6 => Self::random_standard(rng), - 7 => { - let mut buffer = vec![0u8; rng.gen_range(1..100)]; - rng.fill_bytes(buffer.as_mut()); - let transferred_value = rng.gen(); - let target = TransactionTarget::Session { - module_bytes: Bytes::from(buffer), - runtime: TransactionRuntime::VmCasperV1, - transferred_value, - seed: None, - }; - TransactionV1Body::new( - RuntimeArgs::random(rng), - target, - TransactionEntryPoint::Custom(rng.random_string(1..11)), - TransactionLane::Large as u8, - TransactionScheduling::random(rng), - ) - } - _ => unreachable!(), - } - } - - /// Returns a token value attached to the transaction. - pub fn transferred_value(&self) -> u64 { - match self.target() { - TransactionTarget::Native => 0, - TransactionTarget::Stored { - transferred_value, .. - } => *transferred_value, - TransactionTarget::Session { - transferred_value, .. - } => *transferred_value, - } - } - - pub(crate) fn seed(&self) -> Option<[u8; 32]> { - match self.target { - TransactionTarget::Native => None, - TransactionTarget::Stored { .. } => None, - TransactionTarget::Session { seed, .. } => seed, - } - } -} - -const ARGS_INDEX: u16 = 0; -const TARGET_INDEX: u16 = 1; -const ENTRY_POINT_INDEX: u16 = 2; -const TRANSACTION_LANE_INDEX: u16 = 3; -const SCHEDULING_INDEX: u16 = 4; - -impl FromBytes for TransactionV1Body { - fn from_bytes(bytes: &[u8]) -> Result<(TransactionV1Body, &[u8]), Error> { - let (binary_payload, remainder) = - crate::transaction::serialization::CalltableSerializationEnvelope::from_bytes( - 5, bytes, - )?; - let window = binary_payload.start_consuming()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(ARGS_INDEX)?; - let (args, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(TARGET_INDEX)?; - let (target, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(ENTRY_POINT_INDEX)?; - let (entry_point, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(TRANSACTION_LANE_INDEX)?; - let (transaction_lane, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(SCHEDULING_INDEX)?; - let (scheduling, window) = window.deserialize_and_maybe_next::()?; - if window.is_some() { - return Err(Error::Formatting); - } - let from_bytes = TransactionV1Body { - args, - target, - entry_point, - transaction_lane, - scheduling, - }; - Ok((from_bytes, remainder)) - } -} - -impl ToBytes for TransactionV1Body { - fn to_bytes(&self) -> Result, Error> { - CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? - .add_field(ARGS_INDEX, &self.args)? - .add_field(TARGET_INDEX, &self.target)? - .add_field(ENTRY_POINT_INDEX, &self.entry_point)? - .add_field(TRANSACTION_LANE_INDEX, &self.transaction_lane)? - .add_field(SCHEDULING_INDEX, &self.scheduling)? - .binary_payload_bytes() - } - fn serialized_length(&self) -> usize { - CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths()) - } -} - -impl Display for TransactionV1Body { - fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { - write!( - formatter, - "v1-body({} {} {})", - self.target, self.entry_point, self.scheduling - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{bytesrepr, runtime_args, TransactionInvocationTarget}; - - #[test] - fn bytesrepr_roundtrip() { - let rng = &mut TestRng::new(); - let body = TransactionV1Body::random(rng); - bytesrepr::test_serialization_roundtrip(&body); - } - - #[test] - fn not_acceptable_due_to_excessive_args_length() { - let rng = &mut TestRng::new(); - let mut config = TransactionConfig::default(); - let mut body = TransactionV1Body::random_standard(rng); - config.transaction_v1_config.wasm_lanes = - vec![vec![body.transaction_lane as u64, 1_048_576, 10, 0]]; - body.args = TransactionArgs::Named(runtime_args! {"a" => 1_u8}); - - let expected_error = InvalidTransactionV1::ExcessiveArgsLength { - max_length: 10, - got: 16, - }; - - assert_eq!(body.is_valid(&config), Err(expected_error)); - } - - #[test] - fn not_acceptable_due_to_custom_entry_point_in_native() { - let rng = &mut TestRng::new(); - let public_key = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_withdraw_bid_args(public_key, amount).unwrap(); - let entry_point = TransactionEntryPoint::Custom("custom".to_string()); - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - entry_point.clone(), - TransactionLane::Mint as u8, - TransactionScheduling::random(rng), - ); - - let expected_error = InvalidTransactionV1::EntryPointCannotBeCustom { entry_point }; - - let config = TransactionConfig::default(); - assert_eq!(body.is_valid(&config), Err(expected_error)); - } - - #[test] - fn not_acceptable_due_to_call_entry_point_in_native() { - let rng = &mut TestRng::new(); - let public_key = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_withdraw_bid_args(public_key, amount).unwrap(); - let entry_point = TransactionEntryPoint::Call; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - entry_point, - TransactionLane::Mint as u8, - TransactionScheduling::random(rng), - ); - - let expected_error = InvalidTransactionV1::EntryPointCannotBeCall; - - let config = TransactionConfig::default(); - assert_eq!(body.is_valid(&config,), Err(expected_error)); - } - - #[test] - fn not_acceptable_due_to_non_custom_entry_point_in_stored_or_session() { - let rng = &mut TestRng::new(); - let config = TransactionConfig::default(); - - let mut check = |entry_point: TransactionEntryPoint| { - let stored_target = TransactionTarget::new_stored( - TransactionInvocationTarget::ByHash([0; 32]), - TransactionRuntime::VmCasperV1, - 0, - ); - let session_target = TransactionTarget::new_session( - Bytes::from(vec![1]), - TransactionRuntime::VmCasperV1, - 1, - ); - - let stored_body = TransactionV1Body::new( - RuntimeArgs::new(), - stored_target, - entry_point.clone(), - TransactionLane::Large as u8, - TransactionScheduling::random(rng), - ); - let session_body = TransactionV1Body::new( - RuntimeArgs::new(), - session_target, - entry_point.clone(), - TransactionLane::Large as u8, - TransactionScheduling::random(rng), - ); - - let expected_error = InvalidTransactionV1::EntryPointMustBeCustom { entry_point }; - - assert_eq!(stored_body.is_valid(&config), Err(expected_error.clone())); - assert_eq!(session_body.is_valid(&config), Err(expected_error)); - }; - - check(TransactionEntryPoint::Transfer); - check(TransactionEntryPoint::AddBid); - check(TransactionEntryPoint::WithdrawBid); - check(TransactionEntryPoint::Delegate); - check(TransactionEntryPoint::Undelegate); - check(TransactionEntryPoint::Redelegate); - } -} diff --git a/types/src/transaction/transaction_v1/transaction_v1_builder.rs b/types/src/transaction/transaction_v1/transaction_v1_builder.rs index c346245a6d..e86244e3da 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_builder.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_builder.rs @@ -1,56 +1,105 @@ -mod error; +pub mod error; use core::marker::PhantomData; -#[cfg(any(feature = "testing", test))] -use rand::Rng; - use super::{ - super::{ - InitiatorAddr, TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntime, - TransactionScheduling, TransactionTarget, - }, - transaction_v1_body::{arg_handling, TransactionArgs}, - InitiatorAddrAndSecretKey, PricingMode, TransactionV1, TransactionV1Body, + super::{InitiatorAddr, TransactionRuntime, TransactionScheduling, TransactionTarget}, + arg_handling, + fields_container::FieldsContainerError, + InitiatorAddrAndSecretKey, PricingMode, TransactionArgs, TransactionV1, }; use crate::{ - bytesrepr::Bytes, - transaction::{RuntimeArgs, TransactionLane, TransferTarget}, - AddressableEntityHash, CLValue, CLValueError, EntityVersion, PackageHash, PublicKey, SecretKey, - TimeDiff, Timestamp, URef, U512, + bytesrepr::Bytes, transaction::FieldsContainer, AddressableEntityHash, CLValue, CLValueError, + EntityVersion, PackageHash, PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, + TransactionEntryPoint, TransactionInvocationTarget, TransferTarget, URef, U512, }; #[cfg(any(feature = "testing", test))] -use crate::{ - testing::TestRng, transaction::Approval, transaction::TransactionV1Hash, TransactionConfig, -}; +use crate::{testing::TestRng, transaction::Approval, TransactionConfig, TransactionV1Hash}; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use alloc::collections::BTreeMap; pub use error::TransactionV1BuilderError; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use rand::Rng; -/// A builder for constructing a [`TransactionV1`]. +/// A builder for constructing `TransactionV1` instances with various configuration options. +/// +/// The `TransactionV1Builder` provides a flexible API for specifying different transaction +/// parameters like the target, scheduling, entry point, and signing options. Once all the required +/// fields are set, the transaction can be built by calling [`build`](Self::build). /// -/// # Note +/// # Fields /// -/// Before calling [`build`](Self::build), you must ensure that: -/// * an initiator_addr is provided by either calling -/// [`with_initiator_addr`](Self::with_initiator_addr) or -/// [`with_secret_key`](Self::with_secret_key) -/// * the chain name is set by calling [`with_chain_name`](Self::with_chain_name) +/// - `args`: Arguments passed to the transaction's runtime, initialized to +/// [`RuntimeArgs::new`](RuntimeArgs::new). +/// - `target`: Specifies the target of the transaction, which can be native or other custom +/// targets. Defaults to [`TransactionTarget::Native`](TransactionTarget::Native). +/// - `scheduling`: Determines the scheduling mechanism of the transaction, e.g., standard or +/// immediate, and is initialized to +/// [`TransactionScheduling::Standard`](TransactionScheduling::Standard). +/// - `entry_point`: Defines the transaction's entry point, such as transfer or another defined +/// action. Defaults to [`TransactionEntryPoint::Transfer`](TransactionEntryPoint::Transfer). +/// - `chain_name`: The name of the blockchain where the transaction will be executed. Initially set +/// to `None` and must be provided before building the transaction. /// -/// If no secret key is provided, the resulting transaction will be unsigned, and hence invalid. -/// It can be signed later (multiple times if desired) to make it valid before sending to the -/// network for execution. +/// ## Time-Related Fields +/// - `timestamp`: The timestamp at which the transaction is created. It is either set to the +/// current time using [`Timestamp::now`](Timestamp::now) or [`Timestamp::zero`](Timestamp::zero) +/// without the `std-fs-io` feature. +/// - `ttl`: Time-to-live for the transaction, specified as a [`TimeDiff`], representing how long +/// the transaction is valid for execution. Defaults to [`Self::DEFAULT_TTL`]. +/// +/// ## Pricing and Initiator Fields +/// - `pricing_mode`: Specifies the pricing mode to use for transaction execution (e.g., fixed or +/// dynamic). Defaults to [`Self::DEFAULT_PRICING_MODE`]. +/// - `initiator_addr`: The address of the initiator who creates and signs the transaction. +/// Initially set to `None` and must be set before building. +/// +/// ## Signing Fields +/// - `secret_key`: The secret key used to sign the transaction. This field is conditional based on +/// the compilation environment: +/// - In normal mode, it holds a reference to the secret key (`Option<&'a SecretKey>`). +/// - In testing mode or with the `std` feature enabled, it holds an owned secret key +/// (`Option`). +/// +/// ## Invalid Approvals +/// - `invalid_approvals`: A collection of invalid approvals used for testing purposes. This field +/// is available only when the `std` or `testing` features are enabled, or in a test environment. +/// +/// ## Phantom Data +/// - `_phantom_data`: Ensures the correct lifetime `'a` is respected for the builder, helping with +/// proper borrowing and memory safety. pub struct TransactionV1Builder<'a> { + /// Arguments passed to the transaction's runtime. + args: TransactionArgs, + /// The target of the transaction (e.g., native). + target: TransactionTarget, + /// Defines how the transaction is scheduled (e.g., standard, immediate). + scheduling: TransactionScheduling, + /// Specifies the entry point of the transaction (e.g., transfer). + entry_point: TransactionEntryPoint, + /// The name of the blockchain where the transaction will be executed. chain_name: Option, + /// The timestamp of the transaction. timestamp: Timestamp, + /// The time-to-live for the transaction, representing how long it's valid for execution. ttl: TimeDiff, - body: TransactionV1Body, + /// The pricing mode used for the transaction's execution cost. pricing_mode: PricingMode, + /// The address of the transaction initiator. initiator_addr: Option, + /// The secret key used for signing the transaction (in normal mode). #[cfg(not(any(feature = "testing", test)))] secret_key: Option<&'a SecretKey>, - #[cfg(any(feature = "testing", test))] + /// The secret key used for signing the transaction (in testing or with `std` feature). + #[cfg(any(all(feature = "std", feature = "testing"), test))] secret_key: Option, - #[cfg(any(feature = "testing", test))] + /// A list of invalid approvals for testing purposes. + #[cfg(any(all(feature = "std", feature = "testing"), test))] invalid_approvals: Vec, + /// Additional fields + #[cfg(any(all(feature = "std", feature = "testing"), test))] + additional_fields: BTreeMap, + /// Phantom data to ensure the correct lifetime for references. _phantom_data: PhantomData<&'a ()>, } @@ -60,22 +109,70 @@ impl<'a> TransactionV1Builder<'a> { /// The default pricing mode for v1 transactions, ie FIXED cost. pub const DEFAULT_PRICING_MODE: PricingMode = PricingMode::Fixed { gas_price_tolerance: 5, + additional_computation_factor: 0, }; /// The default scheduling for transactions, i.e. `Standard`. pub const DEFAULT_SCHEDULING: TransactionScheduling = TransactionScheduling::Standard; - pub(super) fn new(body: TransactionV1Body) -> Self { + /// Creates a new `TransactionV1Builder` instance with default settings. + /// + /// # Important + /// + /// Before calling [`build`](Self::build), you must ensure that either: + /// - A chain name is provided by calling [`with_chain_name`](Self::with_chain_name), + /// - An initiator address is set by calling [`with_initiator_addr`](Self::with_initiator_addr), + /// - or a secret key is set by calling [`with_secret_key`](Self::with_secret_key). + /// + /// # Default Values + /// This function sets the following default values upon creation: + /// + /// - `chain_name`: Initialized to `None`. + /// - `timestamp`: Set to the current time using [`Timestamp::now`](Timestamp::now), or + /// [`Timestamp::zero`](Timestamp::zero) if the `std-fs-io` feature is disabled. + /// - `ttl`: Defaults to [`Self::DEFAULT_TTL`]. + /// - `pricing_mode`: Defaults to [`Self::DEFAULT_PRICING_MODE`]. + /// - `initiator_addr`: Initialized to `None`. + /// - `secret_key`: Initialized to `None`. + /// + /// Additionally, the following internal fields are configured: + /// + /// - `args`: Initialized to an empty [`RuntimeArgs::new`](RuntimeArgs::new). + /// - `entry_point`: Set to + /// [`TransactionEntryPoint::Transfer`](TransactionEntryPoint::Transfer). + /// - `target`: Defaults to [`TransactionTarget::Native`](TransactionTarget::Native). + /// - `scheduling`: Defaults to + /// [`TransactionScheduling::Standard`](TransactionScheduling::Standard). + /// + /// # Testing and Additional Configuration + /// + /// - If the `std` or `testing` feature is enabled, or in test configurations, the + /// `invalid_approvals` field is initialized as an empty vector. + /// + /// # Returns + /// + /// A new `TransactionV1Builder` instance. + pub(crate) fn new() -> Self { + #[cfg(any(feature = "std-fs-io", test))] + let timestamp = Timestamp::now(); + #[cfg(not(any(feature = "std-fs-io", test)))] + let timestamp = Timestamp::zero(); + TransactionV1Builder { + args: TransactionArgs::Named(RuntimeArgs::new()), + entry_point: TransactionEntryPoint::Transfer, + target: TransactionTarget::Native, + scheduling: TransactionScheduling::Standard, chain_name: None, - timestamp: Timestamp::now(), + timestamp, ttl: Self::DEFAULT_TTL, - body, pricing_mode: Self::DEFAULT_PRICING_MODE, initiator_addr: None, secret_key: None, _phantom_data: PhantomData, - #[cfg(any(feature = "testing", test))] + #[cfg(any(all(feature = "std", feature = "testing"), test))] invalid_approvals: vec![], + #[cfg(any(all(feature = "std", feature = "testing"), test))] + additional_fields: BTreeMap::new(), } } @@ -87,14 +184,12 @@ impl<'a> TransactionV1Builder<'a> { maybe_id: Option, ) -> Result { let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Transfer, - TransactionLane::Mint as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::Transfer; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native add_bid transaction. @@ -112,14 +207,12 @@ impl<'a> TransactionV1Builder<'a> { minimum_delegation_amount, maximum_delegation_amount, )?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::AddBid, - TransactionLane::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::AddBid; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native withdraw_bid @@ -129,14 +222,12 @@ impl<'a> TransactionV1Builder<'a> { amount: A, ) -> Result { let args = arg_handling::new_withdraw_bid_args(public_key, amount)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::WithdrawBid, - TransactionLane::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::WithdrawBid; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native delegate transaction. @@ -146,14 +237,12 @@ impl<'a> TransactionV1Builder<'a> { amount: A, ) -> Result { let args = arg_handling::new_delegate_args(delegator, validator, amount)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Delegate, - TransactionLane::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::Delegate; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native undelegate transaction. @@ -163,14 +252,12 @@ impl<'a> TransactionV1Builder<'a> { amount: A, ) -> Result { let args = arg_handling::new_undelegate_args(delegator, validator, amount)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Undelegate, - TransactionLane::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::Undelegate; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native redelegate transaction. @@ -181,14 +268,12 @@ impl<'a> TransactionV1Builder<'a> { new_validator: PublicKey, ) -> Result { let args = arg_handling::new_redelegate_args(delegator, validator, amount, new_validator)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Redelegate, - TransactionLane::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(args); + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::Redelegate; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } fn new_targeting_stored>( @@ -202,14 +287,12 @@ impl<'a> TransactionV1Builder<'a> { runtime, transferred_value, }; - let body = TransactionV1Body::new( - RuntimeArgs::new(), - target, - TransactionEntryPoint::Custom(entry_point.into()), - TransactionLane::Large as u8, - Self::DEFAULT_SCHEDULING, - ); - TransactionV1Builder::new(body) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(RuntimeArgs::new()); + builder.target = target; + builder.entry_point = TransactionEntryPoint::Custom(entry_point.into()); + builder.scheduling = Self::DEFAULT_SCHEDULING; + builder } /// Returns a new `TransactionV1Builder` suitable for building a transaction targeting a stored @@ -265,26 +348,25 @@ impl<'a> TransactionV1Builder<'a> { /// Returns a new `TransactionV1Builder` suitable for building a transaction for running session /// logic, i.e. compiled Wasm. pub fn new_session( - lane: TransactionLane, + is_install_upgrade: bool, module_bytes: Bytes, runtime: TransactionRuntime, transferred_value: u64, seed: Option<[u8; 32]>, ) -> Self { let target = TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, transferred_value, seed, }; - let body = TransactionV1Body::new( - RuntimeArgs::new(), - target, - TransactionEntryPoint::Call, - lane as u8, - Self::DEFAULT_SCHEDULING, - ); - TransactionV1Builder::new(body) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(RuntimeArgs::new()); + builder.target = target; + builder.entry_point = TransactionEntryPoint::Call; + builder.scheduling = Self::DEFAULT_SCHEDULING; + builder } /// Returns a new `TransactionV1Builder` suitable for building a transaction for calling a smart @@ -295,24 +377,18 @@ impl<'a> TransactionV1Builder<'a> { input_data: Option, transferred_value: u64, ) -> Self { - let body = { - let args = TransactionArgs::Bytesrepr(input_data.unwrap_or_default()); - let target = TransactionTarget::Stored { - id: TransactionInvocationTarget::ByHash(entity_address.value()), - runtime: TransactionRuntime::VmCasperV2, - transferred_value, - }; - let transaction_lane = TransactionLane::Medium as u8; - let scheduling = Self::DEFAULT_SCHEDULING; - TransactionV1Body { - args, - target, - entry_point: TransactionEntryPoint::Custom(entry_point), - transaction_lane, - scheduling, - } + let args = TransactionArgs::Bytesrepr(input_data.unwrap_or_default()); + let target = TransactionTarget::Stored { + id: TransactionInvocationTarget::ByHash(entity_address.value()), + runtime: TransactionRuntime::VmCasperV2, + transferred_value, }; - TransactionV1Builder::new(body) + let mut builder = TransactionV1Builder::new(); + builder.args = TransactionArgs::Named(RuntimeArgs::new()); + builder.target = target; + builder.entry_point = TransactionEntryPoint::Call; + builder.scheduling = Self::DEFAULT_SCHEDULING; + builder } /// Returns a new `TransactionV1Builder` which will build a random, valid but possibly expired @@ -321,23 +397,29 @@ impl<'a> TransactionV1Builder<'a> { /// The transaction can be made invalid in the following ways: /// * unsigned by calling `with_no_secret_key` /// * given an invalid approval by calling `with_invalid_approval` - #[cfg(any(feature = "testing", test))] + #[cfg(any(all(feature = "std", feature = "testing"), test))] pub fn new_random(rng: &mut TestRng) -> Self { let secret_key = SecretKey::random(rng); let ttl_millis = rng.gen_range(60_000..TransactionConfig::default().max_ttl.millis()); - let body = TransactionV1Body::random(rng); + let fields = FieldsContainer::random(rng); TransactionV1Builder { chain_name: Some(rng.random_string(5..10)), timestamp: Timestamp::random(rng), ttl: TimeDiff::from_millis(ttl_millis), - body, + args: TransactionArgs::Named(RuntimeArgs::random(rng)), + target: fields.target, + entry_point: fields.entry_point, + scheduling: fields.scheduling, pricing_mode: PricingMode::Fixed { gas_price_tolerance: 5, + additional_computation_factor: 0, }, initiator_addr: Some(InitiatorAddr::PublicKey(PublicKey::from(&secret_key))), secret_key: Some(secret_key), _phantom_data: PhantomData, invalid_approvals: vec![], + #[cfg(any(all(feature = "std", feature = "testing"), test))] + additional_fields: BTreeMap::new(), } } @@ -347,8 +429,8 @@ impl<'a> TransactionV1Builder<'a> { /// The transaction can be made invalid in the following ways: /// * unsigned by calling `with_no_secret_key` /// * given an invalid approval by calling `with_invalid_approval` - #[cfg(any(feature = "testing", test))] - pub fn new_random_with_lane_and_timestamp_and_ttl( + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn new_random_with_category_and_timestamp_and_ttl( rng: &mut TestRng, lane: u8, timestamp: Option, @@ -359,19 +441,30 @@ impl<'a> TransactionV1Builder<'a> { rng.gen_range(60_000..TransactionConfig::default().max_ttl.millis()), |ttl| ttl.millis(), ); - let body = TransactionV1Body::random_of_lane(rng, lane); + let FieldsContainer { + args, + target, + entry_point, + scheduling, + } = FieldsContainer::random_of_lane(rng, lane); TransactionV1Builder { chain_name: Some(rng.random_string(5..10)), timestamp: timestamp.unwrap_or(Timestamp::now()), ttl: TimeDiff::from_millis(ttl_millis), - body, + args, + target, + entry_point, + scheduling, pricing_mode: PricingMode::Fixed { gas_price_tolerance: 5, + additional_computation_factor: 0, }, initiator_addr: Some(InitiatorAddr::PublicKey(PublicKey::from(&secret_key))), secret_key: Some(secret_key), _phantom_data: PhantomData, invalid_approvals: vec![], + #[cfg(any(all(feature = "std", feature = "testing"), test))] + additional_fields: BTreeMap::new(), } } @@ -425,7 +518,7 @@ impl<'a> TransactionV1Builder<'a> { { self.secret_key = Some(secret_key); } - #[cfg(any(feature = "testing", test))] + #[cfg(any(all(feature = "std", feature = "testing"), test))] { self.secret_key = Some( SecretKey::from_der(secret_key.to_der().expect("should der-encode")) @@ -437,15 +530,15 @@ impl<'a> TransactionV1Builder<'a> { /// Appends the given runtime arg into the body's `args`. pub fn with_runtime_arg>(mut self, key: K, cl_value: CLValue) -> Self { - match &mut self.body.args { + match &mut self.args { TransactionArgs::Named(args) => { args.insert_cl_value(key, cl_value); - self } TransactionArgs::Bytesrepr(raw_bytes) => { panic!("Cannot append named args to unnamed args: {:?}", raw_bytes) } } + self } /// Sets the runtime args in the transaction. @@ -453,19 +546,19 @@ impl<'a> TransactionV1Builder<'a> { /// NOTE: this overwrites any existing runtime args. To append to existing args, use /// [`TransactionV1Builder::with_runtime_arg`]. pub fn with_runtime_args(mut self, args: RuntimeArgs) -> Self { - self.body.args = TransactionArgs::Named(args); + self.args = TransactionArgs::Named(args); self } /// Sets the runtime args in the transaction. pub fn with_chunked_args(mut self, args: Bytes) -> Self { - self.body.args = TransactionArgs::Bytesrepr(args); + self.args = TransactionArgs::Bytesrepr(args); self } /// Sets the transaction args in the transaction. pub fn with_transaction_args(mut self, args: TransactionArgs) -> Self { - self.body.args = args; + self.args = args; self } @@ -476,7 +569,7 @@ impl<'a> TransactionV1Builder<'a> { /// NOTE: This has no effect for native transactions, i.e. where the `body.target` is /// `TransactionTarget::Native`. pub fn with_runtime(mut self, runtime: TransactionRuntime) -> Self { - match &mut self.body.target { + match &mut self.target { TransactionTarget::Native => {} TransactionTarget::Stored { runtime: existing_runtime, @@ -494,30 +587,24 @@ impl<'a> TransactionV1Builder<'a> { self } - /// Sets the entry point for the transaction. - pub fn with_entry_point>(mut self, entry_point: E) -> Self { - self.body.entry_point = TransactionEntryPoint::Custom(entry_point.into()); - self - } - /// Sets the scheduling for the transaction. /// /// If not provided, the scheduling will be set to [`Self::DEFAULT_SCHEDULING`]. pub fn with_scheduling(mut self, scheduling: TransactionScheduling) -> Self { - self.body.scheduling = scheduling; + self.scheduling = scheduling; self } /// Sets the secret key to `None`, meaning the transaction can still be built but will be /// unsigned and will be invalid until subsequently signed. - #[cfg(any(feature = "testing", test))] + #[cfg(any(all(feature = "std", feature = "testing"), test))] pub fn with_no_secret_key(mut self) -> Self { self.secret_key = None; self } /// Sets an invalid approval in the transaction. - #[cfg(any(feature = "testing", test))] + #[cfg(any(all(feature = "std", feature = "testing"), test))] pub fn with_invalid_approval(mut self, rng: &mut TestRng) -> Self { let secret_key = SecretKey::random(rng); let hash = TransactionV1Hash::random(rng).into(); @@ -526,6 +613,13 @@ impl<'a> TransactionV1Builder<'a> { self } + /// Manually sets additional fields + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn with_additional_fields(mut self, additional_fields: BTreeMap) -> Self { + self.additional_fields = additional_fields; + self + } + /// Returns the new transaction, or an error if non-defaulted fields were not set. /// /// For more info, see [the `TransactionBuilder` documentation](TransactionV1Builder). @@ -551,19 +645,28 @@ impl<'a> TransactionV1Builder<'a> { .chain_name .ok_or(TransactionV1BuilderError::MissingChainName)?; + let container = + FieldsContainer::new(self.args, self.target, self.entry_point, self.scheduling) + .to_map() + .map_err(|err| match err { + FieldsContainerError::CouldNotSerializeField { field_index } => { + TransactionV1BuilderError::CouldNotSerializeField { field_index } + } + })?; + let transaction = TransactionV1::build( chain_name, self.timestamp, self.ttl, - self.body, self.pricing_mode, + container, initiator_addr_and_secret_key, ); Ok(transaction) } - #[cfg(any(feature = "testing", test))] + #[cfg(any(all(feature = "std", feature = "testing"), test))] fn do_build(self) -> Result { let initiator_addr_and_secret_key = match (self.initiator_addr, &self.secret_key) { (Some(initiator_addr), Some(secret_key)) => InitiatorAddrAndSecretKey::Both { @@ -580,13 +683,23 @@ impl<'a> TransactionV1Builder<'a> { let chain_name = self .chain_name .ok_or(TransactionV1BuilderError::MissingChainName)?; + let mut container = + FieldsContainer::new(self.args, self.target, self.entry_point, self.scheduling) + .to_map() + .map_err(|err| match err { + FieldsContainerError::CouldNotSerializeField { field_index } => { + TransactionV1BuilderError::CouldNotSerializeField { field_index } + } + })?; + let mut additional_fields = self.additional_fields.clone(); + container.append(&mut additional_fields); let mut transaction = TransactionV1::build( chain_name, self.timestamp, self.ttl, - self.body, self.pricing_mode, + container, initiator_addr_and_secret_key, ); diff --git a/types/src/transaction/transaction_v1/transaction_v1_builder/error.rs b/types/src/transaction/transaction_v1/transaction_v1_builder/error.rs index f92121003f..8ce1196f62 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_builder/error.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_builder/error.rs @@ -19,6 +19,12 @@ pub enum TransactionV1BuilderError { /// Call [`TransactionV1Builder::with_chain_name`] before calling /// [`TransactionV1Builder::build`]. MissingChainName, + /// Failed to build transaction due to an error when calling `to_bytes` on one of the payload + /// `field`. + CouldNotSerializeField { + /// The field index that failed to serialize. + field_index: u16, + }, } impl Display for TransactionV1BuilderError { @@ -36,6 +42,9 @@ impl Display for TransactionV1BuilderError { "transaction requires chain name - use `with_chain_name`" ) } + TransactionV1BuilderError::CouldNotSerializeField { field_index } => { + write!(formatter, "Cannot serialize field at index {}", field_index) + } } } } diff --git a/types/src/transaction/transaction_v1/transaction_v1_header.rs b/types/src/transaction/transaction_v1/transaction_v1_header.rs deleted file mode 100644 index 75dd5df8eb..0000000000 --- a/types/src/transaction/transaction_v1/transaction_v1_header.rs +++ /dev/null @@ -1,274 +0,0 @@ -use alloc::{string::String, vec::Vec}; -use core::fmt::{self, Display, Formatter}; - -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -#[cfg(any(feature = "std", test))] -use serde::{Deserialize, Serialize}; -#[cfg(any(feature = "std", test))] -use tracing::debug; - -#[cfg(doc)] -use super::TransactionV1; -#[cfg(any(feature = "std", test))] -use super::TransactionV1Hash; -use super::{InitiatorAddr, PricingMode}; -use crate::{ - bytesrepr::{ - Error::{self, Formatting}, - FromBytes, ToBytes, - }, - transaction::serialization::{ - CalltableSerializationEnvelope, CalltableSerializationEnvelopeBuilder, - }, - Digest, TimeDiff, Timestamp, -}; -#[cfg(any(feature = "std", test))] -use crate::{InvalidTransactionV1, TransactionConfig}; - -const CHAIN_NAME_INDEX: u16 = 0; -const TIMESTAMP_INDEX: u16 = 1; -const TTL_INDEX: u16 = 2; -const BODY_HASH_INDEX: u16 = 3; -const PRICING_MODE_INDEX: u16 = 4; -const INITIATOR_ADDR_INDEX: u16 = 5; - -/// The header portion of a [`TransactionV1`]. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] -#[cfg_attr( - any(feature = "std", test), - derive(Serialize, Deserialize), - serde(deny_unknown_fields) -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "The header portion of a TransactionV1.") -)] -pub struct TransactionV1Header { - chain_name: String, - timestamp: Timestamp, - ttl: TimeDiff, - body_hash: Digest, - pricing_mode: PricingMode, - initiator_addr: InitiatorAddr, -} - -impl TransactionV1Header { - fn serialized_field_lengths(&self) -> Vec { - vec![ - self.chain_name.serialized_length(), - self.timestamp.serialized_length(), - self.ttl.serialized_length(), - self.body_hash.serialized_length(), - self.pricing_mode.serialized_length(), - self.initiator_addr.serialized_length(), - ] - } - - #[cfg(any(feature = "std", feature = "json-schema", test))] - pub(super) fn new( - chain_name: String, - timestamp: Timestamp, - ttl: TimeDiff, - body_hash: Digest, - pricing_mode: PricingMode, - initiator_addr: InitiatorAddr, - ) -> Self { - TransactionV1Header { - chain_name, - timestamp, - ttl, - body_hash, - pricing_mode, - initiator_addr, - } - } - - /// Computes the hash identifying this transaction. - #[cfg(any(feature = "std", test))] - pub fn compute_hash(&self) -> TransactionV1Hash { - TransactionV1Hash::new(Digest::hash( - self.to_bytes() - .unwrap_or_else(|error| panic!("should serialize header: {}", error)), - )) - } - - /// Returns the name of the chain the transaction should be executed on. - pub fn chain_name(&self) -> &str { - &self.chain_name - } - - /// Returns the creation timestamp of the transaction. - pub fn timestamp(&self) -> Timestamp { - self.timestamp - } - - /// Returns the duration after the creation timestamp for which the transaction will stay valid. - /// - /// After this duration has ended, the transaction will be considered expired. - pub fn ttl(&self) -> TimeDiff { - self.ttl - } - - /// Returns `true` if the transaction has expired. - pub fn expired(&self, current_instant: Timestamp) -> bool { - self.expires() < current_instant - } - - /// Returns the hash of the body of the transaction. - pub fn body_hash(&self) -> &Digest { - &self.body_hash - } - - /// Returns the pricing mode for the transaction. - #[inline] - pub fn pricing_mode(&self) -> &PricingMode { - &self.pricing_mode - } - - /// Returns the address of the initiator of the transaction. - pub fn initiator_addr(&self) -> &InitiatorAddr { - &self.initiator_addr - } - - /// Returns `Ok` if and only if the TTL is within limits, and the timestamp is not later than - /// `at + timestamp_leeway`. Does NOT check for expiry. - #[cfg(any(feature = "std", test))] - pub fn is_valid( - &self, - config: &TransactionConfig, - timestamp_leeway: TimeDiff, - at: Timestamp, - transaction_hash: &TransactionV1Hash, - ) -> Result<(), InvalidTransactionV1> { - if self.ttl() > config.max_ttl { - debug!( - %transaction_hash, - transaction_header = %self, - max_ttl = %config.max_ttl, - "transaction ttl excessive" - ); - return Err(InvalidTransactionV1::ExcessiveTimeToLive { - max_ttl: config.max_ttl, - got: self.ttl(), - }); - } - - if self.timestamp() > at + timestamp_leeway { - debug!( - %transaction_hash, transaction_header = %self, %at, - "transaction timestamp in the future" - ); - return Err(InvalidTransactionV1::TimestampInFuture { - validation_timestamp: at, - timestamp_leeway, - got: self.timestamp(), - }); - } - - Ok(()) - } - - /// Returns the timestamp of when the transaction expires, i.e. `self.timestamp + self.ttl`. - pub fn expires(&self) -> Timestamp { - self.timestamp.saturating_add(self.ttl) - } - - /// Returns the gas price tolerance for the given transaction. - pub fn gas_price_tolerance(&self) -> u8 { - match self.pricing_mode { - PricingMode::PaymentLimited { - gas_price_tolerance, - .. - } => gas_price_tolerance, - PricingMode::Fixed { - gas_price_tolerance, - .. - } => gas_price_tolerance, - PricingMode::Reserved { .. } => { - // TODO: Change this when reserve gets implemented. - 0u8 - } - } - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub(super) fn invalidate(&mut self) { - self.chain_name.clear(); - } -} - -impl ToBytes for TransactionV1Header { - fn to_bytes(&self) -> Result, Error> { - CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? - .add_field(CHAIN_NAME_INDEX, &self.chain_name)? - .add_field(TIMESTAMP_INDEX, &self.timestamp)? - .add_field(TTL_INDEX, &self.ttl)? - .add_field(BODY_HASH_INDEX, &self.body_hash)? - .add_field(PRICING_MODE_INDEX, &self.pricing_mode)? - .add_field(INITIATOR_ADDR_INDEX, &self.initiator_addr)? - .binary_payload_bytes() - } - fn serialized_length(&self) -> usize { - CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths()) - } -} - -impl FromBytes for TransactionV1Header { - fn from_bytes(bytes: &[u8]) -> Result<(TransactionV1Header, &[u8]), Error> { - let (binary_payload, remainder) = - crate::transaction::serialization::CalltableSerializationEnvelope::from_bytes( - 6u32, bytes, - )?; - let window = binary_payload.start_consuming()?; - let window = window.ok_or(Formatting)?; - window.verify_index(CHAIN_NAME_INDEX)?; - let (chain_name, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(TIMESTAMP_INDEX)?; - let (timestamp, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(TTL_INDEX)?; - let (ttl, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(BODY_HASH_INDEX)?; - let (body_hash, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(PRICING_MODE_INDEX)?; - let (pricing_mode, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(INITIATOR_ADDR_INDEX)?; - let (initiator_addr, window) = window.deserialize_and_maybe_next::()?; - if window.is_some() { - return Err(Formatting); - } - let from_bytes = TransactionV1Header { - chain_name, - timestamp, - ttl, - body_hash, - pricing_mode, - initiator_addr, - }; - Ok((from_bytes, remainder)) - } -} - -impl Display for TransactionV1Header { - fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { - #[cfg(any(feature = "std", test))] - let hash = self.compute_hash(); - #[cfg(not(any(feature = "std", test)))] - let hash = "unknown"; - write!( - formatter, - "transaction-v1-header[{}, chain_name: {}, timestamp: {}, ttl: {}, pricing mode: {}, \ - initiator: {}]", - hash, self.chain_name, self.timestamp, self.ttl, self.pricing_mode, self.initiator_addr - ) - } -} diff --git a/types/src/transaction/transaction_v1/transaction_v1_lane.rs b/types/src/transaction/transaction_v1/transaction_v1_lane.rs deleted file mode 100644 index e8cf013347..0000000000 --- a/types/src/transaction/transaction_v1/transaction_v1_lane.rs +++ /dev/null @@ -1,76 +0,0 @@ -use core::{ - convert::TryFrom, - fmt::{self, Formatter}, -}; - -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// The category of a Transaction. -#[derive( - Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, Default, -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "Session kind of a V1 Transaction.") -)] -#[serde(deny_unknown_fields)] -#[repr(u8)] -pub enum TransactionLane { - /// Native mint interaction (the default). - #[default] - Mint = 0, - /// Native auction interaction. - Auction = 1, - /// Install or Upgrade. - InstallUpgrade = 2, - /// A large Wasm based transaction. - Large = 3, - /// A medium Wasm based transaction. - Medium = 4, - /// A small Wasm based transaction. - Small = 5, -} - -impl fmt::Display for TransactionLane { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - TransactionLane::Mint => write!(f, "Mint"), - TransactionLane::Auction => write!(f, "Auction"), - TransactionLane::InstallUpgrade => write!(f, "InstallUpgrade"), - TransactionLane::Large => write!(f, "Large"), - TransactionLane::Medium => write!(f, "Medium"), - TransactionLane::Small => write!(f, "Small"), - } - } -} - -#[derive(Debug)] -pub struct TransactionCategoryConversionError(u8); - -impl fmt::Display for TransactionCategoryConversionError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Failed to convert {} into a TransactionCategory", self.0) - } -} - -impl TryFrom for TransactionLane { - type Error = TransactionCategoryConversionError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::Mint), - 1 => Ok(Self::Auction), - 2 => Ok(Self::InstallUpgrade), - 3 => Ok(Self::Large), - 4 => Ok(Self::Medium), - 5 => Ok(Self::Small), - _ => Err(TransactionCategoryConversionError(value)), - } - } -} diff --git a/types/src/transaction/transaction_v1/transaction_v1_payload.rs b/types/src/transaction/transaction_v1/transaction_v1_payload.rs new file mode 100644 index 0000000000..d54f87e5c0 --- /dev/null +++ b/types/src/transaction/transaction_v1/transaction_v1_payload.rs @@ -0,0 +1,469 @@ +use core::fmt::{self, Debug, Display, Formatter}; + +use super::{errors_v1::FieldDeserializationError, PricingMode}; +use crate::{ + bytesrepr::{ + Bytes, + Error::{self, Formatting}, + FromBytes, ToBytes, + }, + transaction::serialization::{ + CalltableSerializationEnvelope, CalltableSerializationEnvelopeBuilder, + }, + DisplayIter, InitiatorAddr, TimeDiff, Timestamp, +}; +use alloc::{collections::BTreeMap, string::String, vec::Vec}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; +#[cfg(any(feature = "std", test))] +use serde::{Deserialize, Serialize}; + +const INITIATOR_ADDR_FIELD_INDEX: u16 = 0; +const TIMESTAMP_FIELD_INDEX: u16 = 1; +const TTL_FIELD_INDEX: u16 = 2; +const CHAIN_NAME_FIELD_INDEX: u16 = 3; +const PRICING_MODE_FIELD_INDEX: u16 = 4; +const FIELDS_FIELD_INDEX: u16 = 5; + +const ARGS_MAP_KEY: u16 = 0; +const TARGET_MAP_KEY: u16 = 1; +const ENTRY_POINT_MAP_KEY: u16 = 2; +const SCHEDULING_MAP_KEY: u16 = 3; +const EXPECTED_FIELD_KEYS: [u16; 4] = [ + ARGS_MAP_KEY, + TARGET_MAP_KEY, + ENTRY_POINT_MAP_KEY, + SCHEDULING_MAP_KEY, +]; + +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +#[cfg_attr( + any(feature = "std", test), + derive(Serialize, Deserialize), + serde(deny_unknown_fields) +)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr( + feature = "json-schema", + derive(JsonSchema), + schemars( + description = "A unit of work sent by a client to the network, which when executed can \ + cause global state to be altered." + ) +)] +pub struct TransactionV1Payload { + initiator_addr: InitiatorAddr, + timestamp: Timestamp, + ttl: TimeDiff, + chain_name: String, + pricing_mode: PricingMode, + fields: BTreeMap, +} + +impl TransactionV1Payload { + pub fn new( + chain_name: String, + timestamp: Timestamp, + ttl: TimeDiff, + pricing_mode: PricingMode, + initiator_addr: InitiatorAddr, + fields: BTreeMap, + ) -> TransactionV1Payload { + TransactionV1Payload { + chain_name, + timestamp, + ttl, + pricing_mode, + initiator_addr, + fields, + } + } + + fn serialized_field_lengths(&self) -> Vec { + vec![ + self.initiator_addr.serialized_length(), + self.timestamp.serialized_length(), + self.ttl.serialized_length(), + self.chain_name.serialized_length(), + self.pricing_mode.serialized_length(), + self.fields.serialized_length(), + ] + } + + pub fn chain_name(&self) -> &str { + &self.chain_name + } + + pub fn timestamp(&self) -> Timestamp { + self.timestamp + } + + pub fn ttl(&self) -> TimeDiff { + self.ttl + } + + pub fn pricing_mode(&self) -> &PricingMode { + &self.pricing_mode + } + + pub fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + pub fn fields(&self) -> &BTreeMap { + &self.fields + } + + /// Returns the timestamp of when the transaction expires, i.e. `self.timestamp + self.ttl`. + pub fn expires(&self) -> Timestamp { + self.timestamp.saturating_add(self.ttl) + } + + /// Returns `true` if the transaction has expired. + pub fn expired(&self, current_instant: Timestamp) -> bool { + self.expires() < current_instant + } + + pub fn deserialize_field( + &self, + index: u16, + ) -> Result { + let field = self + .fields + .get(&index) + .ok_or(FieldDeserializationError::IndexNotExists { index })?; + let (value, remainder) = T::from_bytes(field) + .map_err(|error| FieldDeserializationError::FromBytesError { index, error })?; + if !remainder.is_empty() { + return Err(FieldDeserializationError::LingeringBytesInField { index }); + } + Ok(value) + } + + pub fn number_of_fields(&self) -> usize { + self.fields.len() + } + + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn invalidate(&mut self) { + self.chain_name.clear(); + } +} + +impl ToBytes for TransactionV1Payload { + fn to_bytes(&self) -> Result, crate::bytesrepr::Error> { + let expected_payload_sizes = self.serialized_field_lengths(); + CalltableSerializationEnvelopeBuilder::new(expected_payload_sizes)? + .add_field(INITIATOR_ADDR_FIELD_INDEX, &self.initiator_addr)? + .add_field(TIMESTAMP_FIELD_INDEX, &self.timestamp)? + .add_field(TTL_FIELD_INDEX, &self.ttl)? + .add_field(CHAIN_NAME_FIELD_INDEX, &self.chain_name)? + .add_field(PRICING_MODE_FIELD_INDEX, &self.pricing_mode)? + .add_field(FIELDS_FIELD_INDEX, &self.fields)? + .binary_payload_bytes() + } + + fn serialized_length(&self) -> usize { + CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths()) + } +} + +impl FromBytes for TransactionV1Payload { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(6, bytes)?; + let window = binary_payload.start_consuming()?.ok_or(Formatting)?; + + window.verify_index(INITIATOR_ADDR_FIELD_INDEX)?; + let (initiator_addr, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(TIMESTAMP_FIELD_INDEX)?; + let (timestamp, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(TTL_FIELD_INDEX)?; + let (ttl, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(CHAIN_NAME_FIELD_INDEX)?; + let (chain_name, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(PRICING_MODE_FIELD_INDEX)?; + let (pricing_mode, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(FIELDS_FIELD_INDEX)?; + let (fields_as_vec, window) = window.deserialize_and_maybe_next::>()?; + let fields = build_map(fields_as_vec)?; + if window.is_some() { + return Err(Formatting); + } + if fields.len() != EXPECTED_FIELD_KEYS.len() + || EXPECTED_FIELD_KEYS + .iter() + .any(|expected_key| !fields.contains_key(expected_key)) + { + return Err(Formatting); + } + let from_bytes = TransactionV1Payload { + chain_name, + timestamp, + ttl, + pricing_mode, + initiator_addr, + fields, + }; + + Ok((from_bytes, remainder)) + } +} + +// We need to make sure that the bytes of the `fields` field are serialized in the correct order. +// A BTreeMap is serialized the same as Vec<(K, V)> and it actually, on deserialization, doesn't +// check if the keys are in ascending order. We need to make sure that the incoming transaction +// payload is serialized in a strict way, otherwise we would have trouble with verifying the +// signature(s). +fn build_map(fields_as_vec: Vec<(u16, Bytes)>) -> Result, Error> { + let mut ret = BTreeMap::new(); + let mut max_idx: i32 = -1; + for (key, value) in fields_as_vec { + let key_signed = key as i32; + if key_signed <= max_idx { + return Err(Formatting); + } + max_idx = key_signed; + ret.insert(key, value); + } + + Ok(ret) +} + +impl Display for TransactionV1Payload { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + write!( + formatter, + "transaction-v1-payload[{}, {}, {}, {}, {}, fields: {}]", + self.chain_name, + self.timestamp, + self.ttl, + self.pricing_mode, + self.initiator_addr, + DisplayIter::new(self.fields.keys()) + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + testing::TestRng, RuntimeArgs, TransactionEntryPoint, TransactionScheduling, + TransactionTarget, + }; + use std::collections::BTreeMap; + + #[test] + fn reserialize_should_work_with_ascending_ids() { + let input = vec![ + (0, Bytes::from(vec![1])), + (1, Bytes::from(vec![2])), + (4, Bytes::from(vec![3])), + ]; + let map = build_map(input).expect("Should not fail"); + assert_eq!( + map, + BTreeMap::from_iter(vec![ + (0, Bytes::from(vec![1])), + (1, Bytes::from(vec![2])), + (4, Bytes::from(vec![3])) + ]) + ); + } + + #[test] + fn reserialize_should_fail_when_ids_not_unique() { + let input = vec![ + (0, Bytes::from(vec![1])), + (0, Bytes::from(vec![2])), + (4, Bytes::from(vec![3])), + ]; + let map_ret = build_map(input); + assert!(map_ret.is_err()); + } + + #[test] + fn reserialize_should_fail_when_ids_not_ascending() { + let input = vec![ + (0, Bytes::from(vec![1])), + (2, Bytes::from(vec![2])), + (1, Bytes::from(vec![3])), + ]; + assert!(build_map(input).is_err()); + let input = vec![ + (0, Bytes::from(vec![1])), + (2, Bytes::from(vec![2])), + (0, Bytes::from(vec![3])), + ]; + assert!(build_map(input).is_err()); + let input = vec![ + (0, Bytes::from(vec![1])), + (1, Bytes::from(vec![2])), + (2, Bytes::from(vec![3])), + (3, Bytes::from(vec![4])), + (2, Bytes::from(vec![5])), + ]; + assert!(build_map(input).is_err()); + } + + #[test] + fn should_fail_if_deserialized_payload_has_too_many_fields() { + let rng = &mut TestRng::new(); + let ( + args, + target, + entry_point, + scheduling, + initiator_addr, + timestamp, + ttl, + chain_name, + pricing_mode, + ) = random_payload_data(rng); + let mut fields = BTreeMap::new(); + fields.insert(ARGS_MAP_KEY, args.to_bytes().unwrap().into()); + fields.insert(TARGET_MAP_KEY, target.to_bytes().unwrap().into()); + fields.insert(ENTRY_POINT_MAP_KEY, entry_point.to_bytes().unwrap().into()); + fields.insert(SCHEDULING_MAP_KEY, scheduling.to_bytes().unwrap().into()); + fields.insert(4, 111_u64.to_bytes().unwrap().into()); + + let bytes = TransactionV1Payload::new( + chain_name, + timestamp, + ttl, + pricing_mode, + initiator_addr, + fields, + ) + .to_bytes() + .unwrap(); + let result = TransactionV1Payload::from_bytes(&bytes); + assert!(result.is_err()); + } + + #[test] + fn should_fail_if_deserialized_payload_has_unrecognized_fields() { + let rng = &mut TestRng::new(); + let ( + args, + target, + entry_point, + scheduling, + initiator_addr, + timestamp, + ttl, + chain_name, + pricing_mode, + ) = random_payload_data(rng); + let mut fields = BTreeMap::new(); + fields.insert(ARGS_MAP_KEY, args.to_bytes().unwrap().into()); + fields.insert(TARGET_MAP_KEY, target.to_bytes().unwrap().into()); + fields.insert(100, entry_point.to_bytes().unwrap().into()); + fields.insert(SCHEDULING_MAP_KEY, scheduling.to_bytes().unwrap().into()); + + let bytes = TransactionV1Payload::new( + chain_name, + timestamp, + ttl, + pricing_mode, + initiator_addr, + fields, + ) + .to_bytes() + .unwrap(); + let result = TransactionV1Payload::from_bytes(&bytes); + assert!(result.is_err()); + } + + #[test] + fn should_fail_if_serialized_payoad_has_fields_out_of_order() { + let rng = &mut TestRng::new(); + let ( + args, + target, + entry_point, + scheduling, + initiator_addr, + timestamp, + ttl, + chain_name, + pricing_mode, + ) = random_payload_data(rng); + let fields: Vec<(u16, Bytes)> = vec![ + (SCHEDULING_MAP_KEY, scheduling.to_bytes().unwrap().into()), + (TARGET_MAP_KEY, target.to_bytes().unwrap().into()), + (ENTRY_POINT_MAP_KEY, entry_point.to_bytes().unwrap().into()), + (ARGS_MAP_KEY, args.to_bytes().unwrap().into()), + ]; + + let expected_payload_sizes = vec![ + initiator_addr.serialized_length(), + timestamp.serialized_length(), + ttl.serialized_length(), + chain_name.serialized_length(), + pricing_mode.serialized_length(), + fields.serialized_length(), + ]; + + let bytes = CalltableSerializationEnvelopeBuilder::new(expected_payload_sizes) + .unwrap() + .add_field(INITIATOR_ADDR_FIELD_INDEX, &initiator_addr) + .unwrap() + .add_field(TIMESTAMP_FIELD_INDEX, ×tamp) + .unwrap() + .add_field(TTL_FIELD_INDEX, &ttl) + .unwrap() + .add_field(CHAIN_NAME_FIELD_INDEX, &chain_name) + .unwrap() + .add_field(PRICING_MODE_FIELD_INDEX, &pricing_mode) + .unwrap() + .add_field(FIELDS_FIELD_INDEX, &fields) + .unwrap() + .binary_payload_bytes() + .unwrap(); + let payload_res = TransactionV1Payload::from_bytes(&bytes); + assert!(payload_res.is_err()); + } + + fn random_payload_data( + rng: &mut TestRng, + ) -> ( + RuntimeArgs, + TransactionTarget, + TransactionEntryPoint, + TransactionScheduling, + InitiatorAddr, + Timestamp, + TimeDiff, + String, + PricingMode, + ) { + let args = RuntimeArgs::random(rng); + let target = TransactionTarget::random(rng); + let entry_point = TransactionEntryPoint::random(rng); + let scheduling = TransactionScheduling::random(rng); + let initiator_addr = InitiatorAddr::random(rng); + let timestamp = Timestamp::now(); + let ttl = TimeDiff::from_millis(1000); + let chain_name = "chain-name".to_string(); + let pricing_mode = PricingMode::random(rng); + ( + args, + target, + entry_point, + scheduling, + initiator_addr, + timestamp, + ttl, + chain_name, + pricing_mode, + ) + } +} diff --git a/types/src/transaction/transfer_target.rs b/types/src/transaction/transfer_target.rs index ba38a93e3d..e1500a5384 100644 --- a/types/src/transaction/transfer_target.rs +++ b/types/src/transaction/transfer_target.rs @@ -6,7 +6,7 @@ use crate::testing::TestRng; use crate::{account::AccountHash, PublicKey, URef}; /// The various types which can be used as the `target` runtime argument of a native transfer. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] pub enum TransferTarget { /// A public key. PublicKey(PublicKey), diff --git a/types/src/uref.rs b/types/src/uref.rs index 29bd061b68..8e2b039298 100644 --- a/types/src/uref.rs +++ b/types/src/uref.rs @@ -207,7 +207,7 @@ impl URef { /// Removes specific access rights from this URef if present. pub fn disable_access_rights(&mut self, access_rights: AccessRights) { - self.1.remove(access_rights) + self.1.remove(access_rights); } } diff --git a/utils/global-state-update-gen/src/system_entity_registry.rs b/utils/global-state-update-gen/src/system_entity_registry.rs index 12c0723280..40b8facc73 100644 --- a/utils/global-state-update-gen/src/system_entity_registry.rs +++ b/utils/global-state-update-gen/src/system_entity_registry.rs @@ -128,7 +128,7 @@ fn generate_system_entity_registry_using_global_state(data_dir: &Path, state_has let registry = match builder .data_access_layer() .system_entity_registry(registry_req) - .as_legacy() + .as_registry_payload() .expect("should have payload") { SystemEntityRegistryPayload::All(registry) => registry,