Skip to content

Commit

Permalink
feat: global contract distribution using receipts (#12793)
Browse files Browse the repository at this point in the history
Part of [#12715](#12715).

Whenever a user triggers a `DeployGlobalContractAction` action, a new
`GlobalContractDistribution` receipt will be distributed to all the
shards for deployment.
  • Loading branch information
stedfn authored Jan 29, 2025
1 parent 8bb2206 commit a33310b
Show file tree
Hide file tree
Showing 18 changed files with 238 additions and 84 deletions.
36 changes: 26 additions & 10 deletions chain/chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4483,9 +4483,16 @@ impl Chain {
) -> HashMap<ShardId, Vec<Receipt>> {
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() {
let entry = result.entry(shard_id).or_insert_with(Vec::new);
entry.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
}
Expand All @@ -4506,13 +4513,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![];
Expand Down
10 changes: 8 additions & 2 deletions chain/chain/src/runtime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,14 @@ impl TestEnv {
let shard_layout = self.epoch_manager.get_shard_layout_from_prev_block(&new_hash).unwrap();
let mut new_receipts = HashMap::<_, Vec<Receipt>>::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;
Expand Down
7 changes: 4 additions & 3 deletions chain/chain/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,9 +382,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 {
Expand Down
3 changes: 2 additions & 1 deletion chain/chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ mod test {
let shard_receipts: Vec<Receipt> = 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();
Expand Down
6 changes: 4 additions & 2 deletions core/primitives/src/action/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ use near_schema_checker_lib::ProtocolSchema;
use serde_with::base64::Base64;
use serde_with::serde_as;
use std::fmt;
use std::sync::Arc;

fn base64(s: &[u8]) -> String {
pub fn base64(s: &[u8]) -> String {
use base64::Engine;
base64::engine::general_purpose::STANDARD.encode(s)
}
Expand Down Expand Up @@ -146,7 +147,7 @@ pub enum GlobalContractDeployMode {
pub struct DeployGlobalContractAction {
/// WebAssembly binary
#[serde_as(as = "Base64")]
pub code: Vec<u8>,
pub code: Arc<[u8]>,

pub deploy_mode: GlobalContractDeployMode,
}
Expand All @@ -166,6 +167,7 @@ impl fmt::Debug for DeployGlobalContractAction {
BorshDeserialize,
serde::Serialize,
serde::Deserialize,
Hash,
PartialEq,
Eq,
Clone,
Expand Down
51 changes: 49 additions & 2 deletions core/primitives/src/receipt.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::action::{base64, GlobalContractIdentifier};
use crate::hash::CryptoHash;
use crate::serialize::dec_format;
use crate::transaction::{Action, TransferAction};
Expand All @@ -15,6 +16,7 @@ use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::io::{self, Read};
use std::io::{Error, ErrorKind};
use std::sync::Arc;

/// The outgoing (egress) data which will be transformed
/// to a `DataReceipt` to be sent to a `receipt.receiver`
Expand Down Expand Up @@ -246,10 +248,10 @@ impl<'a> StateStoredReceipt<'a> {
impl BorshSerialize for Receipt {
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
match self {
Receipt::V0(receipt) => receipt.serialize(writer),
Receipt::V0(receipt) => BorshSerialize::serialize(&receipt, writer),
Receipt::V1(receipt) => {
BorshSerialize::serialize(&1_u8, writer)?;
receipt.serialize(writer)
BorshSerialize::serialize(&receipt, writer)
}
}
}
Expand Down Expand Up @@ -485,6 +487,10 @@ impl Receipt {
*self.receipt_id()
}

pub fn send_to_all_shards(&self) -> bool {
matches!(self.receipt(), ReceiptEnum::GlobalContractDistribution(..))
}

/// 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`.
Expand Down Expand Up @@ -571,6 +577,19 @@ impl Receipt {
}),
}
}

pub fn new_global_contract_distribution(
predecessor_id: AccountId,
code: Arc<[u8]>,
id: GlobalContractIdentifier,
) -> Self {
Self::V0(ReceiptV0 {
predecessor_id,
receiver_id: "system".parse().unwrap(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::GlobalContractDistribution(GlobalContractData { code, id }),
})
}
}

/// Receipt could be either ActionReceipt or DataReceipt
Expand All @@ -590,6 +609,7 @@ pub enum ReceiptEnum {
Data(DataReceipt),
PromiseYield(ActionReceipt),
PromiseResume(DataReceipt),
GlobalContractDistribution(GlobalContractData),
}

/// ActionReceipt is derived from an Action from `Transaction or from Receipt`
Expand Down Expand Up @@ -670,6 +690,33 @@ impl fmt::Debug for ReceivedData {
}
}

#[serde_as]
#[derive(
BorshSerialize,
BorshDeserialize,
Hash,
PartialEq,
Eq,
Clone,
serde::Deserialize,
serde::Serialize,
ProtocolSchema,
)]
pub struct GlobalContractData {
#[serde_as(as = "Base64")]
pub code: Arc<[u8]>,
pub id: GlobalContractIdentifier,
}

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)))
.field("id", &self.id)
.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 {
Expand Down
17 changes: 14 additions & 3 deletions core/primitives/src/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ use crate::errors::TxExecutionError;
use crate::hash::{hash, CryptoHash};
use crate::merkle::{combine_hash, MerklePath};
use crate::network::PeerId;
use crate::receipt::{ActionReceipt, DataReceipt, DataReceiver, Receipt, ReceiptEnum, ReceiptV1};
use crate::receipt::{
ActionReceipt, DataReceipt, DataReceiver, GlobalContractData, Receipt, ReceiptEnum, ReceiptV1,
};
use crate::serialize::dec_format;
use crate::sharding::shard_chunk_header_inner::ShardChunkHeaderInnerV4;
use crate::sharding::{
Expand Down Expand Up @@ -1285,13 +1287,13 @@ impl TryFrom<ActionView> for Action {
}
ActionView::DeployGlobalContract { code } => {
Action::DeployGlobalContract(DeployGlobalContractAction {
code,
code: code.into(),
deploy_mode: GlobalContractDeployMode::CodeHash,
})
}
ActionView::DeployGlobalContractByAccountId { code } => {
Action::DeployGlobalContract(DeployGlobalContractAction {
code,
code: code.into(),
deploy_mode: GlobalContractDeployMode::AccountId,
})
}
Expand Down Expand Up @@ -1962,6 +1964,9 @@ pub enum ReceiptEnumView {
#[serde(default = "default_is_promise")]
is_promise_resume: bool,
},
GlobalContractDistribution {
data: GlobalContractData,
},
}

// Default value used when deserializing ReceiptEnumViews which are missing either the
Expand Down Expand Up @@ -2011,6 +2016,9 @@ impl From<Receipt> for ReceiptView {
is_promise_resume,
}
}
ReceiptEnum::GlobalContractDistribution(data) => {
ReceiptEnumView::GlobalContractDistribution { data }
}
},
priority,
}
Expand Down Expand Up @@ -2068,6 +2076,9 @@ impl TryFrom<ReceiptView> for Receipt {
ReceiptEnum::Data(data_receipt)
}
}
ReceiptEnumView::GlobalContractDistribution { data } => {
ReceiptEnum::GlobalContractDistribution(data)
}
},
priority: receipt_view.priority,
}))
Expand Down
4 changes: 3 additions & 1 deletion core/store/src/genesis/state_applier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,9 @@ impl GenesisStateApplier {
set_promise_yield_receipt(state_update, &receipt);
});
}
ReceiptEnum::Data(_) | ReceiptEnum::PromiseResume(_) => {
ReceiptEnum::GlobalContractDistribution(_)
| ReceiptEnum::Data(_)
| ReceiptEnum::PromiseResume(_) => {
panic!("Expected action receipt")
}
}
Expand Down
36 changes: 27 additions & 9 deletions runtime/runtime/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ 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, UseGlobalContractAction};
use near_primitives::action::{
DeployGlobalContractAction, GlobalContractDeployMode, GlobalContractIdentifier,
UseGlobalContractAction,
};
use near_primitives::checked_feature;
use near_primitives::config::ViewConfig;
use near_primitives::errors::{ActionError, ActionErrorKind, InvalidAccessKeyError, RuntimeError};
use near_primitives::hash::CryptoHash;
use near_primitives::hash::{hash, CryptoHash};
use near_primitives::receipt::{
ActionReceipt, DataReceipt, Receipt, ReceiptEnum, ReceiptPriority, ReceiptV0,
};
Expand Down Expand Up @@ -655,13 +658,26 @@ pub(crate) fn action_deploy_contract(
}

