From 5ae37890d6642dcec34f3ba52a961574c2384556 Mon Sep 17 00:00:00 2001 From: Anton Puhach Date: Thu, 16 Jan 2025 12:56:41 +0100 Subject: [PATCH] feat: add receipt action for deploying global contract code (#12737) This PR includes mostly wiring, actual implementation will be added separately to make it easier to review. I explicitly don't want to introduce compile time feature for this as it makes code much harder to work with. `validate_action` makes sure that newly added action cannot be used before stabilisation. Part of #12715. --- chain/rosetta-rpc/src/adapters/mod.rs | 3 + core/primitives-core/src/version.rs | 2 + core/primitives/src/action/mod.rs | 60 +++++++++++++ core/primitives/src/views.rs | 30 +++++++ runtime/runtime/src/actions.rs | 20 ++++- runtime/runtime/src/config.rs | 8 ++ runtime/runtime/src/lib.rs | 3 + runtime/runtime/src/pipelining.rs | 3 +- runtime/runtime/src/verifier.rs | 26 ++++++ .../res/protocol_schema.toml | 88 ++++++++++--------- tools/state-viewer/src/contract_accounts.rs | 4 + 11 files changed, 201 insertions(+), 46 deletions(-) diff --git a/chain/rosetta-rpc/src/adapters/mod.rs b/chain/rosetta-rpc/src/adapters/mod.rs index 5437430499c..78335ae6949 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::transaction::Action::DeployGlobalContract(_action) => { + // TODO(#12639): Implement global contract deploys support, ignored for now. + } } } operations diff --git a/core/primitives-core/src/version.rs b/core/primitives-core/src/version.rs index 4fe631c9fc4..ed69983aa03 100644 --- a/core/primitives-core/src/version.rs +++ b/core/primitives-core/src/version.rs @@ -196,6 +196,7 @@ pub enum ProtocolFeature { ExcludeExistingCodeFromWitnessForCodeLen, /// Use the block height instead of the block hash to calculate the receipt ID. BlockHeightForReceiptId, + GlobalContracts, } impl ProtocolFeature { @@ -286,6 +287,7 @@ impl ProtocolFeature { ProtocolFeature::ExcludeExistingCodeFromWitnessForCodeLen => 148, ProtocolFeature::BlockHeightForReceiptId => 149, // Place features that are not yet in Nightly below this line. + ProtocolFeature::GlobalContracts => 200, } } diff --git a/core/primitives/src/action/mod.rs b/core/primitives/src/action/mod.rs index c54e367d66d..a250f7465d1 100644 --- a/core/primitives/src/action/mod.rs +++ b/core/primitives/src/action/mod.rs @@ -106,6 +106,59 @@ impl fmt::Debug for DeployContractAction { } } +#[serde_as] +#[derive( + BorshSerialize, + BorshDeserialize, + serde::Serialize, + serde::Deserialize, + PartialEq, + Eq, + Clone, + ProtocolSchema, + Debug, +)] +#[repr(u8)] +pub enum GlobalContractDeployMode { + /// Contract is deployed under its code hash. + /// Users will be able reference it by that hash. + /// This effectively makes the contract immutable. + CodeHash, + /// Contract is deployed under the onwer account id. + /// Users will be able reference it by that account id. + /// This allows the owner to update the contract for all its users. + AccountId, +} + +/// 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, + + pub deploy_mode: GlobalContractDeployMode, +} + +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))) + .field("deploy_mode", &format_args!("{:?}", &self.deploy_mode)) + .finish() + } +} + #[serde_as] #[derive( BorshSerialize, @@ -216,6 +269,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. @@ -261,6 +315,12 @@ impl From for Action { } } +impl From for Action { + fn from(deploy_global_contract_action: DeployGlobalContractAction) -> Self { + Self::DeployGlobalContract(deploy_global_contract_action) + } +} + impl From for Action { fn from(function_call_action: FunctionCallAction) -> Self { Self::FunctionCall(Box::new(function_call_action)) diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index c82f623cfd8..bc247fe1c95 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, GlobalContractDeployMode}; use crate::bandwidth_scheduler::BandwidthRequests; use crate::block::{Block, BlockHeader, Tip}; use crate::block_header::BlockHeaderInnerLite; @@ -1170,6 +1171,14 @@ pub enum ActionView { delegate_action: DelegateAction, signature: Signature, }, + DeployGlobalContract { + #[serde_as(as = "Base64")] + code: Vec, + }, + DeployGlobalContractByAccountId { + #[serde_as(as = "Base64")] + code: Vec, + }, } impl From for ActionView { @@ -1206,6 +1215,15 @@ impl From for ActionView { delegate_action: action.delegate_action, signature: action.signature, }, + Action::DeployGlobalContract(action) => { + let code = hash(&action.code).as_ref().to_vec(); + match action.deploy_mode { + GlobalContractDeployMode::CodeHash => ActionView::DeployGlobalContract { code }, + GlobalContractDeployMode::AccountId => { + ActionView::DeployGlobalContractByAccountId { code } + } + } + } } } } @@ -1247,6 +1265,18 @@ impl TryFrom for Action { ActionView::Delegate { delegate_action, signature } => { Action::Delegate(Box::new(SignedDelegateAction { delegate_action, signature })) } + ActionView::DeployGlobalContract { code } => { + Action::DeployGlobalContract(DeployGlobalContractAction { + code, + deploy_mode: GlobalContractDeployMode::CodeHash, + }) + } + ActionView::DeployGlobalContractByAccountId { code } => { + Action::DeployGlobalContract(DeployGlobalContractAction { + code, + deploy_mode: GlobalContractDeployMode::AccountId, + }) + } }) } } diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index ed832ee9cb6..4ffecb82b36 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,16 @@ pub(crate) fn action_deploy_contract( Ok(()) } +pub(crate) fn action_deploy_global_contract( + _account_id: &AccountId, + _deploy_contract: &DeployGlobalContractAction, + _result: &mut ActionResult, +) -> Result<(), StorageError> { + let _span = tracing::debug_span!(target: "runtime", "action_deploy_global_contract").entered(); + // TODO(#12715): implement global contract distribution + Ok(()) +} + pub(crate) fn action_delete_account( state_update: &mut TrieUpdate, account: &mut Option, @@ -1025,7 +1036,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::Stake(_) + | Action::AddKey(_) + | Action::DeleteKey(_) + | Action::DeployGlobalContract(_) => { if actor_id != account_id { return Err(ActionErrorKind::ActorNoPermission { account_id: account_id.clone(), @@ -1137,7 +1152,8 @@ pub(crate) fn check_account_existence( | Action::AddKey(_) | Action::DeleteKey(_) | Action::DeleteAccount(_) - | Action::Delegate(_) => { + | Action::Delegate(_) + | Action::DeployGlobalContract(_) => { 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 a833af19697..d2fca9d17e2 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -150,6 +150,10 @@ pub fn total_send_fees( &delegate_action.receiver_id, )? } + DeployGlobalContract(_deploy_global_contract_action) => { + // TODO(#12717): implement send fees for global contract deploy + 1 + } }; result = safe_add_gas(result, delta)?; } @@ -241,6 +245,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(), + DeployGlobalContract(_deploy_global_contract_action) => { + // TODO(#12717): implement exec fees for global contract deploys + 1 + } } } diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index f09bfb049b6..443edfce462 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -484,6 +484,9 @@ impl Runtime { apply_state.current_protocol_version, )?; } + Action::DeployGlobalContract(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); let contract = preparation_pipeline.get_contract( diff --git a/runtime/runtime/src/pipelining.rs b/runtime/runtime/src/pipelining.rs index 3fd06b49aa8..f1448537610 100644 --- a/runtime/runtime/src/pipelining.rs +++ b/runtime/runtime/src/pipelining.rs @@ -189,7 +189,8 @@ impl ReceiptPreparationPipeline { | Action::Stake(_) | Action::AddKey(_) | Action::DeleteKey(_) - | Action::DeleteAccount(_) => {} + | Action::DeleteAccount(_) + | Action::DeployGlobalContract(_) => {} #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] Action::NonrefundableStorageTransfer(_) => {} } diff --git a/runtime/runtime/src/verifier.rs b/runtime/runtime/src/verifier.rs index f2453088a69..c625933f577 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, @@ -433,6 +434,9 @@ 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, current_protocol_version) + } Action::FunctionCall(a) => validate_function_call_action(limit_config, a), Action::Transfer(_) => Ok(()), #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] @@ -472,6 +476,28 @@ 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, + current_protocol_version: ProtocolVersion, +) -> Result<(), ActionsValidationError> { + if !checked_feature!("stable", GlobalContracts, current_protocol_version) { + return Err(ActionsValidationError::UnsupportedProtocolFeature { + protocol_feature: "GlobalContracts".to_owned(), + version: current_protocol_version, + }); + } + 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/protocol-schema-check/res/protocol_schema.toml b/tools/protocol-schema-check/res/protocol_schema.toml index 6e8b762976a..f8b89209605 100644 --- a/tools/protocol-schema-check/res/protocol_schema.toml +++ b/tools/protocol-schema-check/res/protocol_schema.toml @@ -3,11 +3,11 @@ AccessKeyPermission = 885623561 Account = 358811118 AccountV2 = 337859929 AccountVersion = 4249996519 -Action = 2541352025 +Action = 776842263 ActionCosts = 3115555891 ActionError = 4217425219 ActionErrorKind = 1632922469 -ActionReceipt = 2414678051 +ActionReceipt = 1557869689 ActionsValidationError = 1053886215 AddKeyAction = 356099649 AdvertisedPeerDistance = 1372421497 @@ -53,8 +53,8 @@ BlockV4 = 619721361 BlockWithChangesInfo = 887507517 BufferedReceiptIndices = 2030010377 CachedParts = 1180507252 -Challenge = 1892506304 -ChallengeBody = 652213846 +Challenge = 790871821 +ChallengeBody = 3913931329 ChunkContractAccesses = 266426785 ChunkContractAccessesInner = 2811580521 ChunkContractAccessesV1 = 3680796018 @@ -68,10 +68,10 @@ ChunkExtraV1 = 774877102 ChunkHash = 1471814478 ChunkHashHeight = 825215623 ChunkProductionKey = 2508733236 -ChunkProofs = 368992087 -ChunkState = 1462720897 +ChunkProofs = 2621917485 +ChunkState = 4147907071 ChunkStateTransition = 307448170 -ChunkStateWitness = 2738191235 +ChunkStateWitness = 2948089286 ChunkStateWitnessAck = 177881908 ChunkStats = 4176245277 CodeBytes = 2940589161 @@ -95,10 +95,11 @@ CurrentEpochValidatorInfo = 434177728 DataReceipt = 2506806701 DataReceiver = 1715762664 DelayedReceiptIndices = 1315689119 -DelegateAction = 3898002655 +DelegateAction = 1467185064 DeleteAccountAction = 3244670577 DeleteKeyAction = 1374597333 DeployContractAction = 2972267833 +DeployGlobalContractAction = 2078234364 Direction = 1296680832 DistanceVector = 181987261 ED25519PublicKey = 213018126 @@ -143,6 +144,7 @@ FlatStorageStatus = 3964465569 FunctionCallAction = 2405840012 FunctionCallError = 3652274053 FunctionCallPermission = 1517509673 +GlobalContractDeployMode = 1256912586 Handshake = 115352275 HandshakeAutoDes = 4093619285 HandshakeFailureReason = 3698375404 @@ -157,22 +159,22 @@ LatestWitnessesInfo = 2488443612 LegacyAccount = 1291371319 LinkAllowance = 1652755161 MainTransitionKey = 3721480128 -MaybeEncodedShardChunk = 2688433530 +MaybeEncodedShardChunk = 2470055256 MerklePathItem = 2615629611 MessageDiscriminant = 3240833245 MethodResolveError = 1206790835 MissingTrieValueContext = 2666011379 NextEpochValidatorInfo = 3660299258 -NonDelegateAction = 3255205790 +NonDelegateAction = 705924980 ParentSplitParameters = 2945469052 PartialEdgeInfo = 1350359189 -PartialEncodedChunk = 3453254449 +PartialEncodedChunk = 2463323471 PartialEncodedChunkForwardMsg = 68012243 PartialEncodedChunkPart = 194051090 PartialEncodedChunkRequestMsg = 1470767646 -PartialEncodedChunkResponseMsg = 2957212759 -PartialEncodedChunkV1 = 3642706173 -PartialEncodedChunkV2 = 889431033 +PartialEncodedChunkResponseMsg = 4085260276 +PartialEncodedChunkV1 = 872351009 +PartialEncodedChunkV2 = 2201750036 PartialEncodedContractDeploys = 3216562245 PartialEncodedContractDeploysInner = 2549441552 PartialEncodedContractDeploysPart = 1672852427 @@ -184,7 +186,7 @@ PeerChainInfoV2 = 1260985250 PeerId = 2447445523 PeerIdOrHash = 4080492546 PeerInfo = 3831734408 -PeerMessage = 2449118209 +PeerMessage = 3436033484 Ping = 2783493472 Pong = 3159638327 PrepareError = 4009037507 @@ -198,28 +200,28 @@ RawStateChangesWithTrieKey = 36781564 RawTrieNode = 4239211001 RawTrieNodeWithSize = 1474149765 ReasonForBan = 792112981 -Receipt = 2916802703 -ReceiptEnum = 3157292228 +Receipt = 2204437923 +ReceiptEnum = 363966149 ReceiptGroup = 2105921101 ReceiptGroupV0 = 2900361850 ReceiptGroupsQueueData = 289073248 ReceiptGroupsQueueDataV0 = 3449687695 ReceiptList = 3805749482 -ReceiptOrStateStoredReceipt = 4272263647 -ReceiptProof = 3619921825 -ReceiptProofResponse = 2911527755 -ReceiptV0 = 3604411866 -ReceiptV1 = 2994842769 +ReceiptOrStateStoredReceipt = 1445320664 +ReceiptProof = 3654203090 +ReceiptProofResponse = 1538369673 +ReceiptV0 = 2579978250 +ReceiptV1 = 2410814374 ReceiptValidationError = 551721215 ReceivedData = 3601438283 RootProof = 3135729669 -RoutedMessage = 4094023612 -RoutedMessageBody = 2434166470 +RoutedMessage = 1371676559 +RoutedMessageBody = 720213720 RoutingTableUpdate = 2987752645 Secp256K1PublicKey = 4117078281 Secp256K1Signature = 3687154735 ServerError = 2338793369 -ShardChunk = 350912142 +ShardChunk = 2569066311 ShardChunkHeader = 2471921769 ShardChunkHeaderInner = 4085026561 ShardChunkHeaderInnerV1 = 1271245459 @@ -229,22 +231,22 @@ ShardChunkHeaderInnerV4 = 3066669719 ShardChunkHeaderV1 = 47891389 ShardChunkHeaderV2 = 226996174 ShardChunkHeaderV3 = 3315420662 -ShardChunkV1 = 1956351688 -ShardChunkV2 = 61923749 +ShardChunkV1 = 1687626581 +ShardChunkV2 = 3129653406 ShardLayout = 1639977238 ShardLayoutV0 = 3139625127 ShardLayoutV1 = 2054829142 ShardLayoutV2 = 997571636 ShardProof = 1787648268 -ShardStateSyncResponse = 85505566 -ShardStateSyncResponseHeaderV1 = 1491041593 -ShardStateSyncResponseHeaderV2 = 3976991370 -ShardStateSyncResponseV1 = 1376844594 -ShardStateSyncResponseV2 = 2436600135 -ShardStateSyncResponseV3 = 3559507986 +ShardStateSyncResponse = 36151067 +ShardStateSyncResponseHeaderV1 = 3239482057 +ShardStateSyncResponseHeaderV2 = 2990843176 +ShardStateSyncResponseV1 = 818774996 +ShardStateSyncResponseV2 = 3658187594 +ShardStateSyncResponseV3 = 4290351817 ShardUId = 2410086023 Signature = 3997391707 -SignedDelegateAction = 2482265228 +SignedDelegateAction = 2481674346 SignedTransaction = 3898692301 SlashState = 3264273950 SlashedValidator = 2601657743 @@ -254,14 +256,14 @@ StateChangeCause = 3890585134 StateHeaderKey = 1666317019 StatePartKey = 1083277414 StatePartRequest = 1911936050 -StateResponseInfo = 3497809959 -StateResponseInfoV1 = 226548439 -StateResponseInfoV2 = 3314983982 +StateResponseInfo = 725797617 +StateResponseInfoV1 = 1157694409 +StateResponseInfoV2 = 1173349517 StateRootNode = 1865105129 -StateStoredReceipt = 311659268 +StateStoredReceipt = 1819790166 StateStoredReceiptMetadata = 2895538362 -StateStoredReceiptV0 = 4029868827 -StateStoredReceiptV1 = 2070659369 +StateStoredReceiptV0 = 705939741 +StateStoredReceiptV1 = 1187482238 StateSyncDumpProgress = 2225888613 StorageError = 2572184728 StoredChunkStateTransitionData = 102691676 @@ -269,9 +271,9 @@ StoredChunkStateTransitionDataV1 = 3220541377 String = 2587724713 SyncSnapshotHosts = 1436852332 Tip = 305642482 -TransactionReceipt = 968816131 -TransactionV0 = 197396442 -TransactionV1 = 2594686420 +TransactionReceipt = 413230486 +TransactionV0 = 1428493041 +TransactionV1 = 442421785 TransferAction = 1078380396 TrieChanges = 2613580820 TrieKey = 1352104737 diff --git a/tools/state-viewer/src/contract_accounts.rs b/tools/state-viewer/src/contract_accounts.rs index 57adfe41f9b..cb230760258 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,9 @@ 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