diff --git a/.github/workflows/casper-node.yml b/.github/workflows/casper-node.yml index 81cb35282a..7024ec558f 100644 --- a/.github/workflows/casper-node.yml +++ b/.github/workflows/casper-node.yml @@ -24,21 +24,34 @@ on: - '**.md' jobs: - lints: - name: tests + checks: + name: checks runs-on: ubuntu-latest + timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v4 with: persist-credentials: false - - name: setup - run: make setup - - name: setup ubuntu run: | - sudo apt-get -y install wabt + sudo apt-get -y install wabt + + - name: cache setup tools + uses: actions/cache@v4 + with: + path: | + ~/.rustup/toolchains + ~/.rustup/update-hashes + ~/.rustup/settings.toml + ~/.cargo/bin/cargo-audit + key: setup-tools-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml', 'smart_contracts/rust-toolchain', 'Makefile') }} + restore-keys: | + setup-tools-${{ runner.os }}- + + - name: setup + run: make setup - uses: Swatinem/rust-cache@v2 @@ -60,8 +73,188 @@ jobs: - name: check-testing-features run: make check-testing-features + test: + name: test + runs-on: ubuntu-latest + timeout-minutes: 60 + needs: + - build_contracts + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: setup ubuntu + run: | + sudo apt-get -y install wabt + + - name: cache setup tools + uses: actions/cache@v4 + with: + path: | + ~/.rustup/toolchains + ~/.rustup/update-hashes + ~/.rustup/settings.toml + ~/.cargo/bin/cargo-audit + key: setup-tools-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml', 'smart_contracts/rust-toolchain', 'Makefile') }} + restore-keys: | + setup-tools-${{ runner.os }}- + + - name: setup + run: make setup + + - uses: Swatinem/rust-cache@v2 + + - name: download smart contracts + uses: actions/download-artifact@v4 + with: + name: smart-contracts + path: . + + - name: extract smart contracts + run: tar -xzf smart-contracts.tar.gz + - name: test - run: make test CARGO_FLAGS=--release + run: make test-rs-ci CARGO_FLAGS=--release CARGO_TEST_PROFILE_ENV='CARGO_PROFILE_RELEASE_LTO=false CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16' + + test_no_default_features: + name: test-no-default-features + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: setup ubuntu + run: | + sudo apt-get -y install wabt + + - name: cache setup tools + uses: actions/cache@v4 + with: + path: | + ~/.rustup/toolchains + ~/.rustup/update-hashes + ~/.rustup/settings.toml + ~/.cargo/bin/cargo-audit + key: setup-tools-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml', 'smart_contracts/rust-toolchain', 'Makefile') }} + restore-keys: | + setup-tools-${{ runner.os }}- + + - name: setup + run: make setup + + - uses: Swatinem/rust-cache@v2 + + - name: test-no-default-features + run: make test-rs-no-default-features CARGO_FLAGS=--release CARGO_TEST_PROFILE_ENV='CARGO_PROFILE_RELEASE_LTO=false CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16' + + build_contracts: + name: build-contracts + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: setup ubuntu + run: | + sudo apt-get -y install wabt + + - name: cache setup tools + uses: actions/cache@v4 + with: + path: | + ~/.rustup/toolchains + ~/.rustup/update-hashes + ~/.rustup/settings.toml + ~/.cargo/bin/cargo-audit + key: setup-tools-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml', 'smart_contracts/rust-toolchain', 'Makefile') }} + restore-keys: | + setup-tools-${{ runner.os }}- + + - name: setup + run: make setup + + - uses: Swatinem/rust-cache@v2 + + - name: build smart contracts + run: make build-test-artifacts CARGO_FLAGS=--release + + - name: archive smart contracts + run: tar -czf smart-contracts.tar.gz resources/local/chainspec.toml target/wasm32-unknown-unknown/release/*.wasm + + - name: upload smart contracts + uses: actions/upload-artifact@v4 + with: + name: smart-contracts + path: smart-contracts.tar.gz + + test_contracts: + name: test-contracts + runs-on: ubuntu-latest + timeout-minutes: 60 + needs: + - build_contracts + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: setup ubuntu + run: | + sudo apt-get -y install wabt + + - name: cache setup tools + uses: actions/cache@v4 + with: + path: | + ~/.rustup/toolchains + ~/.rustup/update-hashes + ~/.rustup/settings.toml + ~/.cargo/bin/cargo-audit + key: setup-tools-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml', 'smart_contracts/rust-toolchain', 'Makefile') }} + restore-keys: | + setup-tools-${{ runner.os }}- + + - name: setup + run: make setup + + - uses: Swatinem/rust-cache@v2 + + - name: download smart contracts + uses: actions/download-artifact@v4 + with: + name: smart-contracts + path: . + + - name: extract smart contracts + run: tar -xzf smart-contracts.tar.gz - name: test-contracts - run: make test-contracts CARGO_FLAGS=--release + run: make test-contracts-rs CARGO_FLAGS=--release CARGO_TEST_PROFILE_ENV='CARGO_PROFILE_RELEASE_LTO=false CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16' + + tests: + name: tests + runs-on: ubuntu-latest + needs: + - checks + - build_contracts + - test + - test_no_default_features + - test_contracts + if: ${{ always() }} + steps: + - name: check required jobs + run: | + test "${{ needs.checks.result }}" = "success" + test "${{ needs.build_contracts.result }}" = "success" + test "${{ needs.test.result }}" = "success" + test "${{ needs.test_no_default_features.result }}" = "success" + test "${{ needs.test_contracts.result }}" = "success" diff --git a/.github/workflows/push-artifacts.yml b/.github/workflows/push-artifacts.yml index 14a8111c49..4f01879e6e 100644 --- a/.github/workflows/push-artifacts.yml +++ b/.github/workflows/push-artifacts.yml @@ -40,7 +40,7 @@ jobs: python3 --version - name: Install cargo deb - run: cargo install cargo-deb + run: cargo install --git https://github.com/sacherjj/cargo-deb cargo-deb - name: Build update package run: ./ci/build_update_package.sh diff --git a/Makefile b/Makefile index e90d7da1e6..9a609327f4 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ # This supports environments where $HOME/.cargo/env has not been sourced (CI, CLion Makefile runner) CARGO = $(or $(shell which cargo), $(HOME)/.cargo/bin/cargo) RUSTUP = $(or $(shell which rustup), $(HOME)/.cargo/bin/rustup) +CARGO_AUDIT = $(or $(shell which cargo-audit), $(HOME)/.cargo/bin/cargo-audit) PINNED_NIGHTLY := $(shell cat smart_contracts/rust-toolchain) PINNED_STABLE := $(shell sed -nr 's/channel *= *\"(.*)\"/\1/p' rust-toolchain.toml) +CARGO_AUDIT_VERSION := 0.22.1 WASM_STRIP_VERSION := $(shell wasm-strip --version) CARGO_OPTS := --locked @@ -20,6 +22,7 @@ EVM_CONTRACTS = $(shell find ./smart_contracts/evm_contracts -mindepth 1 -max CARGO_HOME_REMAP = $(if $(CARGO_HOME),$(CARGO_HOME),$(HOME)/.cargo) RUSTC_FLAGS = "--remap-path-prefix=$(CARGO_HOME_REMAP)=/home/cargo --remap-path-prefix=$$PWD=/dir" WASM_RUSTC_FLAGS = "--remap-path-prefix=$(CARGO_HOME_REMAP)=/home/cargo --remap-path-prefix=$$PWD=/dir -C target-cpu=mvp -C target-feature=-bulk-memory" +CARGO_TEST_PROFILE_ENV ?= CONTRACT_TARGET_DIR = target/wasm32-unknown-unknown/release EVM_CONTRACT_TARGET_DIR = target/evm-contracts @@ -77,27 +80,38 @@ test-contracts-evm: build-contracts-evm resources/local/chainspec.toml: generate-chainspec.sh resources/local/chainspec.toml.in @./$< +.PHONY: build-test-artifacts +build-test-artifacts: resources/local/chainspec.toml build-contracts-rs + .PHONY: test-rs -test-rs: resources/local/chainspec.toml build-contracts-rs - $(LEGACY) $(DISABLE_LOGGING) $(CARGO) test --all-features --no-fail-fast $(CARGO_FLAGS) -- --nocapture +test-rs: + $(LEGACY) $(DISABLE_LOGGING) $(CARGO_TEST_PROFILE_ENV) $(CARGO) test --all-features --no-fail-fast $(CARGO_FLAGS) -- --nocapture + +.PHONY: test-rs-ci +test-rs-ci: + $(LEGACY) $(DISABLE_LOGGING) $(CARGO_TEST_PROFILE_ENV) $(CARGO) test --all-features --no-fail-fast $(CARGO_FLAGS) -- --nocapture --skip reactor::main_reactor::tests::rewards + $(LEGACY) $(DISABLE_LOGGING) $(CARGO_TEST_PROFILE_ENV) $(CARGO) test --all-features --no-fail-fast $(CARGO_FLAGS) -- --nocapture --test-threads=1 reactor::main_reactor::tests::rewards .PHONY: resources/local/chainspec.toml test-rs-no-default-features: - cd smart_contracts/contract && $(DISABLE_LOGGING) $(CARGO) test $(CARGO_FLAGS) --no-default-features --features=version-sync + cd smart_contracts/contract && $(DISABLE_LOGGING) $(CARGO_TEST_PROFILE_ENV) $(CARGO) test $(CARGO_FLAGS) --no-default-features --features=version-sync .PHONY: test -test: test-rs-no-default-features test-rs +test: build-test-artifacts test-rs-no-default-features test-rs + +.PHONY: test-ci +test-ci: test-rs-no-default-features test-rs-ci .PHONY: test-contracts-rs -test-contracts-rs: build-contracts-rs - $(DISABLE_LOGGING) $(CARGO) test $(CARGO_FLAGS) -p casper-engine-tests -- --ignored --skip repeated_ffi_call_should_gas_out_quickly +test-contracts-rs: + $(DISABLE_LOGGING) $(CARGO_TEST_PROFILE_ENV) $(CARGO) test $(CARGO_FLAGS) -p casper-engine-tests -- --ignored --skip repeated_ffi_call_should_gas_out_quickly .PHONY: test-contracts-timings -test-contracts-timings: build-contracts-rs +test-contracts-timings: resources/local/chainspec.toml build-contracts-rs $(DISABLE_LOGGING) $(CARGO) test --release $(filter-out --release, $(CARGO_FLAGS)) -p casper-engine-tests -- --ignored --test-threads=1 repeated_ffi_call_should_gas_out_quickly .PHONY: test-contracts -test-contracts: test-contracts-rs +test-contracts: build-test-artifacts test-contracts-rs .PHONY: check-no-default-features check-no-default-features: @@ -208,7 +222,8 @@ setup-rs: $(RUSTUP) component add --toolchain $(PINNED_NIGHTLY) rustfmt clippy-preview $(RUSTUP) component add --toolchain $(PINNED_STABLE) clippy-preview $(RUSTUP) component add rust-src --toolchain $(PINNED_NIGHTLY) - $(CARGO) install cargo-audit + $(CARGO_AUDIT) --version 2>/dev/null | grep -q ' $(CARGO_AUDIT_VERSION)$$' || \ + $(CARGO) install cargo-audit --version '=$(CARGO_AUDIT_VERSION)' .PHONY: setup setup: setup-rs diff --git a/execution_engine_testing/tests/src/test/explorer/faucet.rs b/execution_engine_testing/tests/src/test/explorer/faucet.rs index dae0472f28..cdd2035b2c 100644 --- a/execution_engine_testing/tests/src/test/explorer/faucet.rs +++ b/execution_engine_testing/tests/src/test/explorer/faucet.rs @@ -663,14 +663,14 @@ 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 = 112_723_697_942; + const EXPECTED_FAUCET_INSTALL_COST: u64 = 118_835_785_065; const EXPECTED_FAUCET_INSTALL_COST_ALT: u64 = 149_230_872_143; - const EXPECTED_FAUCET_SET_VARIABLES_COST: u64 = 79_796_780; + const EXPECTED_FAUCET_SET_VARIABLES_COST: u64 = 79_790_440; - const EXPECTED_FAUCET_CALL_BY_INSTALLER_COST: u64 = 2_652_960_368; + const EXPECTED_FAUCET_CALL_BY_INSTALLER_COST: u64 = 2_652_954_573; - const EXPECTED_FAUCET_CALL_BY_USER_COST: u64 = 2_558_834_176; + const EXPECTED_FAUCET_CALL_BY_USER_COST: u64 = 2_558_820_996; let installer_account = AccountHash::new([1u8; 32]); let user_account: AccountHash = AccountHash::new([2u8; 32]); diff --git a/node/src/components/contract_runtime/tests.rs b/node/src/components/contract_runtime/tests.rs index c147f774a6..cfcfcd0987 100644 --- a/node/src/components/contract_runtime/tests.rs +++ b/node/src/components/contract_runtime/tests.rs @@ -539,6 +539,18 @@ async fn should_correctly_manage_entity_version_calls() { .contract_runtime .set_execution_pre_state(initial_pre_state); + // Prepare to create a block which will take a while to execute, i.e. loaded with many deploys + // transferring from node-1's main account to new random public keys. + let proposer_secret_key = SecretKey::from_file( + RESOURCES_PATH + .join("local") + .join("secret_keys") + .join("node-2.pem"), + ) + .unwrap(); + + let proposer = PublicKey::from(&proposer_secret_key); + // Create the genesis immediate switch block. let block_0 = ExecutableBlock::from_finalized_block_and_transactions( FinalizedBlock::new( @@ -547,7 +559,7 @@ async fn should_correctly_manage_entity_version_calls() { Timestamp::now(), EraId::new(0), 0, - PublicKey::System, + proposer.clone(), ), vec![], ); @@ -567,7 +579,7 @@ async fn should_correctly_manage_entity_version_calls() { Timestamp::now(), EraId::new(1), 1, - PublicKey::System, + proposer.clone(), ), vec![], ); @@ -621,7 +633,7 @@ async fn should_correctly_manage_entity_version_calls() { Timestamp::now(), EraId::new(1), 2, - PublicKey::System, + proposer.clone(), ), vec![installer_transaction], ); @@ -701,7 +713,7 @@ async fn should_correctly_manage_entity_version_calls() { Timestamp::now(), EraId::new(1), 3, - PublicKey::System, + proposer.clone(), ), vec![upgrader_transaction], ); @@ -823,7 +835,7 @@ async fn should_correctly_manage_entity_version_calls() { Timestamp::now(), EraId::new(1), 4, - PublicKey::System, + proposer.clone(), ), txns.clone(), ); diff --git a/node/src/reactor/main_reactor/tests/rewards.rs b/node/src/reactor/main_reactor/tests/rewards.rs index 310caae564..cae59643d6 100644 --- a/node/src/reactor/main_reactor/tests/rewards.rs +++ b/node/src/reactor/main_reactor/tests/rewards.rs @@ -30,12 +30,12 @@ use crate::{ const STAKE: u128 = 1000000000; const PRIME_STAKES: [u128; 5] = [106907, 106921, 106937, 106949, 106957]; const ERA_COUNT: u64 = 3; -const ERA_DURATION: u64 = 20000; +const ERA_DURATION: u64 = 5000; //milliseconds -const MIN_HEIGHT: u64 = 6; +const MIN_HEIGHT: u64 = 4; const BLOCK_TIME: u64 = 1750; //milliseconds -const TIME_OUT: u64 = 600; +const TIME_OUT: u64 = 900; //seconds const SEIGNIORAGE: (u64, u64) = (1u64, 100u64); const REPRESENTATIVE_NODE_INDEX: usize = 0; diff --git a/smart_contracts/contracts/explorer/faucet/README.md b/smart_contracts/contracts/explorer/faucet/README.md index a521c6460a..9bdfdad11d 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 | `112_723_697_942` | -| faucet set variables | `79_796_780` | -| faucet call by installer | `2_652_960_368` | -| faucet call by user | `2_558_834_176` | +| faucet install | `118_835_785_065` | +| faucet set variables | `79_790_440` | +| faucet call by installer | `2_652_954_573` | +| faucet call by user | `2_558_820_996` | diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index b3fb3ced40..d17850bd59 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -222,7 +222,7 @@ pub trait CommitProvider: StateProvider { let protocol_upgrader: ProtocolUpgrader = ProtocolUpgrader::new(request.config().clone(), pre_state_hash, tc); - let post_upgrade_tc = match protocol_upgrader.upgrade(pre_state_hash) { + let post_upgrade_tc = match protocol_upgrader.upgrade() { Err(e) => return e.into(), Ok(tc) => tc, }; @@ -902,7 +902,7 @@ pub trait StateProvider: Send + Sync + Sized { // but the system will put a hold on whatever balance remains. // this is basically punitive to block an edge case resource consumption // attack whereby a malicious purse holder drains a balance to not-zero - // but not-enough-to-cover-holds and then spams a bunch of transactions + // but not-enough-to-cover-holds. they could then spam transactions // knowing that they will fail due to insufficient funds, but only // after making the system do the work of processing the balance // check without penalty to themselves. @@ -2625,11 +2625,10 @@ fn get_snapshot_data( Ok(rewards_handling) => { match rewards_handling.get(&REWARDS_HANDLING_RATIO_TAG) { Some(bytes) => { - let ratio = - match casper_types::bytesrepr::FromBytes::from_bytes(bytes) { - Ok((ratio, _)) => ratio, - Err(_) => Ratio::new(0, 1), - }; + let ratio = match bytesrepr::FromBytes::from_bytes(bytes) { + Ok((ratio, _)) => ratio, + Err(_) => Ratio::new(0, 1), + }; ratio } diff --git a/storage/src/system/genesis/account_contract_installer.rs b/storage/src/system/genesis/account_contract_installer.rs index b2fc8afafc..7472b62300 100644 --- a/storage/src/system/genesis/account_contract_installer.rs +++ b/storage/src/system/genesis/account_contract_installer.rs @@ -585,10 +585,12 @@ where for account in accounts { let account_hash = account.account_hash(); let main_purse = match account { - GenesisAccount::System - if self.config.administrative_accounts().next().is_some() => - { - payment_purse_uref + GenesisAccount::System => { + if self.config.administrative_accounts().next().is_some() { + payment_purse_uref + } else { + self.create_purse(account.balance().value())?.into_read() + } } _ => self.create_purse(account.balance().value())?, }; diff --git a/storage/src/system/genesis/entity_installer.rs b/storage/src/system/genesis/entity_installer.rs index db6db59976..ff57767789 100644 --- a/storage/src/system/genesis/entity_installer.rs +++ b/storage/src/system/genesis/entity_installer.rs @@ -576,8 +576,10 @@ where for account in accounts { let account_starting_balance = account.balance().value(); - - let main_purse = self.create_purse(account_starting_balance)?; + let main_purse = match account { + GenesisAccount::System => self.create_purse(account_starting_balance)?.into_read(), + _ => self.create_purse(account_starting_balance)?, + }; self.store_addressable_entity( EntityKind::Account(account.account_hash()), @@ -876,7 +878,7 @@ where // Create all genesis accounts self.create_accounts(total_supply_key)?; - // Create the auction and setup the stake of all genesis validators. + // Create the auction and set up the stake of all genesis validators. self.create_auction(total_supply_key)?; // Create handle payment diff --git a/storage/src/system/protocol_upgrade.rs b/storage/src/system/protocol_upgrade.rs index 1148b9a720..dd7e696719 100644 --- a/storage/src/system/protocol_upgrade.rs +++ b/storage/src/system/protocol_upgrade.rs @@ -9,9 +9,14 @@ use std::{ use thiserror::Error; use tracing::{debug, error, info, warn}; +use crate::{ + global_state::state::StateProvider, + tracking_copy::{AddResult, TrackingCopy, TrackingCopyEntityExt, TrackingCopyExt}, + AddressGenerator, +}; use casper_types::{ addressable_entity::{ - ActionThresholds, AssociatedKeys, EntityKind, NamedKeyAddr, NamedKeyValue, Weight, + ActionThresholds, AssociatedKeys, EntityKind, NamedKeyAddr, NamedKeyValue, }, bytesrepr::{self, Bytes, ToBytes}, contracts::{ContractHash, ContractPackageStatus, NamedKeys}, @@ -32,18 +37,12 @@ use casper_types::{ }, SystemEntityType, AUCTION, HANDLE_PAYMENT, MINT, }, - AccessRights, AddressableEntity, AddressableEntityHash, ByteCode, ByteCodeAddr, ByteCodeHash, - ByteCodeKind, CLValue, CLValueError, Contract, Digest, EntityAddr, EntityVersionKey, - EntityVersions, EntryPointAddr, EntryPointValue, EntryPoints, EraId, FeeHandling, Groups, - HashAddr, Key, KeyTag, Motes, Package, PackageHash, PackageStatus, Phase, + AccessRights, Account, AddressableEntity, AddressableEntityHash, ByteCode, ByteCodeAddr, + ByteCodeHash, ByteCodeKind, CLValue, CLValueError, Contract, Digest, EntityAddr, + EntityVersionKey, EntityVersions, EntryPointAddr, EntryPointValue, EntryPoints, EraId, + FeeHandling, Groups, HashAddr, Key, KeyTag, Motes, Package, PackageHash, PackageStatus, Phase, ProtocolUpgradeConfig, ProtocolVersion, PublicKey, RewardsHandling, StoredValue, - SystemHashRegistry, URef, REWARDS_HANDLING_RATIO_TAG, U512, -}; - -use crate::{ - global_state::state::StateProvider, - tracking_copy::{AddResult, TrackingCopy, TrackingCopyEntityExt, TrackingCopyExt}, - AddressGenerator, + SystemHashRegistry, URef, URefAddr, REWARDS_HANDLING_RATIO_TAG, U512, }; const NO_CARRY_FORWARD: bool = false; @@ -88,6 +87,9 @@ pub enum ProtocolUpgradeError { /// Tracking copy error. #[error("{0}")] TrackingCopy(crate::tracking_copy::TrackingCopyError), + /// Missing stored value expected to be in global state. + #[error("Missing expected stored value: {0}")] + MissingStoredValue(String), } impl From for ProtocolUpgradeError { @@ -178,14 +180,15 @@ where /// Apply a protocol upgrade. pub fn upgrade( mut self, - pre_state_hash: Digest, ) -> Result::Reader>, ProtocolUpgradeError> { self.check_next_protocol_version_validity()?; self.handle_global_state_updates(); let system_entity_addresses = self.handle_system_hashes()?; + self.read_only_system_purse(system_entity_addresses.mint)?; + if self.config.enable_addressable_entity() { - self.migrate_system_account(pre_state_hash)?; + self.migrate_system_account(system_entity_addresses.mint())?; self.create_accumulation_purse_if_required( &system_entity_addresses.handle_payment(), self.config.fee_handling(), @@ -673,40 +676,42 @@ where Ok(()) } - /// Migrate the system account to addressable entity if necessary. - pub fn migrate_system_account( - &mut self, - pre_state_hash: Digest, - ) -> Result<(), ProtocolUpgradeError> { + /// Migrates the system account to addressable entity. + pub fn migrate_system_account(&mut self, mint: HashAddr) -> Result<(), ProtocolUpgradeError> { debug!("migrate system account"); - let mut address_generator = AddressGenerator::new(pre_state_hash.as_ref(), Phase::System); - let account_hash = PublicKey::System.to_account_hash(); + let account_key = Key::Account(account_hash); + let account = match self.get_account_repr(account_key)? { + AccountRepr::Account(account) => account, + AccountRepr::Entity(_) | AccountRepr::None => { + // migration has already occurred or chain never used account / contract model + return Ok(()); + } + }; - let main_purse = { - let purse_addr = address_generator.new_hash_address(); - let balance_cl_value = CLValue::from_t(U512::zero()) - .map_err(|error| ProtocolUpgradeError::CLValue(error.to_string()))?; + let main_purse = account.main_purse().into_read(); - self.tracking_copy.write( - Key::Balance(purse_addr), - StoredValue::CLValue(balance_cl_value), - ); + let system_account_balance = match self + .tracking_copy + .get_total_balance(Key::Balance(main_purse.addr())) + { + Ok(balance) => balance, + Err(tce) => return Err(ProtocolUpgradeError::TrackingCopy(tce)), + }; - let purse_cl_value = CLValue::unit(); - let purse_uref = URef::new(purse_addr, AccessRights::READ_ADD_WRITE); + if system_account_balance.value() > U512::zero() { + warn!( + "system account had balance at upgrade, burning {}", + system_account_balance + ); - self.tracking_copy - .write(Key::URef(purse_uref), StoredValue::CLValue(purse_cl_value)); - purse_uref - }; + self.burn(mint, system_account_balance.value(), main_purse.addr())?; + } - let associated_keys = AssociatedKeys::new(account_hash, Weight::new(1)); - let byte_code_hash = ByteCodeHash::default(); - let entity_hash = AddressableEntityHash::new(PublicKey::System.to_account_hash().value()); + let mut address_generator = self.address_generator.borrow_mut(); let package_hash = PackageHash::new(address_generator.new_hash_address()); - - let byte_code = ByteCode::new(ByteCodeKind::Empty, vec![]); + let associated_keys = account.associated_keys().clone().into(); + let byte_code_hash = ByteCodeHash::default(); let system_account_entity = AddressableEntity::new( package_hash, @@ -718,6 +723,9 @@ where EntityKind::Account(account_hash), ); + let entity_hash = AddressableEntityHash::new(PublicKey::System.to_account_hash().value()); + let byte_code = ByteCode::new(ByteCodeKind::Empty, vec![]); + let package = { let mut package = Package::new( EntityVersions::default(), @@ -746,18 +754,19 @@ where self.tracking_copy .write(package_hash.into(), StoredValue::SmartContract(package)); - let contract_by_account = CLValue::from_t(entity_key) + let entity_pointer = CLValue::from_t(entity_key) .map_err(|error| ProtocolUpgradeError::CLValue(error.to_string()))?; + // this overwrites the original account with a pointer record to the new entity self.tracking_copy.write( Key::Account(account_hash), - StoredValue::CLValue(contract_by_account), + StoredValue::CLValue(entity_pointer), ); Ok(()) } - /// Creates an accumulation purse in the handle payment system contract if its not present. + /// Creates an accumulation purse in the handle payment system contract if it's not present. /// /// This can happen on older networks that did not have support for [`FeeHandling::Accumulate`] /// at the genesis. In such cases we have to check the state of handle payment contract and @@ -807,13 +816,13 @@ where NamedKeyAddr::new_from_string(entity_addr, ACCUMULATION_PURSE_KEY.to_string()) .map_err(|err| ProtocolUpgradeError::Bytesrepr(err.to_string()))?; - let requries_accumulation_purse = self + let requires_accumulation_purse = self .tracking_copy .read(&Key::NamedKey(named_key_addr)) .map_err(|_| ProtocolUpgradeError::UnexpectedStoredValueVariant)? .is_none(); - if requries_accumulation_purse { + if requires_accumulation_purse { let purse_uref = address_generator.new_uref(AccessRights::READ_ADD_WRITE); let balance_clvalue = CLValue::from_t(U512::zero())?; self.tracking_copy.write( @@ -844,7 +853,7 @@ where Ok(()) } - /// Creates an accumulation purse in the handle payment system contract if its not present. + /// Creates an accumulation purse in the handle payment system contract if it's not present. /// /// This can happen on older networks that did not have support for [`FeeHandling::Accumulate`] /// at the genesis. In such cases we have to check the state of handle payment contract and @@ -957,54 +966,12 @@ where return Ok(()); } warn!("payment purse had remaining balance at upgrade {}", balance); - let balance_key = { - let uref_addr = payment_purse_key - .as_uref() - .expect("payment purse key must be uref.") - .addr(); - Key::Balance(uref_addr) - }; - let mint_named_keys = self.get_named_keys(mint)?; - let total_supply_key = mint_named_keys - .get(TOTAL_SUPPLY_KEY) - .expect("total supply key must exist in mint contract's named keys"); - - let stored_value = self - .tracking_copy - .read(total_supply_key) - .expect("must be able to read total supply") - .expect("total supply must have a value"); - - // by convention, we only store CLValues under Key::URef - if let StoredValue::CLValue(value) = stored_value { - // Only CLTyped instances should be stored as a CLValue. - let total_supply: U512 = - CLValue::into_t(value).expect("total supply must have expected type."); - - let new_total_supply = total_supply.saturating_sub(balance.value()); - info!( - "adjusting total supply from {} to {}", - total_supply, new_total_supply - ); - let cl_value = CLValue::from_t(new_total_supply) - .expect("new total supply must convert to CLValue."); - self.tracking_copy - .write(*total_supply_key, StoredValue::CLValue(cl_value)); - info!( - "adjusting payment purse balance from {} to {}", - balance.value(), - U512::zero() - ); - let cl_value = CLValue::from_t(U512::zero()).expect("zero must convert to CLValue."); - self.tracking_copy - .write(balance_key, StoredValue::CLValue(cl_value)); - Ok(()) - } else { - Err(ProtocolUpgradeError::CLValue( - "failure to retrieve total supply".to_string(), - )) - } + let uref_addr = payment_purse_key + .as_uref() + .expect("payment purse key must be uref.") + .addr(); + self.burn(mint, balance.value(), uref_addr) } /// Upsert gas hold interval to mint named keys. @@ -1255,11 +1222,11 @@ where if existing_bid.staked_amount().is_zero() { // the previous logic enforces unbonding all delegators of // a validator that reduced their personal stake to 0 (and we have - // various existent tests that prove this), thus there is no need + // various existent tests that prove this). thus there is no need // to handle the complicated hypothetical case of one or more // delegator stakes being > 0 if the validator stake is 0. // - // tl;dr this is a "zombie" bid and we don't need to continue + // tl;dr this is a "zombie" bid, and we don't need to continue // carrying it forward at tip. continue; } @@ -1750,4 +1717,166 @@ where )?; Ok(()) } + + /// Sets system purse to read only, if necessary. + pub fn read_only_system_purse(&mut self, mint: HashAddr) -> Result<(), ProtocolUpgradeError> { + debug!("set system purse to read only"); + let account_hash = PublicKey::System.to_account_hash(); + let key = Key::Account(account_hash); + let main_purse_addr = match self.get_account_repr(key)? { + AccountRepr::Account(account) => self.upsert_account_purse_read_only(key, account), + AccountRepr::Entity(entity) => self.upsert_entity_purse_read_only(key, entity), + AccountRepr::None => { + // this applies on a network that never ran account / contract model + let key = Key::AddressableEntity(EntityAddr::Account(account_hash.value())); + if let AccountRepr::Entity(entity) = self.get_account_repr(key)? { + self.upsert_entity_purse_read_only(key, entity) + } else { + // this should be unreachable under current reality as a network + // either genesis'd with account contract and never upgraded to ae, + // or genesis'd with a / c and upgraded to ae + // or genesis'd with ae and never had a / c + return Err(ProtocolUpgradeError::MissingStoredValue( + "system account and entity not found".to_string(), + )); + } + } + }; + let system_account_balance = match self + .tracking_copy + .get_total_balance(Key::Balance(main_purse_addr)) + { + Ok(balance) => balance, + Err(tce) => return Err(ProtocolUpgradeError::TrackingCopy(tce)), + }; + + // if balance > 0, burn balance + if system_account_balance.value() > U512::zero() { + warn!( + "system account had balance at upgrade, burning {}", + system_account_balance + ); + + self.burn(mint, system_account_balance.value(), main_purse_addr)?; + } + Ok(()) + } + + /// Sets system purse of an account to read only, if necessary. + fn upsert_account_purse_read_only(&mut self, key: Key, account: Account) -> URefAddr { + let main_purse = account.main_purse(); + let main_purse_addr = main_purse.addr(); + if main_purse.is_addable() || main_purse.is_writeable() { + let account_hash = PublicKey::System.to_account_hash(); + let read_only_purse = main_purse.into_read(); + let updated_account = Account::new( + account_hash, + account.named_keys().clone(), + read_only_purse, + account.associated_keys().clone(), + account.action_thresholds().clone(), + ); + self.tracking_copy + .write(key, StoredValue::Account(updated_account)); + } + main_purse_addr + } + + /// Sets system purse of an entity to read only, if necessary. + fn upsert_entity_purse_read_only(&mut self, key: Key, entity: AddressableEntity) -> URefAddr { + let main_purse = entity.main_purse(); + let main_purse_addr = main_purse.addr(); + if main_purse.is_addable() || main_purse.is_writeable() { + let read_only_purse = main_purse.into_read(); + let updated_entity = AddressableEntity::new( + entity.package_hash(), + entity.byte_code_hash(), + entity.protocol_version(), + read_only_purse, + entity.associated_keys().clone(), + entity.action_thresholds().clone(), + entity.entity_kind(), + ); + self.tracking_copy + .write(key, StoredValue::AddressableEntity(updated_entity)); + } + + main_purse_addr + } + + /// Returns account representation if available, or error. + fn get_account_repr(&mut self, key: Key) -> Result { + match self.tracking_copy.read(&key) { + Ok(Some(StoredValue::Account(account))) => Ok(AccountRepr::Account(account)), + Ok(Some(StoredValue::CLValue(cl_value))) => match cl_value.into_t::() { + Ok(Key::AddressableEntity(entity_addr)) => { + let entity_key = Key::AddressableEntity(entity_addr); + if let Some(StoredValue::AddressableEntity(entity)) = + self.tracking_copy.read(&entity_key)? + { + Ok(AccountRepr::Entity(entity)) + } else { + Err(ProtocolUpgradeError::UnexpectedStoredValueVariant) + } + } + Ok(_) => Err(ProtocolUpgradeError::UnexpectedStoredValueVariant), + Err(cve) => Err(ProtocolUpgradeError::CLValue(cve.to_string())), + }, + Ok(Some(StoredValue::AddressableEntity(entity))) => Ok(AccountRepr::Entity(entity)), + Ok(Some(_)) => Err(ProtocolUpgradeError::UnexpectedStoredValueVariant), + Ok(None) => Ok(AccountRepr::None), // not found is not necessarily an error + Err(tce) => Err(ProtocolUpgradeError::TrackingCopy(tce)), + } + } + + /// Burns amount from target purse, and reduces total supply by the same amount. + fn burn( + &mut self, + mint: HashAddr, + burn_amount: U512, + purse_to_burn_from: URefAddr, + ) -> Result<(), ProtocolUpgradeError> { + let mint_named_keys = self.get_named_keys(mint)?; + let total_supply_key = mint_named_keys + .get(TOTAL_SUPPLY_KEY) + .expect("total supply key must exist in mint contract's named keys"); + let stored_value = self + .tracking_copy + .read(total_supply_key) + .expect("must be able to read total supply") + .expect("total supply must have a value"); + + if let StoredValue::CLValue(value) = stored_value { + // Only CLTyped instances should be stored as a CLValue. + let total_supply: U512 = + CLValue::into_t(value).expect("total supply must have expected type."); + + let new_total_supply = total_supply.saturating_sub(burn_amount); + info!( + "adjusting total supply from {} to {}", + total_supply, new_total_supply + ); + let cl_value = CLValue::from_t(new_total_supply) + .expect("new total supply must convert to CLValue."); + self.tracking_copy + .write(*total_supply_key, StoredValue::CLValue(cl_value)); + + let cl_value = CLValue::from_t(U512::zero()).expect("zero must convert to CLValue."); + self.tracking_copy.write( + Key::Balance(purse_to_burn_from), + StoredValue::CLValue(cl_value), + ); + Ok(()) + } else { + Err(ProtocolUpgradeError::CLValue( + "failure to retrieve total supply".to_string(), + )) + } + } +} + +enum AccountRepr { + None, + Account(Account), + Entity(AddressableEntity), }