pub(crate) fn action_deploy_global_contract(
_account_id: &AccountId,
_deploy_contract: &DeployGlobalContractAction,
_result: &mut ActionResult,
) -> Result<(), StorageError> {
account_id: &AccountId,
deploy_contract: &DeployGlobalContractAction,
result: &mut ActionResult,
) {
let _span = tracing::debug_span!(target: "runtime", "action_deploy_global_contract").entered();
// TODO(#12715): implement global contract distribution
Ok(())

let id = match deploy_contract.deploy_mode {
GlobalContractDeployMode::CodeHash => {
GlobalContractIdentifier::CodeHash(hash(&deploy_contract.code))
}
GlobalContractDeployMode::AccountId => {
GlobalContractIdentifier::AccountId(account_id.clone())
}
};

result.new_receipts.push(Receipt::new_global_contract_distribution(
account_id.clone(),
deploy_contract.code.clone(),
id,
));
}

pub(crate) fn action_use_global_contract(
Expand Down Expand Up @@ -920,7 +936,9 @@ fn receipt_required_gas(apply_state: &ApplyState, receipt: &Receipt) -> Result<G

required_gas
}
ReceiptEnum::Data(_) | ReceiptEnum::PromiseResume(_) => 0,
ReceiptEnum::GlobalContractDistribution(_)
| ReceiptEnum::Data(_)
| ReceiptEnum::PromiseResume(_) => 0,
})
}

Expand Down
5 changes: 4 additions & 1 deletion runtime/runtime/src/balance_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ fn receipt_cost(
}
total_cost
}
ReceiptEnum::Data(_) | ReceiptEnum::PromiseResume(_) => 0,
ReceiptEnum::GlobalContractDistribution(_)
| ReceiptEnum::Data(_)
| ReceiptEnum::PromiseResume(_) => 0,
})
}

Expand Down Expand Up @@ -258,6 +260,7 @@ fn potential_postponed_receipt_ids(
account_id.clone(),
data_receipt.data_id,
))),
ReceiptEnum::GlobalContractDistribution(_) => None,
}
})
.collect::<Result<HashSet<_>, StorageError>>()
Expand Down
1 change: 1 addition & 0 deletions runtime/runtime/src/congestion_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ pub(crate) fn compute_receipt_congestion_gas(
// of it without expensive state lookups.
Ok(0)
}
ReceiptEnum::GlobalContractDistribution(_) => Ok(0),
}
}

Expand Down
Loading

0 comments on commit a33310b

Please sign in to comment.