Skip to content

Commit

Permalink
feat(chain): add block merkle root to BlockHeaderInnerLite (#2643)
Browse files Browse the repository at this point in the history
Add block merkle root to block header so that we light client can verify transaction outcomes in blocks that are not known by the light client. Resolves #2632.

Test plan
----------
* Unit tests `test_merkle_tree` and `test_invalid_block_merkle_root`.
* Manually trigger nightly to see if anything breaks.
  • Loading branch information
bowenwang1996 authored May 18, 2020
1 parent 7283fb2 commit f734525
Show file tree
Hide file tree
Showing 26 changed files with 400 additions and 133 deletions.
13 changes: 10 additions & 3 deletions chain/chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ impl Chain {
vec![],
chain_genesis.total_supply,
)?;
store_update.save_block_header(genesis.header.clone());
store_update.save_block_header(genesis.header.clone())?;
store_update.save_block(genesis.clone());
store_update.save_block_extra(
&genesis.hash(),
Expand Down Expand Up @@ -784,7 +784,7 @@ impl Chain {
}

chain_update.validate_header(header, &Provenance::SYNC, on_challenge)?;
chain_update.chain_store_update.save_block_header(header.clone());
chain_update.chain_store_update.save_block_header(header.clone())?;
chain_update.commit()?;

// Add validator proposals for given header.
Expand Down Expand Up @@ -2826,7 +2826,7 @@ impl<'a> ChainUpdate<'a> {
F: FnMut(ChallengeBody) -> (),
{
self.validate_header(header, provenance, on_challenge)?;
self.chain_store_update.save_block_header(header.clone());
self.chain_store_update.save_block_header(header.clone())?;
self.update_header_head_if_not_challenged(header)?;
Ok(())
}
Expand Down Expand Up @@ -2968,6 +2968,13 @@ impl<'a> ChainUpdate<'a> {
{
return Err(ErrorKind::InvalidFinalityInfo.into());
}

let mut block_merkle_tree =
self.chain_store_update.get_block_merkle_tree(&header.prev_hash)?.clone();
block_merkle_tree.insert(header.prev_hash);
if block_merkle_tree.root() != header.inner_lite.block_merkle_root {
return Err(ErrorKind::InvalidBlockMerkleRoot.into());
}
}

Ok(())
Expand Down
4 changes: 4 additions & 0 deletions chain/chain/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ pub enum ErrorKind {
/// Invalid VRF proof, or incorrect random_output in the header
#[fail(display = "Invalid Randomness Beacon Output")]
InvalidRandomnessBeaconOutput,
/// Invalid block merkle root.
#[fail(display = "Invalid Block Merkle Root")]
InvalidBlockMerkleRoot,
/// Someone is not a validator. Usually happens in signature verification
#[fail(display = "Not A Validator")]
NotAValidator,
Expand Down Expand Up @@ -249,6 +252,7 @@ impl Error {
| ErrorKind::InvalidShardId(_)
| ErrorKind::InvalidStateRequest(_)
| ErrorKind::InvalidRandomnessBeaconOutput
| ErrorKind::InvalidBlockMerkleRoot
| ErrorKind::NotAValidator => true,
}
}
Expand Down
104 changes: 86 additions & 18 deletions chain/chain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use tracing::debug;
use near_primitives::block::Approval;
use near_primitives::errors::InvalidTxError;
use near_primitives::hash::CryptoHash;
use near_primitives::merkle::MerklePath;
use near_primitives::merkle::{MerklePath, PartialMerkleTree};
use near_primitives::receipt::Receipt;
use near_primitives::sharding::{
ChunkHash, EncodedShardChunk, PartialEncodedChunk, ReceiptProof, ShardChunk, ShardChunkHeader,
Expand All @@ -28,13 +28,14 @@ use near_primitives::types::{
use near_primitives::utils::{index_to_bytes, to_timestamp};
use near_primitives::views::LightClientBlockView;
use near_store::{
read_with_cache, ColBlock, ColBlockExtra, ColBlockHeader, ColBlockHeight, ColBlockMisc,
ColBlockPerHeight, ColBlockRefCount, ColBlocksToCatchup, ColChallengedBlocks, ColChunkExtra,
ColChunkPerHeightShard, ColChunks, ColEpochLightClientBlocks, ColIncomingReceipts,
ColInvalidChunks, ColLastBlockWithNewChunk, ColNextBlockHashes, ColNextBlockWithNewChunk,
ColOutgoingReceipts, ColPartialChunks, ColReceiptIdToShardId, ColState, ColStateChanges,
ColStateDlInfos, ColStateHeaders, ColTransactionResult, ColTransactions, ColTrieChanges,
KeyForStateChanges, ShardTries, Store, StoreUpdate, TrieChanges, WrappedTrieChanges,
read_with_cache, ColBlock, ColBlockExtra, ColBlockHeader, ColBlockHeight, ColBlockMerkleTree,
ColBlockMisc, ColBlockPerHeight, ColBlockRefCount, ColBlocksToCatchup, ColChallengedBlocks,
ColChunkExtra, ColChunkPerHeightShard, ColChunks, ColEpochLightClientBlocks,
ColIncomingReceipts, ColInvalidChunks, ColLastBlockWithNewChunk, ColNextBlockHashes,
ColNextBlockWithNewChunk, ColOutgoingReceipts, ColPartialChunks, ColReceiptIdToShardId,
ColState, ColStateChanges, ColStateDlInfos, ColStateHeaders, ColTransactionResult,
ColTransactions, ColTrieChanges, KeyForStateChanges, ShardTries, Store, StoreUpdate,
TrieChanges, WrappedTrieChanges,
};

use crate::byzantine_assert;
Expand Down Expand Up @@ -253,6 +254,11 @@ pub trait ChainStoreAccess {
) -> Result<StateChanges, Error>;

fn get_genesis_height(&self) -> BlockHeight;

fn get_block_merkle_tree(
&mut self,
block_hash: &CryptoHash,
) -> Result<&PartialMerkleTree, Error>;
}

/// All chain-related database operations.
Expand Down Expand Up @@ -307,6 +313,8 @@ pub struct ChainStore {
transactions: SizedCache<Vec<u8>, SignedTransaction>,
/// Cache with height to hash on any chain.
block_refcounts: SizedCache<Vec<u8>, u64>,
/// Cache of block hash -> block merkle tree at the current block
block_merkle_tree: SizedCache<Vec<u8>, PartialMerkleTree>,
}

pub fn option_to_not_found<T>(res: io::Result<Option<T>>, field_name: &str) -> Result<T, Error> {
Expand Down Expand Up @@ -345,6 +353,7 @@ impl ChainStore {
next_block_with_new_chunk: SizedCache::with_size(CHUNK_CACHE_SIZE),
last_block_with_new_chunk: SizedCache::with_size(CHUNK_CACHE_SIZE),
transactions: SizedCache::with_size(CHUNK_CACHE_SIZE),
block_merkle_tree: SizedCache::with_size(CACHE_SIZE),
}
}

Expand Down Expand Up @@ -961,6 +970,21 @@ impl ChainStoreAccess for ChainStore {
fn get_genesis_height(&self) -> BlockHeight {
self.genesis_height
}

fn get_block_merkle_tree(
&mut self,
block_hash: &CryptoHash,
) -> Result<&PartialMerkleTree, Error> {
option_to_not_found(
read_with_cache(
&*self.store,
ColBlockMerkleTree,
&mut self.block_merkle_tree,
block_hash.as_ref(),
),
&format!("BLOCK MERKLE TREE: {}", block_hash),
)
}
}

/// Cache update for ChainStore
Expand All @@ -987,6 +1011,7 @@ struct ChainStoreCacheUpdate {
last_block_with_new_chunk: HashMap<ShardId, CryptoHash>,
transactions: HashSet<SignedTransaction>,
block_refcounts: HashMap<CryptoHash, u64>,
block_merkle_tree: HashMap<CryptoHash, PartialMerkleTree>,
}

impl ChainStoreCacheUpdate {
Expand Down Expand Up @@ -1014,6 +1039,7 @@ impl ChainStoreCacheUpdate {
last_block_with_new_chunk: Default::default(),
transactions: Default::default(),
block_refcounts: HashMap::default(),
block_merkle_tree: HashMap::default(),
}
}
}
Expand Down Expand Up @@ -1435,6 +1461,17 @@ impl<'a> ChainStoreAccess for ChainStoreUpdate<'a> {
fn get_genesis_height(&self) -> BlockHeight {
self.chain_store.genesis_height
}

fn get_block_merkle_tree(
&mut self,
block_hash: &CryptoHash,
) -> Result<&PartialMerkleTree, Error> {
if let Some(merkle_tree) = self.chain_store_cache_update.block_merkle_tree.get(block_hash) {
Ok(merkle_tree)
} else {
self.chain_store.get_block_merkle_tree(block_hash)
}
}
}

impl<'a> ChainStoreUpdate<'a> {
Expand Down Expand Up @@ -1576,8 +1613,30 @@ impl<'a> ChainStoreUpdate<'a> {
self.chain_store_cache_update.partial_chunks.insert(chunk_hash.clone(), partial_chunk);
}

pub fn save_block_header(&mut self, header: BlockHeader) {
pub fn save_block_merkle_tree(
&mut self,
block_hash: CryptoHash,
block_merkle_tree: PartialMerkleTree,
) {
self.chain_store_cache_update.block_merkle_tree.insert(block_hash, block_merkle_tree);
}

fn update_and_save_block_merkle_tree(&mut self, header: &BlockHeader) -> Result<(), Error> {
let prev_hash = header.prev_hash;
if prev_hash == CryptoHash::default() {
self.save_block_merkle_tree(header.hash(), PartialMerkleTree::default());
} else {
let mut block_merkle_tree = self.get_block_merkle_tree(&prev_hash)?.clone();
block_merkle_tree.insert(prev_hash);
self.save_block_merkle_tree(header.hash(), block_merkle_tree);
}
Ok(())
}

pub fn save_block_header(&mut self, header: BlockHeader) -> Result<(), Error> {
self.update_and_save_block_merkle_tree(&header)?;
self.chain_store_cache_update.headers.insert(header.hash(), header);
Ok(())
}

pub fn save_next_block_hash(&mut self, hash: &CryptoHash, next_hash: CryptoHash) {
Expand Down Expand Up @@ -2072,6 +2131,11 @@ impl<'a> ChainStoreUpdate<'a> {
for (block_hash, refcount) in self.chain_store_cache_update.block_refcounts.iter() {
store_update.set_ser(ColBlockRefCount, block_hash.as_ref(), refcount)?;
}
for (block_hash, block_merkle_tree) in
self.chain_store_cache_update.block_merkle_tree.iter()
{
store_update.set_ser(ColBlockMerkleTree, block_hash.as_ref(), block_merkle_tree)?;
}
for mut wrapped_trie_changes in self.trie_changes.drain(..) {
wrapped_trie_changes
.wrapped_into(&mut store_update)
Expand Down Expand Up @@ -2183,6 +2247,7 @@ impl<'a> ChainStoreUpdate<'a> {
last_block_with_new_chunk,
transactions,
block_refcounts,
block_merkle_tree,
} = self.chain_store_cache_update;
for (hash, block) in blocks {
self.chain_store.blocks.cache_set(hash.into(), block);
Expand Down Expand Up @@ -2267,6 +2332,9 @@ impl<'a> ChainStoreUpdate<'a> {
for (block_hash, refcount) in block_refcounts {
self.chain_store.block_refcounts.cache_set(block_hash.into(), refcount);
}
for (block_hash, merkle_tree) in block_merkle_tree {
self.chain_store.block_merkle_tree.cache_set(block_hash.into(), merkle_tree);
}

Ok(())
}
Expand Down Expand Up @@ -2322,7 +2390,7 @@ mod tests {
Arc::new(InMemoryValidatorSigner::from_seed("test1", KeyType::ED25519, "test1"));
let short_fork = vec![Block::empty_with_height(&genesis, 1, &*signer.clone())];
let mut store_update = chain.mut_store().store_update();
store_update.save_block_header(short_fork[0].header.clone());
store_update.save_block_header(short_fork[0].header.clone()).unwrap();
store_update.commit().unwrap();

let short_fork_head = short_fork[0].clone().header;
Expand All @@ -2340,7 +2408,7 @@ mod tests {
for i in 1..(transaction_validity_period + 3) {
let block = Block::empty_with_height(&prev_block, i, &*signer.clone());
prev_block = block.clone();
store_update.save_block_header(block.header.clone());
store_update.save_block_header(block.header.clone()).unwrap();
store_update
.update_height_if_not_challenged(block.header.inner_lite.height, block.hash())
.unwrap();
Expand Down Expand Up @@ -2381,7 +2449,7 @@ mod tests {
for i in 1..(transaction_validity_period + 2) {
let block = Block::empty_with_height(&prev_block, i, &*signer.clone());
prev_block = block.clone();
store_update.save_block_header(block.header.clone());
store_update.save_block_header(block.header.clone()).unwrap();
store_update
.update_height_if_not_challenged(block.header.inner_lite.height, block.hash())
.unwrap();
Expand All @@ -2404,7 +2472,7 @@ mod tests {
&*signer.clone(),
);
let mut store_update = chain.mut_store().store_update();
store_update.save_block_header(new_block.header.clone());
store_update.save_block_header(new_block.header.clone()).unwrap();
store_update
.update_height_if_not_challenged(new_block.header.inner_lite.height, new_block.hash())
.unwrap();
Expand Down Expand Up @@ -2432,7 +2500,7 @@ mod tests {
for i in 1..(transaction_validity_period + 2) {
let block = Block::empty_with_height(&prev_block, i, &*signer.clone());
prev_block = block.clone();
store_update.save_block_header(block.header.clone());
store_update.save_block_header(block.header.clone()).unwrap();
short_fork.push(block);
}
store_update.commit().unwrap();
Expand All @@ -2452,7 +2520,7 @@ mod tests {
for i in 1..(transaction_validity_period * 5) {
let block = Block::empty_with_height(&prev_block, i, &*signer.clone());
prev_block = block.clone();
store_update.save_block_header(block.header.clone());
store_update.save_block_header(block.header.clone()).unwrap();
long_fork.push(block);
}
store_update.commit().unwrap();
Expand Down Expand Up @@ -2523,7 +2591,7 @@ mod tests {
store_update.save_block(block.clone());
store_update.inc_block_refcount(&block.header.prev_hash).unwrap();
store_update.save_head(&Tip::from_header(&block.header)).unwrap();
store_update.save_block_header(block.header.clone());
store_update.save_block_header(block.header.clone()).unwrap();
store_update
.chain_store_cache_update
.height_to_hashes
Expand Down Expand Up @@ -2574,7 +2642,7 @@ mod tests {
store_update.save_block(block.clone());
store_update.inc_block_refcount(&block.header.prev_hash).unwrap();
store_update.save_head(&Tip::from_header(&block.header)).unwrap();
store_update.save_block_header(block.header.clone());
store_update.save_block_header(block.header.clone()).unwrap();
store_update
.chain_store_cache_update
.height_to_hashes
Expand Down Expand Up @@ -2639,7 +2707,7 @@ mod tests {
store_update.save_block(block.clone());
store_update.inc_block_refcount(&block.header.prev_hash).unwrap();
store_update.save_head(&Tip::from_header(&block.header)).unwrap();
store_update.save_block_header(block.header.clone());
store_update.save_block_header(block.header.clone()).unwrap();
store_update
.chain_store_cache_update
.height_to_hashes
Expand Down
1 change: 1 addition & 0 deletions chain/chain/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ mod tests {
approvals,
&signer,
genesis.header.inner_lite.next_bp_hash,
CryptoHash::default(),
);
b2.header.verify_block_producer(&signer.public_key());
}
Expand Down
9 changes: 7 additions & 2 deletions chain/chain/tests/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod tests {
use near_chain::DoomslugThresholdMode;
use near_crypto::KeyType;
use near_primitives::block::Block;
use near_primitives::merkle::PartialMerkleTree;
use near_primitives::types::{NumBlocks, StateRoot};
use near_primitives::validator_signer::InMemoryValidatorSigner;
use near_store::test_utils::{create_test_store, gen_changes};
Expand Down Expand Up @@ -53,14 +54,18 @@ mod tests {
let signer =
Arc::new(InMemoryValidatorSigner::from_seed("test1", KeyType::ED25519, "test1"));
let num_shards = prev_state_roots.len() as u64;
for _ in 0..num_blocks {
for i in 0..num_blocks {
let block = Block::empty(&prev_block, &*signer);

let head = chain.head().unwrap();
let mut store_update = chain.mut_store().store_update();
if i == 0 {
store_update
.save_block_merkle_tree(prev_block.hash(), PartialMerkleTree::default());
}
store_update.save_block(block.clone());
store_update.inc_block_refcount(&block.header.prev_hash).unwrap();
store_update.save_block_header(block.header.clone());
store_update.save_block_header(block.header.clone()).unwrap();
let tip = Tip::from_header(&block.header);
if head.height < tip.height {
store_update.save_head(&tip).unwrap();
Expand Down
2 changes: 2 additions & 0 deletions chain/chain/tests/simple_chain.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use near_chain::test_utils::setup;
use near_chain::{Block, ChainStoreAccess, ErrorKind, Provenance};
use near_logger_utils::init_test_logger;
use near_primitives::hash::CryptoHash;
use num_rational::Rational;

#[test]
Expand Down Expand Up @@ -50,6 +51,7 @@ fn build_chain_with_orhpans() {
vec![],
&*signer,
last_block.header.inner_lite.next_bp_hash.clone(),
CryptoHash::default(),
);
assert_eq!(
chain
Expand Down
8 changes: 7 additions & 1 deletion chain/chain/tests/sync_chain.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
use near_chain::test_utils::setup;
use near_chain::Block;
use near_logger_utils::init_test_logger;
use near_primitives::merkle::PartialMerkleTree;

#[test]
fn chain_sync_headers() {
init_test_logger();
let (mut chain, _, bls_signer) = setup();
assert_eq!(chain.sync_head().unwrap().height, 0);
let mut blocks = vec![chain.get_block(&chain.genesis().hash()).unwrap().clone()];
let mut block_merkle_tree = PartialMerkleTree::default();
for i in 0..4 {
blocks.push(Block::empty(&blocks[i], &*bls_signer));
blocks.push(Block::empty_with_block_merkle_tree(
&blocks[i],
&*bls_signer,
&mut block_merkle_tree,
));
}
chain
.sync_block_headers(blocks.drain(1..).map(|block| block.header).collect(), |_| {
Expand Down
Loading

0 comments on commit f734525

Please sign in to comment.