From 32c567c491b06a86a691653b43d5357283abd3ee Mon Sep 17 00:00:00 2001 From: Anton Puhach Date: Wed, 18 Dec 2024 21:42:21 +0100 Subject: [PATCH 1/6] add action --- chain/rosetta-rpc/src/adapters/mod.rs | 3 +++ core/primitives/src/action/mod.rs | 27 +++++++++++++++++++++ core/primitives/src/views.rs | 1 + runtime/runtime/src/actions.rs | 17 ++++++++++++- runtime/runtime/src/config.rs | 14 +++++++++++ runtime/runtime/src/lib.rs | 8 ++++++ runtime/runtime/src/pipelining.rs | 1 + runtime/runtime/src/verifier.rs | 17 +++++++++++++ tools/state-viewer/src/contract_accounts.rs | 2 ++ 9 files changed, 89 insertions(+), 1 deletion(-) diff --git a/chain/rosetta-rpc/src/adapters/mod.rs b/chain/rosetta-rpc/src/adapters/mod.rs index 5437430499c..93349c247d0 100644 --- a/chain/rosetta-rpc/src/adapters/mod.rs +++ b/chain/rosetta-rpc/src/adapters/mod.rs @@ -500,6 +500,9 @@ impl From for Vec { operations.extend(delegated_operations); } // TODO(#8469): Implement delegate action support, for now they are ignored. + near_primitives::action::Action::DeployGlobalContract(_) => { + // TODO(#12639): Implement global contracts support, ignored for now + } } } operations diff --git a/core/primitives/src/action/mod.rs b/core/primitives/src/action/mod.rs index c54e367d66d..49cb4ee4f0b 100644 --- a/core/primitives/src/action/mod.rs +++ b/core/primitives/src/action/mod.rs @@ -106,6 +106,32 @@ impl fmt::Debug for DeployContractAction { } } +/// Deploy global contract action +#[serde_as] +#[derive( + BorshSerialize, + BorshDeserialize, + serde::Serialize, + serde::Deserialize, + PartialEq, + Eq, + Clone, + ProtocolSchema, +)] +pub struct DeployGlobalContractAction { + /// WebAssembly binary + #[serde_as(as = "Base64")] + pub code: Vec +} + +impl fmt::Debug for DeployGlobalContractAction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DeployGlobalContractAction") + .field("code", &format_args!("{}", base64(&self.code))) + .finish() + } +} + #[serde_as] #[derive( BorshSerialize, @@ -216,6 +242,7 @@ pub enum Action { DeleteKey(Box), DeleteAccount(DeleteAccountAction), Delegate(Box), + DeployGlobalContract(DeployGlobalContractAction), #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] /// Makes a non-refundable transfer for storage allowance. /// Only possible during new account creation. diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index c82f623cfd8..945abde31a3 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -1206,6 +1206,7 @@ impl From for ActionView { delegate_action: action.delegate_action, signature: action.signature, }, + Action::DeployGlobalContract(_) => todo!(), } } } diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index ed832ee9cb6..85370122e2a 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -9,6 +9,7 @@ use near_crypto::PublicKey; use near_parameters::{AccountCreationConfig, ActionCosts, RuntimeConfig, RuntimeFeesConfig}; use near_primitives::account::{AccessKey, AccessKeyPermission, Account}; use near_primitives::action::delegate::{DelegateAction, SignedDelegateAction}; +use near_primitives::action::DeployGlobalContractAction; use near_primitives::checked_feature; use near_primitives::config::ViewConfig; use near_primitives::errors::{ActionError, ActionErrorKind, InvalidAccessKeyError, RuntimeError}; @@ -653,6 +654,15 @@ pub(crate) fn action_deploy_contract( Ok(()) } +pub(crate) fn action_deploy_global_contract( + _state_update: &mut TrieUpdate, + _account: &mut Account, + _account_id: &AccountId, + _deploy_contract: &DeployGlobalContractAction, +) -> Result<(), StorageError> { + Ok(()) +} + pub(crate) fn action_delete_account( state_update: &mut TrieUpdate, account: &mut Option, @@ -1025,7 +1035,11 @@ pub(crate) fn check_actor_permissions( account_id: &AccountId, ) -> Result<(), ActionError> { match action { - Action::DeployContract(_) | Action::Stake(_) | Action::AddKey(_) | Action::DeleteKey(_) => { + Action::DeployContract(_) + | Action::DeployGlobalContract(_) + | Action::Stake(_) + | Action::AddKey(_) + | Action::DeleteKey(_) => { if actor_id != account_id { return Err(ActionErrorKind::ActorNoPermission { account_id: account_id.clone(), @@ -1132,6 +1146,7 @@ pub(crate) fn check_account_existence( } } Action::DeployContract(_) + | Action::DeployGlobalContract(_) | Action::FunctionCall(_) | Action::Stake(_) | Action::AddKey(_) diff --git a/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index a833af19697..e7ccca52cfc 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -1,6 +1,7 @@ //! Settings of the parameters of the runtime. use near_primitives::account::AccessKeyPermission; +use near_primitives::action::DeployGlobalContractAction; use near_primitives::errors::IntegerOverflowError; use near_primitives::version::FIXED_MINIMUM_NEW_RECEIPT_GAS_VERSION; use near_primitives_core::types::ProtocolVersion; @@ -87,6 +88,13 @@ pub fn total_send_fees( + fees.fee(ActionCosts::deploy_contract_byte).send_fee(sender_is_receiver) * num_bytes } + DeployGlobalContract(DeployGlobalContractAction{ code }) => { + let num_bytes = code.len() as u64; + // TODO(#12639): introduce separate fees for global contracts + fees.fee(ActionCosts::deploy_contract_base).send_fee(sender_is_receiver) + + fees.fee(ActionCosts::deploy_contract_byte).send_fee(sender_is_receiver) + * num_bytes + } FunctionCall(function_call_action) => { let num_bytes = function_call_action.method_name.as_bytes().len() as u64 + function_call_action.args.len() as u64; @@ -197,6 +205,12 @@ pub fn exec_fee(config: &RuntimeConfig, action: &Action, receiver_id: &AccountId fees.fee(ActionCosts::deploy_contract_base).exec_fee() + fees.fee(ActionCosts::deploy_contract_byte).exec_fee() * num_bytes } + DeployGlobalContract(DeployGlobalContractAction { code }) => { + let num_bytes = code.len() as u64; + // TODO(#12639): introduce fees for global contracts + fees.fee(ActionCosts::deploy_contract_base).exec_fee() + + fees.fee(ActionCosts::deploy_contract_byte).exec_fee() * num_bytes + } FunctionCall(function_call_action) => { let num_bytes = function_call_action.method_name.as_bytes().len() as u64 + function_call_action.args.len() as u64; diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index 1ca1a43b3de..ece65379a7f 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -460,6 +460,14 @@ impl Runtime { apply_state.current_protocol_version, )?; } + Action::DeployGlobalContract(deploy_global_contract) => { + action_deploy_global_contract( + state_update, + account.as_mut().expect(EXPECT_ACCOUNT_EXISTS), + account_id, + deploy_global_contract, + )?; + } Action::FunctionCall(function_call) => { let account = account.as_mut().expect(EXPECT_ACCOUNT_EXISTS); let contract = preparation_pipeline.get_contract( diff --git a/runtime/runtime/src/pipelining.rs b/runtime/runtime/src/pipelining.rs index 3fd06b49aa8..690e66a5665 100644 --- a/runtime/runtime/src/pipelining.rs +++ b/runtime/runtime/src/pipelining.rs @@ -185,6 +185,7 @@ impl ReceiptPreparationPipeline { Action::Delegate(_) => {} // No handling for these. Action::CreateAccount(_) + | Action::DeployGlobalContract(_) | Action::Transfer(_) | Action::Stake(_) | Action::AddKey(_) diff --git a/runtime/runtime/src/verifier.rs b/runtime/runtime/src/verifier.rs index e66d4a5aa34..a9e72411bd2 100644 --- a/runtime/runtime/src/verifier.rs +++ b/runtime/runtime/src/verifier.rs @@ -5,6 +5,7 @@ use near_crypto::key_conversion::is_valid_staking_key; use near_parameters::RuntimeConfig; use near_primitives::account::AccessKeyPermission; use near_primitives::action::delegate::SignedDelegateAction; +use near_primitives::action::DeployGlobalContractAction; use near_primitives::checked_feature; use near_primitives::errors::{ ActionsValidationError, InvalidAccessKeyError, InvalidTxError, ReceiptValidationError, @@ -420,6 +421,7 @@ pub fn validate_action( match action { Action::CreateAccount(_) => Ok(()), Action::DeployContract(a) => validate_deploy_contract_action(limit_config, a), + Action::DeployGlobalContract(a) => validate_deploy_global_contract_action(limit_config, a), Action::FunctionCall(a) => validate_function_call_action(limit_config, a), Action::Transfer(_) => Ok(()), #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] @@ -459,6 +461,21 @@ fn validate_deploy_contract_action( Ok(()) } +/// Validates `DeployGlobalContractAction`. Checks that the given contract size doesn't exceed the limit. +fn validate_deploy_global_contract_action( + limit_config: &LimitConfig, + action: &DeployGlobalContractAction, +) -> Result<(), ActionsValidationError> { + if action.code.len() as u64 > limit_config.max_contract_size { + return Err(ActionsValidationError::ContractSizeExceeded { + size: action.code.len() as u64, + limit: limit_config.max_contract_size, + }); + } + + Ok(()) +} + /// Validates `FunctionCallAction`. Checks that the method name length doesn't exceed the limit and /// the length of the arguments doesn't exceed the limit. fn validate_function_call_action( diff --git a/tools/state-viewer/src/contract_accounts.rs b/tools/state-viewer/src/contract_accounts.rs index 57adfe41f9b..115361aad8c 100644 --- a/tools/state-viewer/src/contract_accounts.rs +++ b/tools/state-viewer/src/contract_accounts.rs @@ -136,6 +136,7 @@ pub(crate) enum ActionType { DeleteAccount, DataReceipt, Delegate, + DeployGlobalContract, } impl ContractAccount { @@ -348,6 +349,7 @@ fn try_find_actions_spawned_by_receipt( Action::DeleteKey(_) => ActionType::DeleteKey, Action::DeleteAccount(_) => ActionType::DeleteAccount, Action::Delegate(_) => ActionType::Delegate, + Action::DeployGlobalContract(_) => ActionType::DeployGlobalContract, }; entry .actions From d9b9e9113a20565b7bce3feea117e40b5f1adc6f Mon Sep 17 00:00:00 2001 From: Anton Puhach Date: Fri, 20 Dec 2024 20:58:35 +0100 Subject: [PATCH 2/6] Add distribution via receipt --- core/primitives/src/action/mod.rs | 4 +-- core/primitives/src/receipt.rs | 35 +++++++++++++++++++++ core/primitives/src/views.rs | 1 + core/store/src/genesis/state_applier.rs | 4 ++- runtime/runtime/src/actions.rs | 18 +++++++---- runtime/runtime/src/balance_checker.rs | 8 ++++- runtime/runtime/src/config.rs | 2 +- runtime/runtime/src/congestion_control.rs | 1 + runtime/runtime/src/lib.rs | 14 +++++---- runtime/runtime/src/pipelining.rs | 8 +++-- runtime/runtime/src/prefetch.rs | 4 ++- runtime/runtime/src/verifier.rs | 24 +++++++++++++- tools/state-viewer/src/contract_accounts.rs | 5 ++- 13 files changed, 106 insertions(+), 22 deletions(-) diff --git a/core/primitives/src/action/mod.rs b/core/primitives/src/action/mod.rs index 49cb4ee4f0b..e0c9444cb82 100644 --- a/core/primitives/src/action/mod.rs +++ b/core/primitives/src/action/mod.rs @@ -12,7 +12,7 @@ use serde_with::base64::Base64; use serde_with::serde_as; use std::fmt; -fn base64(s: &[u8]) -> String { +pub fn base64(s: &[u8]) -> String { use base64::Engine; base64::engine::general_purpose::STANDARD.encode(s) } @@ -121,7 +121,7 @@ impl fmt::Debug for DeployContractAction { pub struct DeployGlobalContractAction { /// WebAssembly binary #[serde_as(as = "Base64")] - pub code: Vec + pub code: Vec, } impl fmt::Debug for DeployGlobalContractAction { diff --git a/core/primitives/src/receipt.rs b/core/primitives/src/receipt.rs index e2ab0ab92b7..1f786d4ed82 100644 --- a/core/primitives/src/receipt.rs +++ b/core/primitives/src/receipt.rs @@ -571,6 +571,15 @@ impl Receipt { }), } } + + pub fn new_global_contract_distribution(predecessor_id: AccountId, code: Vec) -> Self { + Self::V0(ReceiptV0 { + predecessor_id, + receiver_id: "system".parse().unwrap(), + receipt_id: CryptoHash::default(), + receipt: ReceiptEnum::GlobalContractDitribution(GlobalContractData { code: code }), + }) + } } /// Receipt could be either ActionReceipt or DataReceipt @@ -590,6 +599,7 @@ pub enum ReceiptEnum { Data(DataReceipt), PromiseYield(ActionReceipt), PromiseResume(DataReceipt), + GlobalContractDitribution(GlobalContractData), } /// ActionReceipt is derived from an Action from `Transaction or from Receipt` @@ -670,6 +680,31 @@ impl fmt::Debug for ReceivedData { } } +#[serde_as] +#[derive( + BorshSerialize, + BorshDeserialize, + Hash, + PartialEq, + Eq, + Clone, + serde::Serialize, + serde::Deserialize, + ProtocolSchema, +)] +pub struct GlobalContractData { + #[serde_as(as = "Base64")] + pub code: Vec, +} + +impl fmt::Debug for GlobalContractData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("GlobalContractData") + //.field("code", &format_args!("{}", base64(&self.code))) + .finish() + } +} + /// Stores indices for a persistent queue for delayed receipts that didn't fit into a block. #[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)] pub struct DelayedReceiptIndices { diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index 945abde31a3..6657853dfe9 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -1954,6 +1954,7 @@ impl From for ReceiptView { is_promise_resume, } } + ReceiptEnum::GlobalContractDitribution(_) => todo!("#12639"), }, priority, } diff --git a/core/store/src/genesis/state_applier.rs b/core/store/src/genesis/state_applier.rs index 4f0475b8e24..c5f8b8b53d0 100644 --- a/core/store/src/genesis/state_applier.rs +++ b/core/store/src/genesis/state_applier.rs @@ -314,7 +314,9 @@ impl GenesisStateApplier { set_promise_yield_receipt(state_update, &receipt); }); } - ReceiptEnum::Data(_) | ReceiptEnum::PromiseResume(_) => { + ReceiptEnum::Data(_) + | ReceiptEnum::PromiseResume(_) + | ReceiptEnum::GlobalContractDitribution(_) => { panic!("Expected action receipt") } } diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index 85370122e2a..18b8ae60d8a 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -655,11 +655,15 @@ pub(crate) fn action_deploy_contract( } pub(crate) fn action_deploy_global_contract( - _state_update: &mut TrieUpdate, - _account: &mut Account, - _account_id: &AccountId, - _deploy_contract: &DeployGlobalContractAction, + account_id: &AccountId, + deploy_contract: &DeployGlobalContractAction, + result: &mut ActionResult, ) -> Result<(), StorageError> { + let _span = tracing::debug_span!(target: "runtime", "action_deploy_global_contract").entered(); + result.new_receipts.push(Receipt::new_global_contract_distribution( + account_id.clone(), + deploy_contract.code.clone(), + )); Ok(()) } @@ -909,7 +913,9 @@ fn receipt_required_gas(apply_state: &ApplyState, receipt: &Receipt) -> Result 0, + ReceiptEnum::Data(_) + | ReceiptEnum::PromiseResume(_) + | ReceiptEnum::GlobalContractDitribution(_) => 0, }) } @@ -1035,7 +1041,7 @@ pub(crate) fn check_actor_permissions( account_id: &AccountId, ) -> Result<(), ActionError> { match action { - Action::DeployContract(_) + Action::DeployContract(_) | Action::DeployGlobalContract(_) | Action::Stake(_) | Action::AddKey(_) diff --git a/runtime/runtime/src/balance_checker.rs b/runtime/runtime/src/balance_checker.rs index 3a622df947b..f70579a2268 100644 --- a/runtime/runtime/src/balance_checker.rs +++ b/runtime/runtime/src/balance_checker.rs @@ -66,7 +66,9 @@ fn receipt_cost( } total_cost } - ReceiptEnum::Data(_) | ReceiptEnum::PromiseResume(_) => 0, + ReceiptEnum::Data(_) + | ReceiptEnum::PromiseResume(_) + | ReceiptEnum::GlobalContractDitribution(_) => 0, }) } @@ -259,6 +261,10 @@ fn potential_postponed_receipt_ids( account_id.clone(), data_receipt.data_id, ))), + ReceiptEnum::GlobalContractDitribution(_) => { + // TODO(#12639): handle global contract distribution + None + } } }) .collect::, StorageError>>() diff --git a/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index e7ccca52cfc..ad4d0aa5bf8 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -88,7 +88,7 @@ pub fn total_send_fees( + fees.fee(ActionCosts::deploy_contract_byte).send_fee(sender_is_receiver) * num_bytes } - DeployGlobalContract(DeployGlobalContractAction{ code }) => { + DeployGlobalContract(DeployGlobalContractAction { code }) => { let num_bytes = code.len() as u64; // TODO(#12639): introduce separate fees for global contracts fees.fee(ActionCosts::deploy_contract_base).send_fee(sender_is_receiver) diff --git a/runtime/runtime/src/congestion_control.rs b/runtime/runtime/src/congestion_control.rs index 5427f83cee1..8cb54910215 100644 --- a/runtime/runtime/src/congestion_control.rs +++ b/runtime/runtime/src/congestion_control.rs @@ -611,6 +611,7 @@ pub(crate) fn compute_receipt_congestion_gas( // of it without expensive state lookups. Ok(0) } + ReceiptEnum::GlobalContractDitribution(_) => Ok(0), } } diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index ece65379a7f..931ff3ff09b 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -461,12 +461,7 @@ impl Runtime { )?; } Action::DeployGlobalContract(deploy_global_contract) => { - action_deploy_global_contract( - state_update, - account.as_mut().expect(EXPECT_ACCOUNT_EXISTS), - account_id, - deploy_global_contract, - )?; + action_deploy_global_contract(account_id, deploy_global_contract, &mut result)?; } Action::FunctionCall(function_call) => { let account = account.as_mut().expect(EXPECT_ACCOUNT_EXISTS); @@ -1208,6 +1203,10 @@ impl Runtime { return Ok(None); } } + ReceiptEnum::GlobalContractDitribution(_global_contract_data) => { + // TODO(#12639): save global contract to the state + return Ok(None); + } }; // We didn't trigger execution, so we need to commit the state. state_update @@ -2684,6 +2683,9 @@ fn schedule_contract_preparation<'b, R: MaybeRefReceipt>( }; return handle_receipt(mgr, state_update, receiver, account_id, &yr); } + ReceiptEnum::GlobalContractDitribution(_) => { + return false; + } } } handle_receipt(pipeline_manager, state_update, &receiver, account_id, peek) diff --git a/runtime/runtime/src/pipelining.rs b/runtime/runtime/src/pipelining.rs index 690e66a5665..7828a8cf1b6 100644 --- a/runtime/runtime/src/pipelining.rs +++ b/runtime/runtime/src/pipelining.rs @@ -120,7 +120,9 @@ impl ReceiptPreparationPipeline { } let actions = match receipt.receipt() { ReceiptEnum::Action(a) | ReceiptEnum::PromiseYield(a) => &a.actions, - ReceiptEnum::Data(_) | ReceiptEnum::PromiseResume(_) => return false, + ReceiptEnum::Data(_) + | ReceiptEnum::PromiseResume(_) + | ReceiptEnum::GlobalContractDitribution(_) => return false, }; let mut any_function_calls = false; for (action_index, action) in actions.iter().enumerate() { @@ -219,7 +221,9 @@ impl ReceiptPreparationPipeline { .actions .get(action_index) .expect("indexing receipt actions by an action_index failed!"), - ReceiptEnum::Data(_) | ReceiptEnum::PromiseResume(_) => { + ReceiptEnum::Data(_) + | ReceiptEnum::PromiseResume(_) + | ReceiptEnum::GlobalContractDitribution(_) => { panic!("attempting to get_contract with a non-action receipt!?") } }; diff --git a/runtime/runtime/src/prefetch.rs b/runtime/runtime/src/prefetch.rs index ef44616fa8e..233cf8fc669 100644 --- a/runtime/runtime/src/prefetch.rs +++ b/runtime/runtime/src/prefetch.rs @@ -97,7 +97,9 @@ impl TriePrefetcher { ReceiptEnum::Action(action_receipt) | ReceiptEnum::PromiseYield(action_receipt) => { action_receipt } - ReceiptEnum::Data(_) | ReceiptEnum::PromiseResume(_) => { + ReceiptEnum::Data(_) + | ReceiptEnum::PromiseResume(_) + | ReceiptEnum::GlobalContractDitribution(_) => { continue; } }; diff --git a/runtime/runtime/src/verifier.rs b/runtime/runtime/src/verifier.rs index a9e72411bd2..039621b52d3 100644 --- a/runtime/runtime/src/verifier.rs +++ b/runtime/runtime/src/verifier.rs @@ -10,7 +10,9 @@ use near_primitives::checked_feature; use near_primitives::errors::{ ActionsValidationError, InvalidAccessKeyError, InvalidTxError, ReceiptValidationError, }; -use near_primitives::receipt::{ActionReceipt, DataReceipt, Receipt, ReceiptEnum}; +use near_primitives::receipt::{ + ActionReceipt, DataReceipt, GlobalContractData, Receipt, ReceiptEnum, +}; use near_primitives::transaction::DeleteAccountAction; use near_primitives::transaction::{ Action, AddKeyAction, DeployContractAction, FunctionCallAction, SignedTransaction, StakeAction, @@ -323,6 +325,9 @@ pub(crate) fn validate_receipt( ReceiptEnum::Data(data_receipt) | ReceiptEnum::PromiseResume(data_receipt) => { validate_data_receipt(limit_config, data_receipt) } + ReceiptEnum::GlobalContractDitribution(data) => { + validate_global_contract_distribution_receipt(limit_config, data) + } } } @@ -357,6 +362,23 @@ fn validate_data_receipt( Ok(()) } +fn validate_global_contract_distribution_receipt( + _limit_config: &LimitConfig, + _data: &GlobalContractData, +) -> Result<(), ReceiptValidationError> { + /* + TODO(#12639): maybe validate size here + if data.code.len() as u64 > limit_config.max_contract_size { + return Err(ReceiptValidationError::ContractSizeExceeded { + size: data.code.len() as u64, + limit: limit_config.max_contract_size, + }); + } + */ + + Ok(()) +} + /// Validates given actions: /// /// - Checks limits if applicable. diff --git a/tools/state-viewer/src/contract_accounts.rs b/tools/state-viewer/src/contract_accounts.rs index 115361aad8c..0dae670e114 100644 --- a/tools/state-viewer/src/contract_accounts.rs +++ b/tools/state-viewer/src/contract_accounts.rs @@ -349,7 +349,9 @@ fn try_find_actions_spawned_by_receipt( Action::DeleteKey(_) => ActionType::DeleteKey, Action::DeleteAccount(_) => ActionType::DeleteAccount, Action::Delegate(_) => ActionType::Delegate, - Action::DeployGlobalContract(_) => ActionType::DeployGlobalContract, + Action::DeployGlobalContract(_) => { + ActionType::DeployGlobalContract + } }; entry .actions @@ -363,6 +365,7 @@ fn try_find_actions_spawned_by_receipt( .get_or_insert_with(Default::default) .insert(ActionType::DataReceipt); } + ReceiptEnum::GlobalContractDitribution(_) => {} } } } From d477677215e1dcbaa9a8bb08fc4683eb8c3f944a Mon Sep 17 00:00:00 2001 From: Anton Puhach Date: Fri, 3 Jan 2025 13:36:36 +0100 Subject: [PATCH 3/6] Send to all shards & add trie key col --- chain/chain/src/chain.rs | 35 +++++++++++++++++++++++--------- chain/chain/src/runtime/tests.rs | 10 +++++++-- chain/chain/src/store/mod.rs | 7 ++++--- chain/chain/src/test_utils.rs | 3 ++- core/primitives/src/receipt.rs | 4 ++++ core/primitives/src/trie_key.rs | 15 +++++++++++++- core/primitives/src/types.rs | 1 + 7 files changed, 58 insertions(+), 17 deletions(-) diff --git a/chain/chain/src/chain.rs b/chain/chain/src/chain.rs index b8029caa135..88a7c480ac9 100644 --- a/chain/chain/src/chain.rs +++ b/chain/chain/src/chain.rs @@ -4427,9 +4427,15 @@ impl Chain { ) -> HashMap> { let mut result = HashMap::new(); for receipt in receipts { - let shard_id = shard_layout.account_id_to_shard_id(receipt.receiver_id()); - let entry = result.entry(shard_id).or_insert_with(Vec::new); - entry.push(receipt) + if receipt.send_to_all_shards() { + for shard_id in shard_layout.shard_ids() { + result.entry(shard_id).or_insert_with(Vec::new).push(receipt.clone()); + } + } else { + let shard_id = shard_layout.account_id_to_shard_id(receipt.receiver_id()); + let entry = result.entry(shard_id).or_insert_with(Vec::new); + entry.push(receipt); + } } result } @@ -4450,13 +4456,22 @@ impl Chain { } let mut cache = HashMap::new(); for receipt in receipts { - let &mut shard_id = cache - .entry(receipt.receiver_id()) - .or_insert_with(|| shard_layout.account_id_to_shard_id(receipt.receiver_id())); - // This unwrap should be safe as we pre-populated the map with all - // valid shard ids. - let shard_index = shard_layout.get_shard_index(shard_id).unwrap(); - result_map.get_mut(&shard_index).unwrap().1.push(receipt); + if receipt.send_to_all_shards() { + for shard_id in shard_layout.shard_ids() { + // This unwrap should be safe as we pre-populated the map with all + // valid shard ids. + let shard_index = shard_layout.get_shard_index(shard_id).unwrap(); + result_map.get_mut(&shard_index).unwrap().1.push(receipt); + } + } else { + let &mut shard_id = cache + .entry(receipt.receiver_id()) + .or_insert_with(|| shard_layout.account_id_to_shard_id(receipt.receiver_id())); + // This unwrap should be safe as we pre-populated the map with all + // valid shard ids. + let shard_index = shard_layout.get_shard_index(shard_id).unwrap(); + result_map.get_mut(&shard_index).unwrap().1.push(receipt); + }; } let mut result_vec = vec![]; diff --git a/chain/chain/src/runtime/tests.rs b/chain/chain/src/runtime/tests.rs index a5e75ca546c..5c9e316efc9 100644 --- a/chain/chain/src/runtime/tests.rs +++ b/chain/chain/src/runtime/tests.rs @@ -372,8 +372,14 @@ impl TestEnv { let shard_layout = self.epoch_manager.get_shard_layout_from_prev_block(&new_hash).unwrap(); let mut new_receipts = HashMap::<_, Vec>::new(); for receipt in all_receipts { - let shard_id = shard_layout.account_id_to_shard_id(receipt.receiver_id()); - new_receipts.entry(shard_id).or_default().push(receipt); + if receipt.send_to_all_shards() { + for shard_id in shard_layout.shard_ids() { + new_receipts.entry(shard_id).or_default().push(receipt.clone()); + } + } else { + let shard_id = shard_layout.account_id_to_shard_id(receipt.receiver_id()); + new_receipts.entry(shard_id).or_default().push(receipt); + } } self.last_receipts = new_receipts; self.last_proposals = all_proposals; diff --git a/chain/chain/src/store/mod.rs b/chain/chain/src/store/mod.rs index 0a59b46c640..21c2ac59153 100644 --- a/chain/chain/src/store/mod.rs +++ b/chain/chain/src/store/mod.rs @@ -387,9 +387,10 @@ pub fn filter_incoming_receipts_for_shard( let mut filtered_receipts = vec![]; let ReceiptProof(receipts, shard_proof) = receipt_proof.clone(); for receipt in receipts { - let receiver_shard_id = - target_shard_layout.account_id_to_shard_id(receipt.receiver_id()); - if receiver_shard_id == target_shard_id { + if receipt.send_to_all_shards() + || target_shard_layout.account_id_to_shard_id(receipt.receiver_id()) + == target_shard_id + { tracing::trace!(target: "chain", receipt_id=?receipt.receipt_id(), "including receipt"); filtered_receipts.push(receipt); } else { diff --git a/chain/chain/src/test_utils.rs b/chain/chain/src/test_utils.rs index d82e9ba549d..a1b65d2acc3 100644 --- a/chain/chain/src/test_utils.rs +++ b/chain/chain/src/test_utils.rs @@ -297,7 +297,8 @@ mod test { let shard_receipts: Vec = receipts .iter() .filter(|&receipt| { - shard_layout.account_id_to_shard_id(receipt.receiver_id()) == shard_id + receipt.send_to_all_shards() + || shard_layout.account_id_to_shard_id(receipt.receiver_id()) == shard_id }) .cloned() .collect(); diff --git a/core/primitives/src/receipt.rs b/core/primitives/src/receipt.rs index 1f786d4ed82..e7074db881e 100644 --- a/core/primitives/src/receipt.rs +++ b/core/primitives/src/receipt.rs @@ -485,6 +485,10 @@ impl Receipt { *self.receipt_id() } + pub fn send_to_all_shards(&self) -> bool { + matches!(self.receipt(), ReceiptEnum::GlobalContractDitribution(..)) + } + /// Generates a receipt with a transfer from system for a given balance without a receipt_id. /// This should be used for token refunds instead of gas refunds. It inherits priority from the parent receipt. /// It doesn't refund the allowance of the access key. For gas refunds use `new_gas_refund`. diff --git a/core/primitives/src/trie_key.rs b/core/primitives/src/trie_key.rs index 40104f2c718..98870a6407b 100644 --- a/core/primitives/src/trie_key.rs +++ b/core/primitives/src/trie_key.rs @@ -62,6 +62,7 @@ pub mod col { pub const BUFFERED_RECEIPT_GROUPS_QUEUE_DATA: u8 = 16; /// A single item of `ReceiptGroupsQueue`. Values are of type `ReceiptGroup`. pub const BUFFERED_RECEIPT_GROUPS_QUEUE_ITEM: u8 = 17; + pub const GLOBAL_CONTRACT_DATA: u8 = 18; /// All columns except those used for the delayed receipts queue, the yielded promises /// queue, and the outgoing receipts buffer, which are global state for the shard. @@ -77,7 +78,7 @@ pub mod col { (PROMISE_YIELD_RECEIPT, "PromiseYieldReceipt"), ]; - pub const ALL_COLUMNS_WITH_NAMES: [(u8, &'static str); 17] = [ + pub const ALL_COLUMNS_WITH_NAMES: [(u8, &'static str); 18] = [ (ACCOUNT, "Account"), (CONTRACT_CODE, "ContractCode"), (ACCESS_KEY, "AccessKey"), @@ -95,6 +96,7 @@ pub mod col { (BANDWIDTH_SCHEDULER_STATE, "BandwidthSchedulerState"), (BUFFERED_RECEIPT_GROUPS_QUEUE_DATA, "BufferedReceiptGroupsQueueData"), (BUFFERED_RECEIPT_GROUPS_QUEUE_ITEM, "BufferedReceiptGroupsQueueItem"), + (GLOBAL_CONTRACT_DATA, "GlobalContractData"), ]; } @@ -193,6 +195,9 @@ pub enum TrieKey { receiving_shard: ShardId, index: u64, }, + GlobalContractCode { + code_hash: CryptoHash, + }, } /// Provides `len` function. @@ -277,6 +282,9 @@ impl TrieKey { + std::mem::size_of::() + std::mem::size_of_val(index) } + TrieKey::GlobalContractCode { code_hash } => { + col::GLOBAL_CONTRACT_DATA.len() + code_hash.as_ref().len() + } } } @@ -370,6 +378,10 @@ impl TrieKey { buf.extend(&receiving_shard.to_le_bytes()); buf.extend(&index.to_le_bytes()); } + TrieKey::GlobalContractCode { code_hash } => { + buf.push(col::GLOBAL_CONTRACT_DATA); + buf.extend(code_hash.as_ref()); + }, }; debug_assert_eq!(expected_len, buf.len() - start_len); } @@ -401,6 +413,7 @@ impl TrieKey { TrieKey::BandwidthSchedulerState => None, TrieKey::BufferedReceiptGroupsQueueData { .. } => None, TrieKey::BufferedReceiptGroupsQueueItem { .. } => None, + TrieKey::GlobalContractCode { .. } => None, } } } diff --git a/core/primitives/src/types.rs b/core/primitives/src/types.rs index c636452b91d..a97f09ab383 100644 --- a/core/primitives/src/types.rs +++ b/core/primitives/src/types.rs @@ -360,6 +360,7 @@ impl StateChanges { TrieKey::BandwidthSchedulerState => {} TrieKey::BufferedReceiptGroupsQueueData { .. } => {} TrieKey::BufferedReceiptGroupsQueueItem { .. } => {} + TrieKey::GlobalContractCode { .. } => {} } } From 3e32a238bf11194a388f9c21873e0dcc551c6f99 Mon Sep 17 00:00:00 2001 From: Anton Puhach Date: Sat, 4 Jan 2025 18:07:00 +0100 Subject: [PATCH 4/6] impl apply_global_contract_distribution_receipt --- core/primitives/src/trie_key.rs | 2 +- runtime/runtime/src/lib.rs | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/core/primitives/src/trie_key.rs b/core/primitives/src/trie_key.rs index 98870a6407b..ce3de249158 100644 --- a/core/primitives/src/trie_key.rs +++ b/core/primitives/src/trie_key.rs @@ -381,7 +381,7 @@ impl TrieKey { TrieKey::GlobalContractCode { code_hash } => { buf.push(col::GLOBAL_CONTRACT_DATA); buf.extend(code_hash.as_ref()); - }, + } }; debug_assert_eq!(expected_len, buf.len() - start_len); } diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index 931ff3ff09b..c6cc2f6b439 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -931,6 +931,26 @@ impl Runtime { }) } + fn apply_global_contract_distribution_receipt( + &self, + receipt: &Receipt, + state_update: &mut TrieUpdate, + ) { + let _span = tracing::debug_span!( + target: "runtime", + "apply_global_contract_distribution_receipt", + ) + .entered(); + let ReceiptEnum::GlobalContractDitribution(global_contract_data) = receipt.receipt() else { + unreachable!("given receipt should be an global contract distribution receipt") + }; + let code_hash = CryptoHash::hash_bytes(&global_contract_data.code); + state_update + .set(TrieKey::GlobalContractCode { code_hash }, global_contract_data.code.clone()); + state_update + .commit(StateChangeCause::ReceiptProcessing { receipt_hash: receipt.get_hash() }); + } + fn generate_refund_receipts( &self, current_gas_price: Balance, @@ -1203,8 +1223,8 @@ impl Runtime { return Ok(None); } } - ReceiptEnum::GlobalContractDitribution(_global_contract_data) => { - // TODO(#12639): save global contract to the state + ReceiptEnum::GlobalContractDitribution(_) => { + self.apply_global_contract_distribution_receipt(receipt, state_update); return Ok(None); } }; From 1eed74b7a48db4a8094828fd20d4b753b389bc16 Mon Sep 17 00:00:00 2001 From: Anton Puhach Date: Sun, 5 Jan 2025 22:48:12 +0100 Subject: [PATCH 5/6] Add use global contract action --- chain/chain/src/flat_storage_resharder.rs | 3 +- chain/rosetta-rpc/src/adapters/mod.rs | 3 +- core/primitives/src/action/mod.rs | 18 ++++++++++ core/primitives/src/views.rs | 1 + core/store/src/trie/update.rs | 39 ++++++++++++--------- runtime/runtime/src/actions.rs | 27 ++++++++++++-- runtime/runtime/src/config.rs | 8 +++++ runtime/runtime/src/lib.rs | 7 ++++ runtime/runtime/src/pipelining.rs | 2 +- runtime/runtime/src/verifier.rs | 1 + tools/state-viewer/src/contract_accounts.rs | 2 ++ 11 files changed, 88 insertions(+), 23 deletions(-) diff --git a/chain/chain/src/flat_storage_resharder.rs b/chain/chain/src/flat_storage_resharder.rs index 83bd481f3fa..c5b61f6f92e 100644 --- a/chain/chain/src/flat_storage_resharder.rs +++ b/chain/chain/src/flat_storage_resharder.rs @@ -994,7 +994,8 @@ fn shard_split_handle_key_value( col::DELAYED_RECEIPT_OR_INDICES | col::PROMISE_YIELD_INDICES | col::PROMISE_YIELD_TIMEOUT - | col::BANDWIDTH_SCHEDULER_STATE => { + | col::BANDWIDTH_SCHEDULER_STATE + | col::GLOBAL_CONTRACT_DATA => { copy_kv_to_all_children(&split_params, key, value, store_update) } col::BUFFERED_RECEIPT_INDICES | col::BUFFERED_RECEIPT => { diff --git a/chain/rosetta-rpc/src/adapters/mod.rs b/chain/rosetta-rpc/src/adapters/mod.rs index 93349c247d0..5eff3a7d1b9 100644 --- a/chain/rosetta-rpc/src/adapters/mod.rs +++ b/chain/rosetta-rpc/src/adapters/mod.rs @@ -500,7 +500,8 @@ impl From for Vec { operations.extend(delegated_operations); } // TODO(#8469): Implement delegate action support, for now they are ignored. - near_primitives::action::Action::DeployGlobalContract(_) => { + near_primitives::action::Action::DeployGlobalContract(_) + | near_primitives::action::Action::UseGlobalContract(_) => { // TODO(#12639): Implement global contracts support, ignored for now } } diff --git a/core/primitives/src/action/mod.rs b/core/primitives/src/action/mod.rs index e0c9444cb82..143d6d68a94 100644 --- a/core/primitives/src/action/mod.rs +++ b/core/primitives/src/action/mod.rs @@ -4,6 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use near_crypto::PublicKey; use near_primitives_core::{ account::AccessKey, + hash::CryptoHash, serialize::dec_format, types::{AccountId, Balance, Gas}, }; @@ -132,6 +133,22 @@ impl fmt::Debug for DeployGlobalContractAction { } } +#[serde_as] +#[derive( + BorshSerialize, + BorshDeserialize, + serde::Serialize, + serde::Deserialize, + PartialEq, + Eq, + Clone, + ProtocolSchema, + Debug, +)] +pub struct UseGlobalContractAction { + pub global_code_hash: CryptoHash, +} + #[serde_as] #[derive( BorshSerialize, @@ -243,6 +260,7 @@ pub enum Action { DeleteAccount(DeleteAccountAction), Delegate(Box), DeployGlobalContract(DeployGlobalContractAction), + UseGlobalContract(Box), #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] /// Makes a non-refundable transfer for storage allowance. /// Only possible during new account creation. diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index 6657853dfe9..8aa845beece 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -1207,6 +1207,7 @@ impl From for ActionView { signature: action.signature, }, Action::DeployGlobalContract(_) => todo!(), + Action::UseGlobalContract(_) => todo!(), } } } diff --git a/core/store/src/trie/update.rs b/core/store/src/trie/update.rs index e2de65a3991..58374b012cb 100644 --- a/core/store/src/trie/update.rs +++ b/core/store/src/trie/update.rs @@ -336,23 +336,28 @@ impl TrieUpdate { if code_hash == CryptoHash::default() { return Ok(()); } - let trie_key = TrieKey::ContractCode { account_id }; - let contract_ref = self - .trie - .get_optimized_ref_no_side_effects(&trie_key.to_vec(), KeyLookupMode::FlatStorage) - .or_else(|err| { - // If the value for the trie key is not found, we treat it as if the contract does not exist. - // In this case, we ignore the error and skip recording the contract call below. - if matches!(err, StorageError::MissingTrieValue(_, _)) { - Ok(None) - } else { - Err(err) - } - })?; - let contract_exists = - contract_ref.is_some_and(|value_ref| value_ref.value_hash() == code_hash); - if contract_exists { - self.contract_storage.record_call(code_hash); + // ugly hack here + for trie_key in + [TrieKey::ContractCode { account_id }, TrieKey::GlobalContractCode { code_hash }] + { + let contract_ref = self + .trie + .get_optimized_ref_no_side_effects(&trie_key.to_vec(), KeyLookupMode::FlatStorage) + .or_else(|err| { + // If the value for the trie key is not found, we treat it as if the contract does not exist. + // In this case, we ignore the error and skip recording the contract call below. + if matches!(err, StorageError::MissingTrieValue(_, _)) { + Ok(None) + } else { + Err(err) + } + })?; + let contract_exists = + contract_ref.is_some_and(|value_ref| value_ref.value_hash() == code_hash); + if contract_exists { + self.contract_storage.record_call(code_hash); + break; + } } Ok(()) } diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index 18b8ae60d8a..048e221fc31 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -9,7 +9,7 @@ use near_crypto::PublicKey; use near_parameters::{AccountCreationConfig, ActionCosts, RuntimeConfig, RuntimeFeesConfig}; use near_primitives::account::{AccessKey, AccessKeyPermission, Account}; use near_primitives::action::delegate::{DelegateAction, SignedDelegateAction}; -use near_primitives::action::DeployGlobalContractAction; +use near_primitives::action::{DeployGlobalContractAction, UseGlobalContractAction}; use near_primitives::checked_feature; use near_primitives::config::ViewConfig; use near_primitives::errors::{ActionError, ActionErrorKind, InvalidAccessKeyError, RuntimeError}; @@ -894,6 +894,25 @@ pub(crate) fn apply_delegate_action( Ok(()) } +pub(crate) fn action_use_global_contract( + state_update: &mut TrieUpdate, + account: &mut Account, + action: &UseGlobalContractAction, +) -> Result<(), RuntimeError> { + if !state_update.contains_key(&near_primitives::trie_key::TrieKey::GlobalContractCode { + code_hash: action.global_code_hash, + })? { + // TODO: error instead of panic + panic!("Global contract does not exist"); + } + if account.code_hash() != CryptoHash::default() { + todo!("update storage when non-global contract was previously used") + } + // TODO: change Account struct to natively support global contracts + account.set_code_hash(action.global_code_hash); + Ok(()) +} + /// Returns Gas amount is required to execute Receipt and all actions it contains fn receipt_required_gas(apply_state: &ApplyState, receipt: &Receipt) -> Result { Ok(match receipt.receipt() { @@ -1045,6 +1064,7 @@ pub(crate) fn check_actor_permissions( | Action::DeployGlobalContract(_) | Action::Stake(_) | Action::AddKey(_) + | Action::UseGlobalContract(_) | Action::DeleteKey(_) => { if actor_id != account_id { return Err(ActionErrorKind::ActorNoPermission { @@ -1152,13 +1172,14 @@ pub(crate) fn check_account_existence( } } Action::DeployContract(_) - | Action::DeployGlobalContract(_) | Action::FunctionCall(_) | Action::Stake(_) | Action::AddKey(_) | Action::DeleteKey(_) | Action::DeleteAccount(_) - | Action::Delegate(_) => { + | Action::Delegate(_) + | Action::DeployGlobalContract(_) + | Action::UseGlobalContract(_) => { if account.is_none() { return Err(ActionErrorKind::AccountDoesNotExist { account_id: account_id.clone(), diff --git a/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index ad4d0aa5bf8..cb1193f2db0 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -158,6 +158,10 @@ pub fn total_send_fees( &delegate_action.receiver_id, )? } + UseGlobalContract(_) => { + // TODO(#12639): properly introduce fees for global contracts + 1 + } }; result = safe_add_gas(result, delta)?; } @@ -255,6 +259,10 @@ pub fn exec_fee(config: &RuntimeConfig, action: &Action, receiver_id: &AccountId DeleteKey(_) => fees.fee(ActionCosts::delete_key).exec_fee(), DeleteAccount(_) => fees.fee(ActionCosts::delete_account).exec_fee(), Delegate(_) => fees.fee(ActionCosts::delegate).exec_fee(), + UseGlobalContract(_) => { + // TODO(#12639): properly introduce fees for global contracts + 1 + } } } diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index c6cc2f6b439..20079f2a8a1 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -575,6 +575,13 @@ impl Runtime { receipt.priority(), )?; } + Action::UseGlobalContract(use_global_contract_action) => { + action_use_global_contract( + state_update, + account.as_mut().expect(EXPECT_ACCOUNT_EXISTS), + use_global_contract_action, + )?; + } }; Ok(result) } diff --git a/runtime/runtime/src/pipelining.rs b/runtime/runtime/src/pipelining.rs index 7828a8cf1b6..fe5871e3053 100644 --- a/runtime/runtime/src/pipelining.rs +++ b/runtime/runtime/src/pipelining.rs @@ -128,7 +128,7 @@ impl ReceiptPreparationPipeline { for (action_index, action) in actions.iter().enumerate() { let account_id = account_id.clone(); match action { - Action::DeployContract(_) => { + Action::DeployContract(_) | Action::UseGlobalContract(_) => { // FIXME: instead of blocking these accounts, move the handling of // deploy action into here, so that the necessary data dependencies can be // established. diff --git a/runtime/runtime/src/verifier.rs b/runtime/runtime/src/verifier.rs index 039621b52d3..8156cf56845 100644 --- a/runtime/runtime/src/verifier.rs +++ b/runtime/runtime/src/verifier.rs @@ -455,6 +455,7 @@ pub fn validate_action( Action::DeleteKey(_) => Ok(()), Action::DeleteAccount(a) => validate_delete_action(a), Action::Delegate(a) => validate_delegate_action(limit_config, a, current_protocol_version), + Action::UseGlobalContract(_) => Ok(()), } } diff --git a/tools/state-viewer/src/contract_accounts.rs b/tools/state-viewer/src/contract_accounts.rs index 0dae670e114..02a6899f370 100644 --- a/tools/state-viewer/src/contract_accounts.rs +++ b/tools/state-viewer/src/contract_accounts.rs @@ -137,6 +137,7 @@ pub(crate) enum ActionType { DataReceipt, Delegate, DeployGlobalContract, + UseGlobalContract, } impl ContractAccount { @@ -352,6 +353,7 @@ fn try_find_actions_spawned_by_receipt( Action::DeployGlobalContract(_) => { ActionType::DeployGlobalContract } + Action::UseGlobalContract(_) => ActionType::UseGlobalContract, }; entry .actions From b1dd6ba0e5186d8ad285bb1c0b17847a60c17dd8 Mon Sep 17 00:00:00 2001 From: Anton Puhach Date: Fri, 10 Jan 2025 17:45:21 +0100 Subject: [PATCH 6/6] add test_global_contracts --- core/primitives/src/views.rs | 22 +++++++- .../contract_distribution_cross_shard.rs | 51 ++++++++++++++++- .../src/test_loop/utils/transactions.rs | 55 +++++++++++++++++++ 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index 8aa845beece..e40992ca984 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -5,6 +5,7 @@ //! from the source structure in the relevant `From` impl. use crate::account::{AccessKey, AccessKeyPermission, Account, FunctionCallPermission}; use crate::action::delegate::{DelegateAction, SignedDelegateAction}; +use crate::action::{DeployGlobalContractAction, UseGlobalContractAction}; use crate::bandwidth_scheduler::BandwidthRequests; use crate::block::{Block, BlockHeader, Tip}; use crate::block_header::BlockHeaderInnerLite; @@ -1170,6 +1171,13 @@ pub enum ActionView { delegate_action: DelegateAction, signature: Signature, }, + DeployGlobalContract { + #[serde_as(as = "Base64")] + code: Vec, + }, + UseGlobalContract { + global_code_hash: CryptoHash, + }, } impl From for ActionView { @@ -1206,8 +1214,12 @@ impl From for ActionView { delegate_action: action.delegate_action, signature: action.signature, }, - Action::DeployGlobalContract(_) => todo!(), - Action::UseGlobalContract(_) => todo!(), + Action::DeployGlobalContract(action) => { + ActionView::DeployGlobalContract { code: action.code } + } + Action::UseGlobalContract(action) => { + ActionView::UseGlobalContract { global_code_hash: action.global_code_hash } + } } } } @@ -1249,6 +1261,12 @@ impl TryFrom for Action { ActionView::Delegate { delegate_action, signature } => { Action::Delegate(Box::new(SignedDelegateAction { delegate_action, signature })) } + ActionView::DeployGlobalContract { code } => { + Action::DeployGlobalContract(DeployGlobalContractAction { code }) + } + ActionView::UseGlobalContract { global_code_hash } => { + Action::UseGlobalContract(Box::new(UseGlobalContractAction { global_code_hash })) + } }) } } diff --git a/integration-tests/src/test_loop/tests/contract_distribution_cross_shard.rs b/integration-tests/src/test_loop/tests/contract_distribution_cross_shard.rs index ae37fabd066..a6028d7a130 100644 --- a/integration-tests/src/test_loop/tests/contract_distribution_cross_shard.rs +++ b/integration-tests/src/test_loop/tests/contract_distribution_cross_shard.rs @@ -4,6 +4,7 @@ use near_chain_configs::test_genesis::{ build_genesis_and_epoch_config_store, GenesisAndEpochConfigParams, ValidatorsSpec, }; use near_o11y::testonly::init_test_logger; +use near_primitives::hash::CryptoHash; use near_primitives::shard_layout::ShardLayout; use near_primitives::types::AccountId; use near_primitives::version::PROTOCOL_VERSION; @@ -17,7 +18,8 @@ use crate::test_loop::utils::contract_distribution::{ }; use crate::test_loop::utils::get_head_height; use crate::test_loop::utils::transactions::{ - call_contract, check_txs, deploy_contract, make_accounts, + call_contract, check_txs, deploy_contract, deploy_global_contract, make_accounts, + use_global_contract, }; const EPOCH_LENGTH: u64 = 10; @@ -29,6 +31,53 @@ const NUM_RPC: usize = 1; const NUM_VALIDATORS: usize = NUM_BLOCK_AND_CHUNK_PRODUCERS + NUM_CHUNK_VALIDATORS_ONLY; const NUM_ACCOUNTS: usize = NUM_VALIDATORS + NUM_RPC; +#[test] +fn test_global_contracts() { + init_test_logger(); + let accounts = make_accounts(NUM_ACCOUNTS); + + let (mut env, rpc_id) = setup(&accounts); + + let mut nonce = 1; + let rpc_index = 8; + assert_eq!(accounts[rpc_index], rpc_id); + + let contract = ContractCode::new(near_test_contracts::rs_contract().to_vec(), None); + let deploy_tx = deploy_global_contract( + &mut env.test_loop, + &env.datas, + &rpc_id, + &accounts[0], + contract.code().to_vec(), + nonce, + ); + nonce += 1; + env.test_loop.run_for(Duration::seconds(3)); + check_txs(&env.test_loop, &env.datas, &rpc_id, &[deploy_tx]); + let code_hash = CryptoHash::hash_bytes(contract.code()); + // test on accounts from different shards + for account in [&accounts[1], &accounts[6]] { + let use_tx = + use_global_contract(&mut env.test_loop, &env.datas, &rpc_id, account, code_hash, nonce); + nonce += 1; + env.test_loop.run_for(Duration::seconds(3)); + check_txs(&env.test_loop, &env.datas, &rpc_id, &[use_tx]); + let call_tx = call_contract( + &mut env.test_loop, + &env.datas, + &rpc_id, + account, + account, + "log_something".to_owned(), + vec![], + nonce, + ); + env.test_loop.run_for(Duration::seconds(3)); + check_txs(&env.test_loop, &env.datas, &rpc_id, &[call_tx]); + } + env.shutdown_and_drain_remaining_events(Duration::seconds(20)); +} + /// Tests a scenario that different contracts are deployed to a number of accounts and /// these contracts are called from a set of accounts. /// Test setup: 2 shards with 9 accounts, for 8 validators and 1 RPC node. diff --git a/integration-tests/src/test_loop/utils/transactions.rs b/integration-tests/src/test_loop/utils/transactions.rs index 9dda074d95f..f9f67190eb8 100644 --- a/integration-tests/src/test_loop/utils/transactions.rs +++ b/integration-tests/src/test_loop/utils/transactions.rs @@ -12,6 +12,7 @@ use near_client::test_utils::test_loop::ClientQueries; use near_client::{Client, ProcessTxResponse}; use near_crypto::Signer; use near_network::client::ProcessTxRequest; +use near_primitives::action::{Action, DeployGlobalContractAction, UseGlobalContractAction}; use near_primitives::block::Tip; use near_primitives::errors::InvalidTxError; use near_primitives::hash::CryptoHash; @@ -301,6 +302,60 @@ pub fn deploy_contract( tx_hash } +pub fn deploy_global_contract( + test_loop: &mut TestLoopV2, + node_datas: &[TestData], + rpc_id: &AccountId, + account_id: &AccountId, + code: Vec, + nonce: u64, +) -> CryptoHash { + let block_hash = get_shared_block_hash(node_datas, test_loop); + let signer = create_user_test_signer(&account_id); + + let tx = SignedTransaction::from_actions( + nonce, + account_id.clone(), + account_id.clone(), + &signer, + vec![Action::DeployGlobalContract(DeployGlobalContractAction { code })], + block_hash, + 0, + ); + let tx_hash = tx.get_hash(); + submit_tx(node_datas, rpc_id, tx); + + tracing::debug!(target: "test", ?account_id, ?tx_hash, "deployed global contract"); + tx_hash +} + +pub fn use_global_contract( + test_loop: &mut TestLoopV2, + node_datas: &[TestData], + rpc_id: &AccountId, + account_id: &AccountId, + global_code_hash: CryptoHash, + nonce: u64, +) -> CryptoHash { + let block_hash = get_shared_block_hash(node_datas, test_loop); + let signer = create_user_test_signer(&account_id); + + let tx = SignedTransaction::from_actions( + nonce, + account_id.clone(), + account_id.clone(), + &signer, + vec![Action::UseGlobalContract(Box::new(UseGlobalContractAction { global_code_hash }))], + block_hash, + 0, + ); + let tx_hash = tx.get_hash(); + submit_tx(node_datas, rpc_id, tx); + + tracing::debug!(target: "test", ?account_id, ?tx_hash, "used global contract"); + tx_hash +} + /// Call the contract deployed at contract id from the sender id. /// /// This function does not wait until the transactions is executed.