Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: global contract usage #12886

Open
wants to merge 51 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d81c640
global contract support
stedfn Feb 5, 2025
05887a2
remove duplicate
stedfn Feb 5, 2025
f2fd27b
clippy
stedfn Feb 5, 2025
498a9ad
.
stedfn Feb 5, 2025
a95fe45
simplify
stedfn Feb 5, 2025
625d592
.
stedfn Feb 5, 2025
e4b30e6
fix
stedfn Feb 5, 2025
b2d8efa
schema
stedfn Feb 5, 2025
e73e9d9
address comments
stedfn Feb 6, 2025
506d964
schema
stedfn Feb 6, 2025
f995762
Merge branch 'master' into stefan/global_contract_account_support
stedfn Feb 6, 2025
a285285
fix
stedfn Feb 6, 2025
28c6ea1
comments
stedfn Feb 6, 2025
115b53d
fmt
stedfn Feb 6, 2025
6baaef7
style
stedfn Feb 6, 2025
0cc098a
.
stedfn Feb 6, 2025
4d9bbc9
reference todo
stedfn Feb 6, 2025
e73e627
.
stedfn Feb 6, 2025
897985e
comments
stedfn Feb 6, 2025
102e4a9
comments
stedfn Feb 7, 2025
b4f5ed9
introduce local_contract_hash
stedfn Feb 7, 2025
f24d365
Merge branch 'master' into stefan/global_contract_account_support
stedfn Feb 7, 2025
ad1f781
rename tests
stedfn Feb 7, 2025
1ca7b96
Merge branch 'stefan/global_contract_account_support' of github.com:n…
stedfn Feb 7, 2025
0d24479
wip
stedfn Feb 7, 2025
8751cb6
Merge branch 'stefan/global_contract_account_support' into stefan/glo…
stedfn Feb 7, 2025
dd266a7
wip
stedfn Feb 7, 2025
e981c09
deploy global contract
stedfn Feb 7, 2025
cdc92ef
merge
stedfn Feb 10, 2025
f747bb8
.
stedfn Feb 10, 2025
067c5bb
remove useless fn
stedfn Feb 10, 2025
9db880d
impl errors
stedfn Feb 10, 2025
037611a
Revert "remove useless fn"
stedfn Feb 10, 2025
139f5ff
mvp
stedfn Feb 11, 2025
40e9976
Merge branch 'master' into stefan/global_contract_usage
stedfn Feb 11, 2025
5f10308
test wip failing
stedfn Feb 11, 2025
c4e603a
remove debug logs
stedfn Feb 11, 2025
d3cf5ed
remove logs
stedfn Feb 11, 2025
713d0b6
tests
stedfn Feb 11, 2025
6500dde
remove comment
stedfn Feb 11, 2025
b4c4c2f
fix
stedfn Feb 11, 2025
a1eed5b
move tests behind nightly_protocol feature
stedfn Feb 11, 2025
157b0f9
Merge branch 'master' into stefan/global_contract_usage
stedfn Feb 12, 2025
be89a14
remove get_global_code fn and update errors
stedfn Feb 12, 2025
71cca08
.
stedfn Feb 12, 2025
fc319ce
consider global contract deployment receipt cost
stedfn Feb 13, 2025
f771c89
address comments
stedfn Feb 14, 2025
bc20a98
remove log
stedfn Feb 14, 2025
48a5d4f
Merge branch 'master' into stefan/global_contract_usage
stedfn Feb 14, 2025
41239c1
add new field in BalanceStats for gas burnt by global actions
stedfn Feb 14, 2025
0d07078
comments
stedfn Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion chain/chain-primitives/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use near_primitives::block::BlockValidityError;
use near_primitives::challenge::{ChunkProofs, ChunkState, MaybeEncodedShardChunk};
use near_primitives::errors::{ChunkAccessError, EpochError, StorageError};
use near_primitives::errors::{ChunkAccessError, EpochError, GlobalContractError, StorageError};
use near_primitives::shard_layout::ShardLayoutError;
use near_primitives::sharding::{BadHeaderForProtocolVersionError, ChunkHash, ShardChunkHeader};
use near_primitives::types::{BlockHeight, EpochId, ShardId, ShardIndex};
Expand Down Expand Up @@ -251,6 +251,9 @@ pub enum Error {
/// Invalid chunk header version for protocol version
#[error(transparent)]
BadHeaderForProtocolVersion(#[from] BadHeaderForProtocolVersionError),
/// Global contract error.
#[error("Global Contract Error: {0}")]
GlobalContractError(GlobalContractError),
/// Anything else
#[error("Other Error: {0}")]
Other(String),
Expand Down Expand Up @@ -289,6 +292,7 @@ impl Error {
| Error::StorageError(_)
| Error::GCError(_)
| Error::ReshardingError(_)
| Error::GlobalContractError(_)
| Error::DBNotFoundErr(_) => false,
Error::InvalidBlockPastTime(_, _)
| Error::InvalidBlockFutureTime(_)
Expand Down Expand Up @@ -424,6 +428,7 @@ impl Error {
Error::NotAChunkValidator => "not_a_chunk_validator",
Error::InvalidChallengeRoot => "invalid_challenge_root",
Error::ReshardingError(_) => "resharding_error",
Error::GlobalContractError(_) => "global_contract_error",
Error::BadHeaderForProtocolVersion(_) => "bad_header_for_protocol_version",
}
}
Expand Down
1 change: 1 addition & 0 deletions chain/chain/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ impl NightshadeRuntime {
// TODO(#2152): process gracefully
RuntimeError::ReceiptValidationError(e) => panic!("{}", e),
RuntimeError::ValidatorError(e) => e.into(),
RuntimeError::GlobalContractError(e) => Error::GlobalContractError(e),
})?;
let elapsed = instant.elapsed();

Expand Down
15 changes: 15 additions & 0 deletions core/primitives/src/action/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use serde_with::serde_as;
use std::fmt;
use std::sync::Arc;

use crate::trie_key::GlobalContractCodeIdentifier;

pub fn base64(s: &[u8]) -> String {
use base64::Engine;
base64::engine::general_purpose::STANDARD.encode(s)
Expand Down Expand Up @@ -179,6 +181,19 @@ pub enum GlobalContractIdentifier {
AccountId(AccountId),
}

impl From<GlobalContractCodeIdentifier> for GlobalContractIdentifier {
fn from(identifier: GlobalContractCodeIdentifier) -> Self {
match identifier {
GlobalContractCodeIdentifier::CodeHash(hash) => {
GlobalContractIdentifier::CodeHash(hash)
}
GlobalContractCodeIdentifier::AccountId(account_id) => {
GlobalContractIdentifier::AccountId(account_id)
}
}
}
}

/// Use global contract action
#[serde_as]
#[derive(
Expand Down
1 change: 1 addition & 0 deletions core/primitives/src/chunk_apply_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ pub struct BalanceStats {
/// This is a negative amount. This amount was not charged from the account that issued
/// the transaction. It's likely due to the delayed queue of the receipts.
pub gas_deficit_amount: Balance,
pub global_actions_burnt_amount: Balance,
}

/// Convert a bandwidth request from the bitmap representation to a list of requested values.
Expand Down
78 changes: 65 additions & 13 deletions core/primitives/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::action::GlobalContractIdentifier;
use crate::hash::CryptoHash;
use crate::serialize::dec_format;
use crate::shard_layout::ShardLayoutError;
Expand Down Expand Up @@ -67,6 +68,8 @@ pub enum RuntimeError {
ReceiptValidationError(ReceiptValidationError),
/// Error when accessing validator information. Happens inside epoch manager.
ValidatorError(EpochError),
/// Global contract errors.
GlobalContractError(GlobalContractError),
}

impl std::fmt::Display for RuntimeError {
Expand Down Expand Up @@ -517,9 +520,13 @@ impl std::error::Error for ActionError {}
)]
pub enum ActionErrorKind {
/// Happens when CreateAccount action tries to create an account with account_id which is already exists in the storage
AccountAlreadyExists { account_id: AccountId },
AccountAlreadyExists {
account_id: AccountId,
},
/// Happens when TX receiver_id doesn't exist (but action is not Action::CreateAccount)
AccountDoesNotExist { account_id: AccountId },
AccountDoesNotExist {
account_id: AccountId,
},
/// A top-level account ID can only be created by registrar.
CreateAccountOnlyByRegistrar {
account_id: AccountId,
Expand All @@ -528,16 +535,30 @@ pub enum ActionErrorKind {
},

/// A newly created account must be under a namespace of the creator account
CreateAccountNotAllowed { account_id: AccountId, predecessor_id: AccountId },
CreateAccountNotAllowed {
account_id: AccountId,
predecessor_id: AccountId,
},
/// Administrative actions like `DeployContract`, `Stake`, `AddKey`, `DeleteKey`. can be proceed only if sender=receiver
/// or the first TX action is a `CreateAccount` action
ActorNoPermission { account_id: AccountId, actor_id: AccountId },
ActorNoPermission {
account_id: AccountId,
actor_id: AccountId,
},
/// Account tries to remove an access key that doesn't exist
DeleteKeyDoesNotExist { account_id: AccountId, public_key: Box<PublicKey> },
DeleteKeyDoesNotExist {
account_id: AccountId,
public_key: Box<PublicKey>,
},
/// The public key is already used for an existing access key
AddKeyAlreadyExists { account_id: AccountId, public_key: Box<PublicKey> },
AddKeyAlreadyExists {
account_id: AccountId,
public_key: Box<PublicKey>,
},
/// Account is staking and can not be deleted
DeleteAccountStaking { account_id: AccountId },
DeleteAccountStaking {
account_id: AccountId,
},
/// ActionReceipt can't be completed, because the remaining balance will not be enough to cover storage.
LackBalanceForState {
/// An account which needs balance
Expand All @@ -547,7 +568,9 @@ pub enum ActionErrorKind {
amount: Balance,
},
/// Account is not yet staked, but tries to unstake
TriesToUnstake { account_id: AccountId },
TriesToUnstake {
account_id: AccountId,
},
/// The account doesn't have enough balance to increase the stake.
TriesToStake {
account_id: AccountId,
Expand Down Expand Up @@ -576,21 +599,37 @@ pub enum ActionErrorKind {
///
/// TODO(#8598): This error is named very poorly. A better name would be
/// `OnlyNamedAccountCreationAllowed`.
OnlyImplicitAccountCreationAllowed { account_id: AccountId },
OnlyImplicitAccountCreationAllowed {
account_id: AccountId,
},
/// Delete account whose state is large is temporarily banned.
DeleteAccountWithLargeState { account_id: AccountId },
DeleteAccountWithLargeState {
account_id: AccountId,
},
/// Signature does not match the provided actions and given signer public key.
DelegateActionInvalidSignature,
/// Receiver of the transaction doesn't match Sender of the delegate action
DelegateActionSenderDoesNotMatchTxReceiver { sender_id: AccountId, receiver_id: AccountId },
DelegateActionSenderDoesNotMatchTxReceiver {
sender_id: AccountId,
receiver_id: AccountId,
},
/// Delegate action has expired. `max_block_height` is less than actual block height.
DelegateActionExpired,
/// The given public key doesn't exist for Sender account
DelegateActionAccessKeyError(InvalidAccessKeyError),
/// DelegateAction nonce must be greater sender[public_key].nonce
DelegateActionInvalidNonce { delegate_nonce: Nonce, ak_nonce: Nonce },
DelegateActionInvalidNonce {
delegate_nonce: Nonce,
ak_nonce: Nonce,
},
/// DelegateAction nonce is larger than the upper bound given by the block height
DelegateActionNonceTooLarge { delegate_nonce: Nonce, upper_bound: Nonce },
DelegateActionNonceTooLarge {
delegate_nonce: Nonce,
upper_bound: Nonce,
},
GlobalContractIdentifierNotFound {
identifier: GlobalContractIdentifier,
},
}

impl From<ActionErrorKind> for ActionError {
Expand Down Expand Up @@ -931,10 +970,23 @@ impl Display for ActionErrorKind {
ActionErrorKind::DelegateActionAccessKeyError(access_key_error) => Display::fmt(&access_key_error, f),
ActionErrorKind::DelegateActionInvalidNonce { delegate_nonce, ak_nonce } => write!(f, "DelegateAction nonce {} must be larger than nonce of the used access key {}", delegate_nonce, ak_nonce),
ActionErrorKind::DelegateActionNonceTooLarge { delegate_nonce, upper_bound } => write!(f, "DelegateAction nonce {} must be smaller than the access key nonce upper bound {}", delegate_nonce, upper_bound),
ActionErrorKind::GlobalContractIdentifierNotFound { identifier } => write!(f, "Global contract identifier {:?} not found", identifier),
}
}
}

#[derive(Eq, PartialEq, Clone, Debug)]
pub enum GlobalContractError {
IdentifierNotFound(GlobalContractIdentifier),
}

impl Display for GlobalContractError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let Self::IdentifierNotFound(id) = self;
write!(f, "GlobalContractError: {:?}", id)
}
}

#[derive(Eq, PartialEq, Clone)]
pub enum EpochError {
/// Error calculating threshold from given stakes for given number of seats.
Expand Down
50 changes: 50 additions & 0 deletions core/primitives/src/test_utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::account::{AccessKey, AccessKeyPermission, Account};
use crate::action::{
DeployGlobalContractAction, GlobalContractDeployMode, GlobalContractIdentifier,
UseGlobalContractAction,
};
use crate::block::Block;
use crate::block_body::{BlockBody, ChunkEndorsementSignatures};
use crate::block_header::BlockHeader;
Expand Down Expand Up @@ -273,6 +277,52 @@ impl SignedTransaction {
)
}

pub fn deploy_global_contract(
nonce: Nonce,
contract_id: AccountId,
code: Vec<u8>,
signer: &Signer,
block_hash: CryptoHash,
deploy_mode: GlobalContractDeployMode,
) -> SignedTransaction {
let signer_id = contract_id.clone();
let receiver_id = contract_id;
SignedTransaction::from_actions(
nonce,
signer_id,
receiver_id,
&signer,
vec![Action::DeployGlobalContract(DeployGlobalContractAction {
code: code.into(),
deploy_mode,
})],
block_hash,
0,
)
}

pub fn use_global_contract(
nonce: Nonce,
contract_id: &AccountId,
signer: &Signer,
block_hash: CryptoHash,
contract_identifier: GlobalContractIdentifier,
) -> SignedTransaction {
let signer_id = contract_id.clone();
let receiver_id = contract_id.clone();
SignedTransaction::from_actions(
nonce,
signer_id,
receiver_id,
&signer,
vec![Action::UseGlobalContract(Box::new(UseGlobalContractAction {
contract_identifier,
}))],
block_hash,
0,
)
}

pub fn create_contract(
nonce: Nonce,
originator: AccountId,
Expand Down
15 changes: 14 additions & 1 deletion core/primitives/src/trie_key.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::hash::CryptoHash;
use crate::types::AccountId;
use crate::{action::GlobalContractIdentifier, hash::CryptoHash};
use borsh::{to_vec, BorshDeserialize, BorshSerialize};
use near_crypto::PublicKey;
use near_primitives_core::types::ShardId;
Expand Down Expand Up @@ -124,6 +124,19 @@ impl GlobalContractCodeIdentifier {
}
}

impl From<GlobalContractIdentifier> for GlobalContractCodeIdentifier {
fn from(identifier: GlobalContractIdentifier) -> Self {
match identifier {
GlobalContractIdentifier::CodeHash(hash) => {
GlobalContractCodeIdentifier::CodeHash(hash)
}
GlobalContractIdentifier::AccountId(account_id) => {
GlobalContractCodeIdentifier::AccountId(account_id)
}
}
}
}

/// Describes the key of a specific key-value record in a state trie.
#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, ProtocolSchema)]
pub enum TrieKey {
Expand Down
18 changes: 13 additions & 5 deletions core/store/src/trie/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ use crate::contract::ContractStorage;
use crate::trie::TrieAccess;
use crate::trie::{KeyLookupMode, TrieChanges};
use crate::StorageError;
use near_primitives::account::AccountContract;
use near_primitives::apply::ApplyChunkReason;
use near_primitives::hash::{hash, CryptoHash};
use near_primitives::stateless_validation::contract_distribution::ContractUpdates;
use near_primitives::trie_key::TrieKey;
use near_primitives::trie_key::{GlobalContractCodeIdentifier, TrieKey};
use near_primitives::types::{
AccountId, RawStateChange, RawStateChanges, RawStateChangesWithTrieKey, StateChangeCause,
StateRoot, TrieCacheMode,
Expand Down Expand Up @@ -339,6 +340,7 @@ impl TrieUpdate {
&self,
account_id: AccountId,
code_hash: CryptoHash,
account_contract: &AccountContract,
apply_reason: ApplyChunkReason,
protocol_version: ProtocolVersion,
) -> Result<(), StorageError> {
Expand All @@ -357,10 +359,16 @@ impl TrieUpdate {
// Only record the call if trie contains the contract (with the given hash) being called deployed to the given account.
// This avoids recording contracts that do not exist or are newly-deployed to the account.
// Note that the check below to see if the contract exists has no side effects (not charging gas or recording trie nodes)
if code_hash == CryptoHash::default() {
return Ok(());
}
let trie_key = TrieKey::ContractCode { account_id };
let trie_key = match account_contract {
AccountContract::None => return Ok(()),
AccountContract::Local(_) => TrieKey::ContractCode { account_id },
AccountContract::Global(code_hash) => TrieKey::GlobalContractCode {
identifier: GlobalContractCodeIdentifier::CodeHash(*code_hash),
},
AccountContract::GlobalByAccount(account_id) => TrieKey::GlobalContractCode {
identifier: GlobalContractCodeIdentifier::AccountId(account_id.clone()),
},
};
let contract_ref = self
.trie
.get_optimized_ref_no_side_effects(&trie_key.to_vec(), KeyLookupMode::FlatStorage)
Expand Down
Loading
Loading