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 46 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/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 @@ -935,6 +938,18 @@ impl Display for ActionErrorKind {
}
}

#[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
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
17 changes: 14 additions & 3 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,19 @@ 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() {
if account_contract.is_none() {
stedfn marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,99 @@ fn test_contract_distribution_cross_shard() {
env.shutdown_and_drain_remaining_events(Duration::seconds(20));
}

#[cfg(feature = "nightly_protocol")]
mod global_contracts_tests {
stedfn marked this conversation as resolved.
Show resolved Hide resolved
use super::*;
use crate::test_loop::utils::transactions::{deploy_global_contract, use_global_contract};
use near_primitives::action::{GlobalContractDeployMode, GlobalContractIdentifier};
use near_primitives::hash::CryptoHash;

#[test]
fn test_global_contract_by_hash() {
let (env, accounts, contract, rpc_id) = setup_global_contract_test();
let deploy_mode = GlobalContractDeployMode::CodeHash;
test_global_contract(env, deploy_mode, accounts.as_slice(), &contract, rpc_id);
}

#[test]
fn test_global_contract_by_account_id() {
let (env, accounts, contract, rpc_id) = setup_global_contract_test();
let deploy_mode = GlobalContractDeployMode::AccountId;
test_global_contract(env, deploy_mode, accounts.as_slice(), &contract, rpc_id);
}

fn setup_global_contract_test() -> (TestLoopEnv, Vec<AccountId>, ContractCode, AccountId) {
init_test_logger();
let accounts = make_accounts(NUM_ACCOUNTS);

let (env, rpc_id) = setup(&accounts);

let rpc_index = 8;
assert_eq!(accounts[rpc_index], rpc_id);

let contract = ContractCode::new(near_test_contracts::rs_contract().to_vec(), None);
(env, accounts, contract, rpc_id)
}

fn test_global_contract(
mut env: TestLoopEnv,
deploy_mode: GlobalContractDeployMode,
accounts: &[AccountId],
contract: &ContractCode,
rpc_id: AccountId,
) {
let mut nonce = 1;
let deploy_tx = deploy_global_contract(
&mut env.test_loop,
&env.datas,
&rpc_id,
&accounts[0],
contract.code().into(),
deploy_mode.clone(),
nonce,
);
nonce += 1;
env.test_loop.run_for(Duration::seconds(3));
check_txs(&env.test_loop.data, &env.datas, &rpc_id, &[deploy_tx]);
let identifier = match deploy_mode {
GlobalContractDeployMode::CodeHash => {
let code_hash = CryptoHash::hash_bytes(contract.code());
GlobalContractIdentifier::CodeHash(code_hash)
}
GlobalContractDeployMode::AccountId => {
GlobalContractIdentifier::AccountId(accounts[0].clone())
}
};
// 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,
identifier.clone(),
nonce,
);
nonce += 1;
env.test_loop.run_for(Duration::seconds(3));
check_txs(&env.test_loop.data, &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.data, &env.datas, &rpc_id, &[call_tx]);
}
env.shutdown_and_drain_remaining_events(Duration::seconds(20));
}
}

fn setup(accounts: &Vec<AccountId>) -> (TestLoopEnv, AccountId) {
let builder = TestLoopBuilder::new();

Expand Down
66 changes: 66 additions & 0 deletions integration-tests/src/test_loop/utils/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ use near_client::test_utils::test_loop::ClientQueries;
use near_client::{Client, ProcessTxResponse};
use near_crypto::Signer;
use near_network::client::ProcessTxRequest;
#[cfg(feature = "nightly_protocol")]
use near_primitives::action::{
Action, DeployGlobalContractAction, GlobalContractDeployMode, GlobalContractIdentifier,
UseGlobalContractAction,
};
use near_primitives::block::Tip;
use near_primitives::errors::InvalidTxError;
use near_primitives::hash::CryptoHash;
Expand Down Expand Up @@ -303,6 +308,67 @@ pub fn deploy_contract(
tx_hash
}

#[cfg(feature = "nightly_protocol")]
stedfn marked this conversation as resolved.
Show resolved Hide resolved
pub fn deploy_global_contract(
test_loop: &mut TestLoopV2,
node_datas: &[TestData],
rpc_id: &AccountId,
account_id: &AccountId,
code: Vec<u8>,
deploy_mode: GlobalContractDeployMode,
nonce: u64,
) -> CryptoHash {
let block_hash = get_shared_block_hash(node_datas, &test_loop.data);
let signer = create_user_test_signer(&account_id);

let tx = SignedTransaction::from_actions(
stedfn marked this conversation as resolved.
Show resolved Hide resolved
nonce,
account_id.clone(),
account_id.clone(),
&signer,
vec![Action::DeployGlobalContract(DeployGlobalContractAction {
code: code.into(),
deploy_mode,
})],
block_hash,
0,
);
let tx_hash = tx.get_hash();
submit_tx(node_datas, rpc_id, tx);

tx_hash
}

#[cfg(feature = "nightly_protocol")]
pub fn use_global_contract(
test_loop: &mut TestLoopV2,
node_datas: &[TestData],
rpc_id: &AccountId,
account_id: &AccountId,
global_contract_identifier: GlobalContractIdentifier,
nonce: u64,
) -> CryptoHash {
let block_hash = get_shared_block_hash(node_datas, &test_loop.data);
let signer = create_user_test_signer(&account_id);

let tx = SignedTransaction::from_actions(
stedfn marked this conversation as resolved.
Show resolved Hide resolved
nonce,
account_id.clone(),
account_id.clone(),
&signer,
vec![Action::UseGlobalContract(Box::new(UseGlobalContractAction {
contract_identifier: global_contract_identifier,
}))],
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.
Expand Down
1 change: 1 addition & 0 deletions integration-tests/src/user/runtime_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ impl RuntimeUser {
}
RuntimeError::ReceiptValidationError(e) => panic!("{}", e),
RuntimeError::ValidatorError(e) => panic!("{}", e),
RuntimeError::GlobalContractError(e) => panic!("{}", e),
})?;
for outcome_with_id in apply_result.outcomes {
self.transaction_results
Expand Down
Loading
Loading