Skip to content

Commit

Permalink
feat(optimistic block): use block height for receipt id (#12599)
Browse files Browse the repository at this point in the history
To support optimistic blocks, we need to decouple receipt IDs from the
block hash, as optimistic blocks will not include a block hash.

This PR introduces a new protocol feature that enables the use of block
height in computing receipt IDs.
  • Loading branch information
VanBarbascu authored Dec 11, 2024
1 parent bf0db79 commit c2d7028
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 21 deletions.
4 changes: 2 additions & 2 deletions chain/chain/src/tests/simple_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn build_chain() {
// cargo insta test --accept -p near-chain --features nightly -- tests::simple_chain::build_chain
let hash = chain.head().unwrap().last_block_hash;
if cfg!(feature = "nightly") {
insta::assert_snapshot!(hash, @"Ch6xEoeJ1MNWLR2em48f6mKFko7niVoMyUazeUzFY8b4");
insta::assert_snapshot!(hash, @"GARF4HBtQJ41quFA9fvjHpbVYT4o15syhL3FkH1o7poT");
} else {
insta::assert_snapshot!(hash, @"5LkmueLrB2cc3vURr6VKvT9acRTuNzWGTvzLGFJkRD9c");
}
Expand All @@ -51,7 +51,7 @@ fn build_chain() {

let hash = chain.head().unwrap().last_block_hash;
if cfg!(feature = "nightly") {
insta::assert_snapshot!(hash, @"A1uyFaVoJ2spKmGfjDRgnFXRx4aP21yQxArA7muN23bb");
insta::assert_snapshot!(hash, @"HiXuBfW5Xd6e8ZTbMhwtPEXeZxe7macc8DvaWryNdvcf");
} else {
insta::assert_snapshot!(hash, @"5txsrLCmQp9kn3jYRp1VHrCDt7oBTnhyi71rEPZmm8Ce");
}
Expand Down
5 changes: 4 additions & 1 deletion core/primitives-core/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ pub enum ProtocolFeature {
/// Exclude existing contract code in deploy-contract and delete-account actions from the chunk state witness.
/// Instead of sending code in the witness, the code checks the code-size using the internal trie nodes.
ExcludeExistingCodeFromWitnessForCodeLen,
/// Use the block height instead of the block hash to calculate the receipt ID.
BlockHeightForReceiptId,
}

impl ProtocolFeature {
Expand Down Expand Up @@ -272,6 +274,7 @@ impl ProtocolFeature {
ProtocolFeature::RelaxedChunkValidation => 146,
ProtocolFeature::ExcludeExistingCodeFromWitnessForCodeLen => 147,
ProtocolFeature::BandwidthScheduler => 148,
ProtocolFeature::BlockHeightForReceiptId => 149,
// Place features that are not yet in Nightly below this line.
}
}
Expand All @@ -285,7 +288,7 @@ impl ProtocolFeature {
const STABLE_PROTOCOL_VERSION: ProtocolVersion = 74;

// On nightly, pick big enough version to support all features.
const NIGHTLY_PROTOCOL_VERSION: ProtocolVersion = 148;
const NIGHTLY_PROTOCOL_VERSION: ProtocolVersion = 149;

/// Largest protocol version supported by the current binary.
pub const PROTOCOL_VERSION: ProtocolVersion = if cfg!(feature = "nightly_protocol") {
Expand Down
115 changes: 101 additions & 14 deletions core/primitives/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::fmt;
use chrono;
use chrono::DateTime;

use near_primitives_core::types::BlockHeight;
use near_primitives_core::version::ProtocolFeature;
use serde;

use crate::hash::{hash, CryptoHash};
Expand Down Expand Up @@ -215,70 +217,84 @@ pub fn get_outcome_id_block_hash_rev(key: &[u8]) -> std::io::Result<(CryptoHash,
Ok((outcome_id, block_hash))
}

/// Creates a new Receipt ID from a given signed transaction and a block hash.
/// Creates a new Receipt ID from a given signed transaction and a block height or hash.
/// This method is backward compatible, so it takes the current protocol version.
pub fn create_receipt_id_from_transaction(
protocol_version: ProtocolVersion,
signed_transaction: &SignedTransaction,
prev_block_hash: &CryptoHash,
block_hash: &CryptoHash,
block_height: BlockHeight,
) -> CryptoHash {
create_hash_upgradable(
protocol_version,
&signed_transaction.get_hash(),
prev_block_hash,
block_hash,
block_height,
0,
)
}

/// Creates a new Receipt ID from a given receipt id, a block hash and a new receipt index.
/// Creates a new Receipt ID from a given receipt id, a block height or hash and a new receipt index.
/// This method is backward compatible, so it takes the current protocol version.
pub fn create_receipt_id_from_receipt_id(
protocol_version: ProtocolVersion,
receipt_id: &CryptoHash,
prev_block_hash: &CryptoHash,
block_hash: &CryptoHash,
block_height: BlockHeight,
receipt_index: usize,
) -> CryptoHash {
create_hash_upgradable(
protocol_version,
receipt_id,
prev_block_hash,
block_hash,
block_height,
receipt_index as u64,
)
}

/// Creates a new action_hash from a given receipt, a block hash and an action index.
/// Creates a new action_hash from a given receipt, a block height or hash and an action index.
/// This method is backward compatible, so it takes the current protocol version.
pub fn create_action_hash_from_receipt_id(
protocol_version: ProtocolVersion,
receipt_id: &CryptoHash,
prev_block_hash: &CryptoHash,
block_hash: &CryptoHash,
block_height: BlockHeight,
action_index: usize,
) -> CryptoHash {
// Action hash uses the same input as a new receipt ID, so to avoid hash conflicts we use the
// salt starting from the `u64` going backward.
let salt = u64::MAX.wrapping_sub(action_index as u64);
create_hash_upgradable(protocol_version, receipt_id, prev_block_hash, block_hash, salt)
create_hash_upgradable(
protocol_version,
receipt_id,
prev_block_hash,
block_hash,
block_height,
salt,
)
}

/// Creates a new Receipt ID from a given action hash, a block hash and a new receipt index.
/// Creates a new Receipt ID from a given action hash, a block height or hash and a new receipt index.
/// This method is backward compatible, so it takes the current protocol version.
pub fn create_receipt_id_from_action_hash(
protocol_version: ProtocolVersion,
action_hash: &CryptoHash,
prev_block_hash: &CryptoHash,
block_hash: &CryptoHash,
block_height: BlockHeight,
receipt_index: usize,
) -> CryptoHash {
create_hash_upgradable(
protocol_version,
action_hash,
prev_block_hash,
block_hash,
block_height,
receipt_index as u64,
)
}
Expand Down Expand Up @@ -310,14 +326,17 @@ pub fn create_random_seed(

/// Creates a new CryptoHash ID based on the protocol version.
/// Before `CREATE_HASH_PROTOCOL_VERSION` it uses `create_nonce_with_nonce` with
/// just `base` and `salt`. But after `CREATE_HASH_PROTOCOL_VERSION` it uses
/// `extra_hash` in addition to the `base` and `salt`.
/// just `base` and `salt`.
/// After `CREATE_HASH_PROTOCOL_VERSION` it uses `extra_hash` in addition to the `base` and `salt`.
/// E.g. this `extra_hash` can be a block hash to distinguish receipts between forks.
/// After ProtocolFeature::BlockHeightForReceiptId, the code uses `block_height` instead of `extra_hash`.
/// This enables applying chunks using only the optimistic block, which does not yet have a block hash.
fn create_hash_upgradable(
protocol_version: ProtocolVersion,
base: &CryptoHash,
extra_hash_old: &CryptoHash,
extra_hash: &CryptoHash,
block_height: BlockHeight,
salt: u64,
) -> CryptoHash {
if protocol_version < CREATE_HASH_PROTOCOL_VERSION {
Expand All @@ -327,13 +346,13 @@ fn create_hash_upgradable(
size_of::<CryptoHash>() + size_of::<CryptoHash>() + size_of::<u64>();
let mut bytes: Vec<u8> = Vec::with_capacity(BYTES_LEN);
bytes.extend_from_slice(base.as_ref());
let extra_hash_used =
if protocol_version < CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION {
extra_hash_old
} else {
extra_hash
};
bytes.extend_from_slice(extra_hash_used.as_ref());
if ProtocolFeature::BlockHeightForReceiptId.enabled(protocol_version) {
bytes.extend_from_slice(block_height.to_le_bytes().as_ref())
} else if protocol_version >= CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION {
bytes.extend_from_slice(extra_hash.as_ref())
} else {
bytes.extend_from_slice(extra_hash_old.as_ref())
};
bytes.extend(index_to_bytes(salt));
hash(&bytes)
}
Expand Down Expand Up @@ -496,6 +515,7 @@ pub fn derive_eth_implicit_account_id(public_key: &Secp256K1PublicKey) -> Accoun
mod tests {
use super::*;
use near_crypto::{KeyType, PublicKey};
use near_primitives_core::version::ProtocolFeature;

#[test]
fn test_derive_near_implicit_account_id() {
Expand Down Expand Up @@ -529,6 +549,8 @@ mod tests {
let base = hash(b"atata");
let extra_base = hash(b"hohoho");
let other_extra_base = hash(b"banana");
let block_height: BlockHeight = 123_456_789;
let other_block_height: BlockHeight = 123_123_123;
let salt = 3;
assert_eq!(
create_nonce_with_nonce(&base, salt),
Expand All @@ -537,6 +559,7 @@ mod tests {
&base,
&extra_base,
&extra_base,
block_height,
salt,
)
);
Expand All @@ -547,6 +570,7 @@ mod tests {
&base,
&extra_base,
&extra_base,
block_height,
salt,
)
);
Expand All @@ -556,13 +580,15 @@ mod tests {
&base,
&extra_base,
&extra_base,
block_height,
salt,
),
create_hash_upgradable(
CREATE_HASH_PROTOCOL_VERSION,
&base,
&other_extra_base,
&other_extra_base,
block_height,
salt,
)
);
Expand All @@ -572,13 +598,15 @@ mod tests {
&base,
&extra_base,
&other_extra_base,
block_height,
salt,
),
create_hash_upgradable(
CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION,
&base,
&extra_base,
&other_extra_base,
block_height,
salt,
)
);
Expand All @@ -588,15 +616,74 @@ mod tests {
&base,
&extra_base,
&other_extra_base,
block_height,
salt,
),
create_hash_upgradable(
CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION,
&base,
&other_extra_base,
&other_extra_base,
block_height,
salt
)
);
// Check that for protocol versions post BlockHeightForReceiptId, the hash does not depend on block hash.
assert_eq!(
create_hash_upgradable(
ProtocolFeature::BlockHeightForReceiptId.protocol_version(),
&base,
&extra_base,
&extra_base,
block_height,
salt,
),
create_hash_upgradable(
ProtocolFeature::BlockHeightForReceiptId.protocol_version(),
&base,
&other_extra_base,
&other_extra_base,
block_height,
salt
)
);
// Check that for protocol versions pre BlockHeightForReceiptId, the hash does not depend on block height.
assert_eq!(
create_hash_upgradable(
ProtocolFeature::BlockHeightForReceiptId.protocol_version() - 1,
&base,
&extra_base,
&other_extra_base,
block_height,
salt,
),
create_hash_upgradable(
ProtocolFeature::BlockHeightForReceiptId.protocol_version() - 1,
&base,
&extra_base,
&other_extra_base,
other_block_height,
salt,
)
);
// Check that for protocol versions post BlockHeightForReceiptId, the hash changes if block height changes.
assert_ne!(
create_hash_upgradable(
ProtocolFeature::BlockHeightForReceiptId.protocol_version(),
&base,
&extra_base,
&other_extra_base,
block_height,
salt,
),
create_hash_upgradable(
ProtocolFeature::BlockHeightForReceiptId.protocol_version(),
&base,
&extra_base,
&other_extra_base,
other_block_height,
salt,
)
);
}
}
1 change: 1 addition & 0 deletions runtime/runtime/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ pub(crate) fn action_function_call(
apply_state.epoch_id,
apply_state.prev_block_hash,
apply_state.block_hash,
apply_state.block_height,
epoch_info_provider,
apply_state.current_protocol_version,
);
Expand Down
6 changes: 5 additions & 1 deletion runtime/runtime/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use near_primitives::checked_feature;
use near_primitives::errors::{EpochError, StorageError};
use near_primitives::hash::CryptoHash;
use near_primitives::trie_key::{trie_key_parsers, TrieKey};
use near_primitives::types::{AccountId, Balance, EpochId, EpochInfoProvider, Gas};
use near_primitives::types::{AccountId, Balance, BlockHeight, EpochId, EpochInfoProvider, Gas};
use near_primitives::utils::create_receipt_id_from_action_hash;
use near_primitives::version::ProtocolVersion;
use near_store::contract::ContractStorage;
Expand All @@ -28,6 +28,7 @@ pub struct RuntimeExt<'a> {
epoch_id: EpochId,
prev_block_hash: CryptoHash,
last_block_hash: CryptoHash,
block_height: BlockHeight,
epoch_info_provider: &'a dyn EpochInfoProvider,
current_protocol_version: ProtocolVersion,
}
Expand Down Expand Up @@ -70,6 +71,7 @@ impl<'a> RuntimeExt<'a> {
epoch_id: EpochId,
prev_block_hash: CryptoHash,
last_block_hash: CryptoHash,
block_height: BlockHeight,
epoch_info_provider: &'a dyn EpochInfoProvider,
current_protocol_version: ProtocolVersion,
) -> Self {
Expand All @@ -83,6 +85,7 @@ impl<'a> RuntimeExt<'a> {
epoch_id,
prev_block_hash,
last_block_hash,
block_height,
epoch_info_provider,
current_protocol_version,
}
Expand Down Expand Up @@ -188,6 +191,7 @@ impl<'a> External for RuntimeExt<'a> {
&self.action_hash,
&self.prev_block_hash,
&self.last_block_hash,
self.block_height,
self.data_count as usize,
);
self.data_count += 1;
Expand Down
Loading

0 comments on commit c2d7028

Please sign in to comment.