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

[Epoch Sync] EpochSyncProof validation #12020

Merged
merged 8 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions chain/chain-primitives/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ pub enum Error {
/// Resharding error.
#[error("Resharding Error: {0}")]
ReshardingError(String),
/// EpochSyncProof validation error.
#[error("EpochSyncProof Validation Error: {0}")]
InvalidEpochSyncProof(String),
/// Anything else
#[error("Other Error: {0}")]
Other(String),
Expand Down Expand Up @@ -300,6 +303,7 @@ impl Error {
| Error::MaliciousChallenge
| Error::IncorrectNumberOfChunkHeaders
| Error::InvalidEpochHash
| Error::InvalidEpochSyncProof(_)
| Error::InvalidNextBPHash
| Error::NotEnoughApprovals
| Error::InvalidFinalityInfo
Expand Down Expand Up @@ -377,6 +381,7 @@ impl Error {
Error::MaliciousChallenge => "malicious_challenge",
Error::IncorrectNumberOfChunkHeaders => "incorrect_number_of_chunk_headers",
Error::InvalidEpochHash => "invalid_epoch_hash",
Error::InvalidEpochSyncProof(_) => "invalid_epoch_sync_proof",
Error::InvalidNextBPHash => "invalid_next_bp_hash",
Error::NotEnoughApprovals => "not_enough_approvals",
Error::InvalidFinalityInfo => "invalid_finality_info",
Expand Down
214 changes: 30 additions & 184 deletions chain/chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::state_snapshot_actor::SnapshotCallbacks;
use crate::stateless_validation::chunk_endorsement::{
validate_chunk_endorsements_in_block, validate_chunk_endorsements_in_header,
};
use crate::store::{ChainStore, ChainStoreAccess, ChainStoreUpdate};
use crate::store::{ChainStore, ChainStoreAccess, ChainStoreUpdate, MerkleProofAccess};
use crate::types::{
AcceptedBlock, ApplyChunkBlockContext, BlockEconomicsConfig, ChainConfig, RuntimeAdapter,
StorageDataSource,
Expand Down Expand Up @@ -58,9 +58,7 @@ use near_primitives::congestion_info::CongestionInfo;
use near_primitives::epoch_block_info::BlockInfo;
use near_primitives::errors::EpochError;
use near_primitives::hash::{hash, CryptoHash};
use near_primitives::merkle::{
combine_hash, merklize, verify_path, Direction, MerklePath, MerklePathItem, PartialMerkleTree,
};
use near_primitives::merkle::{merklize, verify_path, PartialMerkleTree};
use near_primitives::receipt::Receipt;
use near_primitives::sandbox::state_patch::SandboxStatePatch;
use near_primitives::shard_layout::{account_id_to_shard_id, ShardLayout, ShardUId};
Expand All @@ -78,6 +76,7 @@ use near_primitives::stateless_validation::state_witness::{
};
use near_primitives::transaction::{ExecutionOutcomeWithIdAndProof, SignedTransaction};
use near_primitives::types::chunk_extra::ChunkExtra;
use near_primitives::types::validator_stake::ValidatorStake;
use near_primitives::types::{
AccountId, Balance, BlockExtra, BlockHeight, BlockHeightDelta, EpochId, Gas, MerkleHash,
NumBlocks, ShardId, StateRoot,
Expand Down Expand Up @@ -586,13 +585,20 @@ impl Chain {
last_known_hash: &CryptoHash,
) -> Result<CryptoHash, Error> {
let bps = epoch_manager.get_epoch_block_producers_ordered(&epoch_id, last_known_hash)?;
let validator_stakes = bps.into_iter().map(|(bp, _)| bp).collect_vec();
let protocol_version = epoch_manager.get_epoch_protocol_version(&prev_epoch_id)?;
Self::compute_bp_hash_from_validator_stakes(&validator_stakes, protocol_version)
}

pub fn compute_bp_hash_from_validator_stakes(
validator_stakes: &Vec<ValidatorStake>,
protocol_version: ProtocolVersion,
) -> Result<CryptoHash, Error> {
if checked_feature!("stable", BlockHeaderV3, protocol_version) {
let validator_stakes = bps.into_iter().map(|(bp, _)| bp);
Ok(CryptoHash::hash_borsh_iter(validator_stakes))
} else {
let validator_stakes = bps.into_iter().map(|(bp, _)| bp.into_v1());
Ok(CryptoHash::hash_borsh_iter(validator_stakes))
let stakes = validator_stakes.into_iter().map(|stake| stake.clone().into_v1());
Ok(CryptoHash::hash_borsh_iter(stakes))
}
}

Expand Down Expand Up @@ -1521,6 +1527,16 @@ impl Chain {
(accepted_blocks, errors)
}

fn chain_update(&mut self) -> ChainUpdate {
ChainUpdate::new(
&mut self.chain_store,
self.epoch_manager.clone(),
self.runtime_adapter.clone(),
self.doomslug_threshold_mode,
self.transaction_validity_period,
)
}

/// Process challenge to invalidate chain. This is done between blocks to unroll the chain as
/// soon as possible and allow next block producer to skip invalid blocks.
pub fn process_challenge(&mut self, challenge: &Challenge) {
Expand Down Expand Up @@ -4011,186 +4027,16 @@ fn get_should_apply_chunk(
}
}

/// Implement block merkle proof retrieval.
impl Chain {
fn combine_maybe_hashes(
hash1: Option<MerkleHash>,
hash2: Option<MerkleHash>,
) -> Option<MerkleHash> {
match (hash1, hash2) {
(Some(h1), Some(h2)) => Some(combine_hash(&h1, &h2)),
(Some(h1), None) => Some(h1),
(None, Some(_)) => {
debug_assert!(false, "Inconsistent state in merkle proof computation: left node is None but right node exists");
None
}
_ => None,
}
}

fn chain_update(&mut self) -> ChainUpdate {
ChainUpdate::new(
&mut self.chain_store,
self.epoch_manager.clone(),
self.runtime_adapter.clone(),
self.doomslug_threshold_mode,
self.transaction_validity_period,
)
}

/// Get node at given position (index, level). If the node does not exist, return `None`.
fn get_merkle_tree_node(
&self,
index: u64,
level: u64,
counter: u64,
tree_size: u64,
tree_nodes: &mut HashMap<(u64, u64), Option<MerkleHash>>,
) -> Result<Option<MerkleHash>, Error> {
if let Some(hash) = tree_nodes.get(&(index, level)) {
Ok(*hash)
} else {
if level == 0 {
let maybe_hash = if index >= tree_size {
None
} else {
Some(self.chain_store().get_block_hash_from_ordinal(index)?)
};
tree_nodes.insert((index, level), maybe_hash);
Ok(maybe_hash)
} else {
let cur_tree_size = (index + 1) * counter;
let maybe_hash = if cur_tree_size > tree_size {
if index * counter <= tree_size {
let left_hash = self.get_merkle_tree_node(
index * 2,
level - 1,
counter / 2,
tree_size,
tree_nodes,
)?;
let right_hash = self.reconstruct_merkle_tree_node(
index * 2 + 1,
level - 1,
counter / 2,
tree_size,
tree_nodes,
)?;
Self::combine_maybe_hashes(left_hash, right_hash)
} else {
None
}
} else {
Some(
*self
.chain_store()
.get_block_merkle_tree_from_ordinal(cur_tree_size)?
.get_path()
.last()
.ok_or_else(|| Error::Other("Merkle tree node missing".to_string()))?,
)
};
tree_nodes.insert((index, level), maybe_hash);
Ok(maybe_hash)
}
}
}

/// Reconstruct node at given position (index, level). If the node does not exist, return `None`.
fn reconstruct_merkle_tree_node(
impl MerkleProofAccess for Chain {
fn get_block_merkle_tree(
&self,
index: u64,
level: u64,
counter: u64,
tree_size: u64,
tree_nodes: &mut HashMap<(u64, u64), Option<MerkleHash>>,
) -> Result<Option<MerkleHash>, Error> {
if let Some(hash) = tree_nodes.get(&(index, level)) {
Ok(*hash)
} else {
if level == 0 {
let maybe_hash = if index >= tree_size {
None
} else {
Some(self.chain_store().get_block_hash_from_ordinal(index)?)
};
tree_nodes.insert((index, level), maybe_hash);
Ok(maybe_hash)
} else {
let left_hash = self.get_merkle_tree_node(
index * 2,
level - 1,
counter / 2,
tree_size,
tree_nodes,
)?;
let right_hash = self.reconstruct_merkle_tree_node(
index * 2 + 1,
level - 1,
counter / 2,
tree_size,
tree_nodes,
)?;
let maybe_hash = Self::combine_maybe_hashes(left_hash, right_hash);
tree_nodes.insert((index, level), maybe_hash);

Ok(maybe_hash)
}
}
block_hash: &CryptoHash,
) -> Result<Arc<PartialMerkleTree>, Error> {
ChainStoreAccess::get_block_merkle_tree(self.chain_store(), block_hash)
}

/// Get merkle proof for block with hash `block_hash` in the merkle tree of `head_block_hash`.
pub fn get_block_proof(
&self,
block_hash: &CryptoHash,
head_block_hash: &CryptoHash,
) -> Result<MerklePath, Error> {
let leaf_index = self.chain_store().get_block_merkle_tree(block_hash)?.size();
let tree_size = self.chain_store().get_block_merkle_tree(head_block_hash)?.size();
if leaf_index >= tree_size {
if block_hash == head_block_hash {
// special case if the block to prove is the same as head
return Ok(vec![]);
}
return Err(Error::Other(format!(
"block {} is ahead of head block {}",
block_hash, head_block_hash
)));
}
let mut level = 0;
let mut counter = 1;
let mut cur_index = leaf_index;
let mut path = vec![];
let mut tree_nodes = HashMap::new();
let mut iter = tree_size;
while iter > 1 {
if cur_index % 2 == 0 {
cur_index += 1
} else {
cur_index -= 1;
}
let direction = if cur_index % 2 == 0 { Direction::Left } else { Direction::Right };
let maybe_hash = if cur_index % 2 == 1 {
// node not immediately available. Needs to be reconstructed
self.reconstruct_merkle_tree_node(
cur_index,
level,
counter,
tree_size,
&mut tree_nodes,
)?
} else {
self.get_merkle_tree_node(cur_index, level, counter, tree_size, &mut tree_nodes)?
};
if let Some(hash) = maybe_hash {
path.push(MerklePathItem { hash, direction });
}
cur_index /= 2;
iter = (iter + 1) / 2;
level += 1;
counter *= 2;
}
Ok(path)
fn get_block_hash_from_ordinal(&self, block_ordinal: NumBlocks) -> Result<CryptoHash, Error> {
ChainStoreAccess::get_block_hash_from_ordinal(self.chain_store(), block_ordinal)
}
}

Expand Down
4 changes: 3 additions & 1 deletion chain/chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ pub use doomslug::{Doomslug, DoomslugBlockProductionReadiness, DoomslugThreshold
pub use lightclient::{create_light_client_block_view, get_epoch_block_producers_view};
pub use near_chain_primitives::{self, Error};
pub use near_primitives::receipt::ReceiptResult;
pub use store::{ChainStore, ChainStoreAccess, ChainStoreUpdate, LatestWitnessesInfo};
pub use store::{
ChainStore, ChainStoreAccess, ChainStoreUpdate, LatestWitnessesInfo, MerkleProofAccess,
};
pub use store_validator::{ErrorMessage, StoreValidator};
pub use types::{Block, BlockHeader, BlockStatus, ChainGenesis, LatestKnown, Provenance};

Expand Down
Loading
Loading