mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 11:31:08 +03:00
Replace "header by height" index with reads into the header MMR (#2030)
* replace header_by_height index with reads into the header MMR * rustfmt * cleanup * cleanup chain tests * fix locate_headers to stop on our max header * fix the deadlock in comact_blocks_db... * cleanup and docs/comments
This commit is contained in:
parent
e356280841
commit
8e3a3e1a40
6 changed files with 132 additions and 227 deletions
|
@ -26,7 +26,6 @@ use lmdb;
|
||||||
|
|
||||||
use core::core::hash::{Hash, Hashed, ZERO_HASH};
|
use core::core::hash::{Hash, Hashed, ZERO_HASH};
|
||||||
use core::core::merkle_proof::MerkleProof;
|
use core::core::merkle_proof::MerkleProof;
|
||||||
use core::core::pmmr;
|
|
||||||
use core::core::verifier_cache::VerifierCache;
|
use core::core::verifier_cache::VerifierCache;
|
||||||
use core::core::{
|
use core::core::{
|
||||||
Block, BlockHeader, BlockSums, Output, OutputIdentifier, Transaction, TxKernelEntry,
|
Block, BlockHeader, BlockSums, Output, OutputIdentifier, Transaction, TxKernelEntry,
|
||||||
|
@ -744,12 +743,18 @@ impl Chain {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// TODO - Investigate finding the "common header" by comparing header_mmr and
|
||||||
|
// sync_mmr (bytes will be identical up to the common header).
|
||||||
|
//
|
||||||
while let Ok(header) = current {
|
while let Ok(header) = current {
|
||||||
// break out of the while loop when we find a header common
|
// break out of the while loop when we find a header common
|
||||||
// between the header chain and the current body chain
|
// between the header chain and the current body chain
|
||||||
|
if header.height <= body_head.height {
|
||||||
if let Ok(_) = self.is_on_current_chain(&header) {
|
if let Ok(_) = self.is_on_current_chain(&header) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oldest_height = header.height;
|
oldest_height = header.height;
|
||||||
oldest_hash = header.hash();
|
oldest_hash = header.hash();
|
||||||
|
@ -839,8 +844,6 @@ impl Chain {
|
||||||
{
|
{
|
||||||
let tip = Tip::from_header(&header);
|
let tip = Tip::from_header(&header);
|
||||||
batch.save_body_head(&tip)?;
|
batch.save_body_head(&tip)?;
|
||||||
batch.save_header_height(&header)?;
|
|
||||||
batch.build_by_height_index(&header, true)?;
|
|
||||||
|
|
||||||
// Reset the body tail to the body head after a txhashset write
|
// Reset the body tail to the body head after a txhashset write
|
||||||
batch.save_body_tail(&tip)?;
|
batch.save_body_tail(&tip)?;
|
||||||
|
@ -908,8 +911,11 @@ impl Chain {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
|
let tail = self.get_header_by_height(head.height - horizon)?;
|
||||||
|
let mut current = self.get_header_by_height(head.height - horizon - 1)?;
|
||||||
|
|
||||||
let batch = self.store.batch()?;
|
let batch = self.store.batch()?;
|
||||||
let mut current = batch.get_header_by_height(head.height - horizon - 1)?;
|
|
||||||
loop {
|
loop {
|
||||||
// Go to the store directly so we can handle NotFoundErr robustly.
|
// Go to the store directly so we can handle NotFoundErr robustly.
|
||||||
match self.store.get_block(¤t.hash()) {
|
match self.store.get_block(¤t.hash()) {
|
||||||
|
@ -935,7 +941,6 @@ impl Chain {
|
||||||
Err(e) => return Err(From::from(e)),
|
Err(e) => return Err(From::from(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tail = batch.get_header_by_height(head.height - horizon)?;
|
|
||||||
batch.save_body_tail(&Tip::from_header(&tail))?;
|
batch.save_body_tail(&Tip::from_header(&tail))?;
|
||||||
batch.commit()?;
|
batch.commit()?;
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -1075,9 +1080,14 @@ impl Chain {
|
||||||
|
|
||||||
/// Gets the block header at the provided height
|
/// Gets the block header at the provided height
|
||||||
pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> {
|
pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> {
|
||||||
self.store
|
let mut txhashset = self.txhashset.write();
|
||||||
.get_header_by_height(height)
|
let mut batch = self.store.batch()?;
|
||||||
.map_err(|e| ErrorKind::StoreErr(e, "chain get header by height".to_owned()).into())
|
let header = txhashset::header_extending(&mut txhashset, &mut batch, |extension| {
|
||||||
|
let header = extension.get_header_by_height(height)?;
|
||||||
|
Ok(header)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the block header in which a given output appears in the txhashset
|
/// Gets the block header in which a given output appears in the txhashset
|
||||||
|
@ -1118,9 +1128,12 @@ impl Chain {
|
||||||
/// Checks the header_by_height index to verify the header is where we say
|
/// Checks the header_by_height index to verify the header is where we say
|
||||||
/// it is
|
/// it is
|
||||||
pub fn is_on_current_chain(&self, header: &BlockHeader) -> Result<(), Error> {
|
pub fn is_on_current_chain(&self, header: &BlockHeader) -> Result<(), Error> {
|
||||||
self.store
|
let chain_header = self.get_header_by_height(header.height)?;
|
||||||
.is_on_current_chain(header)
|
if chain_header.hash() == header.hash() {
|
||||||
.map_err(|e| ErrorKind::StoreErr(e, "chain is_on_current_chain".to_owned()).into())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::Other(format!("not on current chain")).into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the tip of the current "sync" header chain.
|
/// Get the tip of the current "sync" header chain.
|
||||||
|
@ -1179,10 +1192,9 @@ fn setup_head(
|
||||||
// If we have no header MMR then rebuild as necessary.
|
// If we have no header MMR then rebuild as necessary.
|
||||||
// Supports old nodes with no header MMR.
|
// Supports old nodes with no header MMR.
|
||||||
txhashset::header_extending(txhashset, &mut batch, |extension| {
|
txhashset::header_extending(txhashset, &mut batch, |extension| {
|
||||||
let pos = pmmr::insertion_to_pmmr_index(head.height + 1);
|
let needs_rebuild = match extension.get_header_by_height(head.height) {
|
||||||
let needs_rebuild = match extension.get_header_hash(pos) {
|
Ok(header) => header.hash() != head.last_block_h,
|
||||||
None => true,
|
Err(_) => true,
|
||||||
Some(hash) => hash != head.last_block_h,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if needs_rebuild {
|
if needs_rebuild {
|
||||||
|
@ -1237,7 +1249,6 @@ fn setup_head(
|
||||||
// header and try again
|
// header and try again
|
||||||
let prev_header = batch.get_block_header(&head.prev_block_h)?;
|
let prev_header = batch.get_block_header(&head.prev_block_h)?;
|
||||||
let _ = batch.delete_block(&header.hash());
|
let _ = batch.delete_block(&header.hash());
|
||||||
let _ = batch.setup_height(&prev_header, &head)?;
|
|
||||||
head = Tip::from_header(&prev_header);
|
head = Tip::from_header(&prev_header);
|
||||||
batch.save_head(&head)?;
|
batch.save_head(&head)?;
|
||||||
}
|
}
|
||||||
|
@ -1251,7 +1262,6 @@ fn setup_head(
|
||||||
|
|
||||||
let tip = Tip::from_header(&genesis.header);
|
let tip = Tip::from_header(&genesis.header);
|
||||||
batch.save_head(&tip)?;
|
batch.save_head(&tip)?;
|
||||||
batch.setup_height(&genesis.header, &tip)?;
|
|
||||||
|
|
||||||
// Initialize our header MM with the genesis header.
|
// Initialize our header MM with the genesis header.
|
||||||
txhashset::header_extending(txhashset, &mut batch, |extension| {
|
txhashset::header_extending(txhashset, &mut batch, |extension| {
|
||||||
|
|
|
@ -521,11 +521,6 @@ fn update_head(b: &Block, ctx: &BlockContext) -> Result<Option<Tip>, Error> {
|
||||||
// when extending the head), update it
|
// when extending the head), update it
|
||||||
let head = ctx.batch.head()?;
|
let head = ctx.batch.head()?;
|
||||||
if has_more_work(&b.header, &head) {
|
if has_more_work(&b.header, &head) {
|
||||||
// Update the block height index based on this new head.
|
|
||||||
ctx.batch
|
|
||||||
.setup_height(&b.header, &head)
|
|
||||||
.map_err(|e| ErrorKind::StoreErr(e, "pipe setup height".to_owned()))?;
|
|
||||||
|
|
||||||
let tip = Tip::from_header(&b.header);
|
let tip = Tip::from_header(&b.header);
|
||||||
|
|
||||||
ctx.batch
|
ctx.batch
|
||||||
|
@ -585,7 +580,7 @@ pub fn rewind_and_apply_header_fork(
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut fork_hashes = vec![];
|
let mut fork_hashes = vec![];
|
||||||
let mut current = ext.batch.get_previous_header(header)?;
|
let mut current = ext.batch.get_previous_header(header)?;
|
||||||
while current.height > 0 && !ext.batch.is_on_current_chain(¤t).is_ok() {
|
while current.height > 0 && !ext.is_on_current_chain(¤t).is_ok() {
|
||||||
fork_hashes.push(current.hash());
|
fork_hashes.push(current.hash());
|
||||||
current = ext.batch.get_previous_header(¤t)?;
|
current = ext.batch.get_previous_header(¤t)?;
|
||||||
}
|
}
|
||||||
|
@ -616,7 +611,7 @@ pub fn rewind_and_apply_fork(b: &Block, ext: &mut txhashset::Extension) -> Resul
|
||||||
// keeping the hashes of blocks along the fork
|
// keeping the hashes of blocks along the fork
|
||||||
let mut fork_hashes = vec![];
|
let mut fork_hashes = vec![];
|
||||||
let mut current = ext.batch.get_previous_header(&b.header)?;
|
let mut current = ext.batch.get_previous_header(&b.header)?;
|
||||||
while current.height > 0 && !ext.batch.is_on_current_chain(¤t).is_ok() {
|
while current.height > 0 && !ext.is_on_current_chain(¤t).is_ok() {
|
||||||
fork_hashes.push(current.hash());
|
fork_hashes.push(current.hash());
|
||||||
current = ext.batch.get_previous_header(¤t)?;
|
current = ext.batch.get_previous_header(¤t)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ use core::core::hash::{Hash, Hashed};
|
||||||
use core::core::{Block, BlockHeader, BlockSums};
|
use core::core::{Block, BlockHeader, BlockSums};
|
||||||
use core::pow::Difficulty;
|
use core::pow::Difficulty;
|
||||||
use grin_store as store;
|
use grin_store as store;
|
||||||
use grin_store::{option_to_not_found, to_key, u64_to_key, Error};
|
use grin_store::{option_to_not_found, to_key, Error};
|
||||||
use types::Tip;
|
use types::Tip;
|
||||||
|
|
||||||
const STORE_SUBPATH: &'static str = "chain";
|
const STORE_SUBPATH: &'static str = "chain";
|
||||||
|
@ -37,7 +37,6 @@ const HEAD_PREFIX: u8 = 'H' as u8;
|
||||||
const TAIL_PREFIX: u8 = 'T' as u8;
|
const TAIL_PREFIX: u8 = 'T' as u8;
|
||||||
const HEADER_HEAD_PREFIX: u8 = 'I' as u8;
|
const HEADER_HEAD_PREFIX: u8 = 'I' as u8;
|
||||||
const SYNC_HEAD_PREFIX: u8 = 's' as u8;
|
const SYNC_HEAD_PREFIX: u8 = 's' as u8;
|
||||||
const HEADER_HEIGHT_PREFIX: u8 = '8' as u8;
|
|
||||||
const COMMIT_POS_PREFIX: u8 = 'c' as u8;
|
const COMMIT_POS_PREFIX: u8 = 'c' as u8;
|
||||||
const BLOCK_INPUT_BITMAP_PREFIX: u8 = 'B' as u8;
|
const BLOCK_INPUT_BITMAP_PREFIX: u8 = 'B' as u8;
|
||||||
const BLOCK_SUMS_PREFIX: u8 = 'M' as u8;
|
const BLOCK_SUMS_PREFIX: u8 = 'M' as u8;
|
||||||
|
@ -109,43 +108,6 @@ impl ChainStore {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hash_by_height(&self, height: u64) -> Result<Hash, Error> {
|
|
||||||
option_to_not_found(
|
|
||||||
self.db.get_ser(&u64_to_key(HEADER_HEIGHT_PREFIX, height)),
|
|
||||||
&format!("Hash at height: {}", height),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are on the current chain if -
|
|
||||||
// * the header by height index matches the header, and
|
|
||||||
// * we are not ahead of the current head
|
|
||||||
pub fn is_on_current_chain(&self, header: &BlockHeader) -> Result<(), Error> {
|
|
||||||
let head = self.head()?;
|
|
||||||
|
|
||||||
// check we are not out ahead of the current head
|
|
||||||
if header.height > head.height {
|
|
||||||
return Err(Error::NotFoundErr(String::from(
|
|
||||||
"header.height > head.height",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let header_at_height = self.get_header_by_height(header.height)?;
|
|
||||||
if header.hash() == header_at_height.hash() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::NotFoundErr(String::from(
|
|
||||||
"header.hash == header_at_height.hash",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> {
|
|
||||||
option_to_not_found(
|
|
||||||
self.db.get_ser(&u64_to_key(HEADER_HEIGHT_PREFIX, height)),
|
|
||||||
&format!("Header at height: {}", height),
|
|
||||||
).and_then(|hash| self.get_block_header(&hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_output_pos(&self, commit: &Commitment) -> Result<u64, Error> {
|
pub fn get_output_pos(&self, commit: &Commitment) -> Result<u64, Error> {
|
||||||
option_to_not_found(
|
option_to_not_found(
|
||||||
self.db
|
self.db
|
||||||
|
@ -209,13 +171,6 @@ impl<'a> Batch<'a> {
|
||||||
self.db.put_ser(&vec![HEADER_HEAD_PREFIX], t)
|
self.db.put_ser(&vec![HEADER_HEAD_PREFIX], t)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hash_by_height(&self, height: u64) -> Result<Hash, Error> {
|
|
||||||
option_to_not_found(
|
|
||||||
self.db.get_ser(&u64_to_key(HEADER_HEIGHT_PREFIX, height)),
|
|
||||||
&format!("Hash at height: {}", height),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_sync_head(&self, t: &Tip) -> Result<(), Error> {
|
pub fn save_sync_head(&self, t: &Tip) -> Result<(), Error> {
|
||||||
self.db.put_ser(&vec![SYNC_HEAD_PREFIX], t)
|
self.db.put_ser(&vec![SYNC_HEAD_PREFIX], t)
|
||||||
}
|
}
|
||||||
|
@ -283,15 +238,6 @@ impl<'a> Batch<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_header_height(&self, bh: &BlockHeader) -> Result<(), Error> {
|
|
||||||
self.db
|
|
||||||
.put_ser(&u64_to_key(HEADER_HEIGHT_PREFIX, bh.height), &bh.hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_header_by_height(&self, height: u64) -> Result<(), Error> {
|
|
||||||
self.db.delete(&u64_to_key(HEADER_HEIGHT_PREFIX, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_output_pos(&self, commit: &Commitment, pos: u64) -> Result<(), Error> {
|
pub fn save_output_pos(&self, commit: &Commitment, pos: u64) -> Result<(), Error> {
|
||||||
self.db.put_ser(
|
self.db.put_ser(
|
||||||
&to_key(COMMIT_POS_PREFIX, &mut commit.as_ref().to_vec())[..],
|
&to_key(COMMIT_POS_PREFIX, &mut commit.as_ref().to_vec())[..],
|
||||||
|
@ -352,70 +298,6 @@ impl<'a> Batch<'a> {
|
||||||
self.db.delete(&to_key(BLOCK_SUMS_PREFIX, &mut bh.to_vec()))
|
self.db.delete(&to_key(BLOCK_SUMS_PREFIX, &mut bh.to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are on the current chain if -
|
|
||||||
// * the header by height index matches the header, and
|
|
||||||
// * we are not ahead of the current head
|
|
||||||
pub fn is_on_current_chain(&self, header: &BlockHeader) -> Result<(), Error> {
|
|
||||||
let head = self.head()?;
|
|
||||||
|
|
||||||
// check we are not out ahead of the current head
|
|
||||||
if header.height > head.height {
|
|
||||||
return Err(Error::NotFoundErr(String::from(
|
|
||||||
"header.height > head.height",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let header_at_height = self.get_header_by_height(header.height)?;
|
|
||||||
if header.hash() == header_at_height.hash() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::NotFoundErr(String::from(
|
|
||||||
"header.hash == header_at_height.hash",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> {
|
|
||||||
option_to_not_found(
|
|
||||||
self.db.get_ser(&u64_to_key(HEADER_HEIGHT_PREFIX, height)),
|
|
||||||
&format!("Header at height: {}", height),
|
|
||||||
).and_then(|hash| self.get_block_header(&hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maintain consistency of the "header_by_height" index by traversing back
|
|
||||||
/// through the current chain and updating "header_by_height" until we reach
|
|
||||||
/// a block_header that is consistent with its height (everything prior to
|
|
||||||
/// this will be consistent).
|
|
||||||
/// We need to handle the case where we have no index entry for a given
|
|
||||||
/// height to account for the case where we just switched to a new fork and
|
|
||||||
/// the height jumped beyond current chain height.
|
|
||||||
pub fn setup_height(&self, header: &BlockHeader, old_tip: &Tip) -> Result<(), Error> {
|
|
||||||
// remove headers ahead if we backtracked
|
|
||||||
for n in header.height..old_tip.height {
|
|
||||||
self.delete_header_by_height(n + 1)?;
|
|
||||||
}
|
|
||||||
self.build_by_height_index(header, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_by_height_index(&self, header: &BlockHeader, force: bool) -> Result<(), Error> {
|
|
||||||
self.save_header_height(&header)?;
|
|
||||||
|
|
||||||
if header.height > 0 {
|
|
||||||
let mut prev_header = self.get_previous_header(&header)?;
|
|
||||||
while prev_header.height > 0 {
|
|
||||||
if !force {
|
|
||||||
if let Ok(_) = self.is_on_current_chain(&prev_header) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.save_header_height(&prev_header)?;
|
|
||||||
|
|
||||||
prev_header = self.get_previous_header(&prev_header)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_block_input_bitmap(&self, block: &Block) -> Result<Bitmap, Error> {
|
fn build_block_input_bitmap(&self, block: &Block) -> Result<Bitmap, Error> {
|
||||||
let bitmap = block
|
let bitmap = block
|
||||||
.inputs()
|
.inputs()
|
||||||
|
|
|
@ -204,6 +204,22 @@ impl TxHashSet {
|
||||||
kernel_pmmr.get_last_n_insertions(distance)
|
kernel_pmmr.get_last_n_insertions(distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the header at the specified height based on the current state of the txhashset.
|
||||||
|
/// Derives the MMR pos from the height (insertion index) and retrieves the header hash.
|
||||||
|
/// Looks the header up in the db by hash.
|
||||||
|
pub fn get_header_by_height(&mut self, height: u64) -> Result<BlockHeader, Error> {
|
||||||
|
let pos = pmmr::insertion_to_pmmr_index(height + 1);
|
||||||
|
|
||||||
|
let header_pmmr: PMMR<BlockHeader, _> =
|
||||||
|
PMMR::at(&mut self.header_pmmr_h.backend, self.header_pmmr_h.last_pos);
|
||||||
|
if let Some(hash) = header_pmmr.get_data(pos) {
|
||||||
|
let header = self.commit_index.get_block_header(&hash)?;
|
||||||
|
Ok(header)
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::Other(format!("get header by height")).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// returns outputs from the given insertion (leaf) index up to the
|
/// returns outputs from the given insertion (leaf) index up to the
|
||||||
/// specified limit. Also returns the last index actually populated
|
/// specified limit. Also returns the last index actually populated
|
||||||
pub fn outputs_by_insertion_index(
|
pub fn outputs_by_insertion_index(
|
||||||
|
@ -267,7 +283,7 @@ impl TxHashSet {
|
||||||
|
|
||||||
// horizon for compacting is based on current_height
|
// horizon for compacting is based on current_height
|
||||||
let horizon = current_height.saturating_sub(global::cut_through_horizon().into());
|
let horizon = current_height.saturating_sub(global::cut_through_horizon().into());
|
||||||
let horizon_header = self.commit_index.get_header_by_height(horizon)?;
|
let horizon_header = self.get_header_by_height(horizon)?;
|
||||||
|
|
||||||
let batch = self.commit_index.batch()?;
|
let batch = self.commit_index.batch()?;
|
||||||
|
|
||||||
|
@ -608,10 +624,34 @@ impl<'a> HeaderExtension<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the header hash for the specified pos from the underlying MMR backend.
|
/// Get the header hash for the specified pos from the underlying MMR backend.
|
||||||
pub fn get_header_hash(&self, pos: u64) -> Option<Hash> {
|
fn get_header_hash(&self, pos: u64) -> Option<Hash> {
|
||||||
self.pmmr.get_data(pos)
|
self.pmmr.get_data(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the header at the specified height based on the current state of the header extension.
|
||||||
|
/// Derives the MMR pos from the height (insertion index) and retrieves the header hash.
|
||||||
|
/// Looks the header up in the db by hash.
|
||||||
|
pub fn get_header_by_height(&mut self, height: u64) -> Result<BlockHeader, Error> {
|
||||||
|
let pos = pmmr::insertion_to_pmmr_index(height + 1);
|
||||||
|
if let Some(hash) = self.get_header_hash(pos) {
|
||||||
|
let header = self.batch.get_block_header(&hash)?;
|
||||||
|
Ok(header)
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::Other(format!("get header by height")).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares the provided header to the header in the header MMR at that height.
|
||||||
|
/// If these match we know the header is on the current chain.
|
||||||
|
pub fn is_on_current_chain(&mut self, header: &BlockHeader) -> Result<(), Error> {
|
||||||
|
let chain_header = self.get_header_by_height(header.height)?;
|
||||||
|
if chain_header.hash() == header.hash() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::Other(format!("not on current chain")).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Force the rollback of this extension, no matter the result.
|
/// Force the rollback of this extension, no matter the result.
|
||||||
pub fn force_rollback(&mut self) {
|
pub fn force_rollback(&mut self) {
|
||||||
self.rollback = true;
|
self.rollback = true;
|
||||||
|
@ -806,10 +846,13 @@ impl<'a> Extension<'a> {
|
||||||
UTXOView::new(self.output_pmmr.readonly_pmmr(), self.batch)
|
UTXOView::new(self.output_pmmr.readonly_pmmr(), self.batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - move this into "utxo_view"
|
|
||||||
/// Verify we are not attempting to spend any coinbase outputs
|
/// Verify we are not attempting to spend any coinbase outputs
|
||||||
/// that have not sufficiently matured.
|
/// that have not sufficiently matured.
|
||||||
pub fn verify_coinbase_maturity(&self, inputs: &Vec<Input>, height: u64) -> Result<(), Error> {
|
pub fn verify_coinbase_maturity(
|
||||||
|
&mut self,
|
||||||
|
inputs: &Vec<Input>,
|
||||||
|
height: u64,
|
||||||
|
) -> Result<(), Error> {
|
||||||
// Find the greatest output pos of any coinbase
|
// Find the greatest output pos of any coinbase
|
||||||
// outputs we are attempting to spend.
|
// outputs we are attempting to spend.
|
||||||
let pos = inputs
|
let pos = inputs
|
||||||
|
@ -829,7 +872,7 @@ impl<'a> Extension<'a> {
|
||||||
// Find the "cutoff" pos in the output MMR based on the
|
// Find the "cutoff" pos in the output MMR based on the
|
||||||
// header from 1,000 blocks ago.
|
// header from 1,000 blocks ago.
|
||||||
let cutoff_height = height.checked_sub(global::coinbase_maturity()).unwrap_or(0);
|
let cutoff_height = height.checked_sub(global::coinbase_maturity()).unwrap_or(0);
|
||||||
let cutoff_header = self.batch.get_header_by_height(cutoff_height)?;
|
let cutoff_header = self.get_header_by_height(cutoff_height)?;
|
||||||
let cutoff_pos = cutoff_header.output_mmr_size;
|
let cutoff_pos = cutoff_header.output_mmr_size;
|
||||||
|
|
||||||
// If any output pos exceed the cutoff_pos
|
// If any output pos exceed the cutoff_pos
|
||||||
|
@ -965,7 +1008,35 @@ impl<'a> Extension<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO - move this into "utxo_view"
|
/// Get the header hash for the specified pos from the underlying MMR backend.
|
||||||
|
fn get_header_hash(&self, pos: u64) -> Option<Hash> {
|
||||||
|
self.header_pmmr.get_data(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the header at the specified height based on the current state of the extension.
|
||||||
|
/// Derives the MMR pos from the height (insertion index) and retrieves the header hash.
|
||||||
|
/// Looks the header up in the db by hash.
|
||||||
|
pub fn get_header_by_height(&mut self, height: u64) -> Result<BlockHeader, Error> {
|
||||||
|
let pos = pmmr::insertion_to_pmmr_index(height + 1);
|
||||||
|
if let Some(hash) = self.get_header_hash(pos) {
|
||||||
|
let header = self.batch.get_block_header(&hash)?;
|
||||||
|
Ok(header)
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::Other(format!("get header by height")).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares the provided header to the header in the header MMR at that height.
|
||||||
|
/// If these match we know the header is on the current chain.
|
||||||
|
pub fn is_on_current_chain(&mut self, header: &BlockHeader) -> Result<(), Error> {
|
||||||
|
let chain_header = self.get_header_by_height(header.height)?;
|
||||||
|
if chain_header.hash() == header.hash() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::Other(format!("not on current chain")).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a Merkle proof for the given output and the block
|
/// Build a Merkle proof for the given output and the block
|
||||||
/// this extension is currently referencing.
|
/// this extension is currently referencing.
|
||||||
/// Note: this relies on the MMR being stable even after pruning/compaction.
|
/// Note: this relies on the MMR being stable even after pruning/compaction.
|
||||||
|
@ -1487,9 +1558,6 @@ pub fn input_pos_to_rewind(
|
||||||
head_header: &BlockHeader,
|
head_header: &BlockHeader,
|
||||||
batch: &Batch,
|
batch: &Batch,
|
||||||
) -> Result<Bitmap, Error> {
|
) -> Result<Bitmap, Error> {
|
||||||
let mut current = head_header.hash();
|
|
||||||
let mut height = head_header.height;
|
|
||||||
|
|
||||||
if head_header.height < block_header.height {
|
if head_header.height < block_header.height {
|
||||||
debug!(
|
debug!(
|
||||||
"input_pos_to_rewind: {} < {}, nothing to rewind",
|
"input_pos_to_rewind: {} < {}, nothing to rewind",
|
||||||
|
@ -1514,21 +1582,19 @@ pub fn input_pos_to_rewind(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut block_input_bitmaps: Vec<Bitmap> = vec![];
|
let mut block_input_bitmaps: Vec<Bitmap> = vec![];
|
||||||
let bh = block_header.hash();
|
|
||||||
|
|
||||||
while current != bh {
|
let mut current = head_header.clone();
|
||||||
// We cache recent block headers and block_input_bitmaps
|
while current.hash() != block_header.hash() {
|
||||||
// internally in our db layer (commit_index).
|
if current.height < 1 {
|
||||||
// I/O should be minimized or eliminated here for most
|
|
||||||
// rewind scenarios.
|
|
||||||
if let Ok(b_res) = batch.get_block_input_bitmap(¤t) {
|
|
||||||
bitmap_fast_or(Some(b_res), &mut block_input_bitmaps);
|
|
||||||
}
|
|
||||||
if height == 0 {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
height -= 1;
|
|
||||||
current = batch.get_hash_by_height(height)?;
|
// I/O should be minimized or eliminated here for most
|
||||||
|
// rewind scenarios.
|
||||||
|
if let Ok(b_res) = batch.get_block_input_bitmap(¤t.hash()) {
|
||||||
|
bitmap_fast_or(Some(b_res), &mut block_input_bitmaps);
|
||||||
|
}
|
||||||
|
current = batch.get_previous_header(¤t)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bitmap = bitmap_fast_or(None, &mut block_input_bitmaps).unwrap();
|
let bitmap = bitmap_fast_or(None, &mut block_input_bitmaps).unwrap();
|
||||||
|
|
|
@ -25,7 +25,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use chain::{Error, Tip};
|
use chain::{Error, Tip};
|
||||||
use core::core::hash::Hashed;
|
use core::core::hash::Hashed;
|
||||||
use core::core::{Block, BlockHeader};
|
use core::core::Block;
|
||||||
use core::global::{self, ChainTypes};
|
use core::global::{self, ChainTypes};
|
||||||
use core::pow::{self, Difficulty};
|
use core::pow::{self, Difficulty};
|
||||||
use keychain::{ExtKeychain, ExtKeychainPath, Keychain};
|
use keychain::{ExtKeychain, ExtKeychainPath, Keychain};
|
||||||
|
@ -41,7 +41,6 @@ fn setup_chain(genesis: &Block, chain_store: Arc<chain::store::ChainStore>) -> R
|
||||||
batch.save_block(&genesis)?;
|
batch.save_block(&genesis)?;
|
||||||
let head = Tip::from_header(&genesis.header);
|
let head = Tip::from_header(&genesis.header);
|
||||||
batch.save_head(&head)?;
|
batch.save_head(&head)?;
|
||||||
batch.setup_height(&genesis.header, &head)?;
|
|
||||||
batch.save_block_header(&genesis.header)?;
|
batch.save_block_header(&genesis.header)?;
|
||||||
batch.commit()?;
|
batch.commit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -75,18 +74,12 @@ fn test_various_store_indices() {
|
||||||
let batch = chain_store.batch().unwrap();
|
let batch = chain_store.batch().unwrap();
|
||||||
batch.save_block_header(&block.header).unwrap();
|
batch.save_block_header(&block.header).unwrap();
|
||||||
batch.save_block(&block).unwrap();
|
batch.save_block(&block).unwrap();
|
||||||
batch
|
|
||||||
.setup_height(&block.header, &Tip::from_header(&block.header))
|
|
||||||
.unwrap();
|
|
||||||
batch.commit().unwrap();
|
batch.commit().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let block_header = chain_store.get_block_header(&block_hash).unwrap();
|
let block_header = chain_store.get_block_header(&block_hash).unwrap();
|
||||||
assert_eq!(block_header.hash(), block_hash);
|
assert_eq!(block_header.hash(), block_hash);
|
||||||
|
|
||||||
let block_header = chain_store.get_header_by_height(1).unwrap();
|
|
||||||
assert_eq!(block_header.hash(), block_hash);
|
|
||||||
|
|
||||||
// Test we can retrive the block from the db and that we can safely delete the
|
// Test we can retrive the block from the db and that we can safely delete the
|
||||||
// block from the db even though the block_sums are missing.
|
// block from the db even though the block_sums are missing.
|
||||||
{
|
{
|
||||||
|
@ -109,43 +102,3 @@ fn test_various_store_indices() {
|
||||||
assert!(chain_store.get_block(&block_hash).is_ok());
|
assert!(chain_store.get_block(&block_hash).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_store_header_height() {
|
|
||||||
match env_logger::try_init() {
|
|
||||||
Ok(_) => println!("Initializing env logger"),
|
|
||||||
Err(e) => println!("env logger already initialized: {:?}", e),
|
|
||||||
};
|
|
||||||
let chain_dir = ".grin_idx_2";
|
|
||||||
clean_output_dir(chain_dir);
|
|
||||||
|
|
||||||
let db_env = Arc::new(store::new_env(chain_dir.to_string()));
|
|
||||||
let chain_store = Arc::new(chain::store::ChainStore::new(db_env).unwrap());
|
|
||||||
|
|
||||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
|
||||||
let genesis = pow::mine_genesis_block().unwrap();
|
|
||||||
|
|
||||||
setup_chain(&genesis, chain_store.clone()).unwrap();
|
|
||||||
|
|
||||||
let mut block_header = BlockHeader::default();
|
|
||||||
block_header.height = 1;
|
|
||||||
|
|
||||||
{
|
|
||||||
let batch = chain_store.batch().unwrap();
|
|
||||||
batch.save_block_header(&block_header).unwrap();
|
|
||||||
batch.save_header_height(&block_header).unwrap();
|
|
||||||
batch.commit().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let stored_block_header = chain_store.get_header_by_height(1).unwrap();
|
|
||||||
assert_eq!(block_header.hash(), stored_block_header.hash());
|
|
||||||
|
|
||||||
{
|
|
||||||
let batch = chain_store.batch().unwrap();
|
|
||||||
batch.delete_header_by_height(1).unwrap();
|
|
||||||
batch.commit().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = chain_store.get_header_by_height(1);
|
|
||||||
assert_eq!(result.is_err(), true);
|
|
||||||
}
|
|
||||||
|
|
|
@ -261,33 +261,32 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn locate_headers(&self, locator: Vec<Hash>) -> Vec<core::BlockHeader> {
|
fn locate_headers(&self, locator: Vec<Hash>) -> Vec<core::BlockHeader> {
|
||||||
debug!("locate_headers: {:?}", locator,);
|
debug!("locator: {:?}", locator);
|
||||||
|
|
||||||
let header = match self.find_common_header(locator) {
|
let header = match self.find_common_header(locator) {
|
||||||
Some(header) => header,
|
Some(header) => header,
|
||||||
None => return vec![],
|
None => return vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("locate_headers: common header: {:?}", header.hash(),);
|
let max_height = self.chain().header_head().unwrap().height;
|
||||||
|
|
||||||
// looks like we know one, getting as many following headers as allowed
|
// looks like we know one, getting as many following headers as allowed
|
||||||
let hh = header.height;
|
let hh = header.height;
|
||||||
let mut headers = vec![];
|
let mut headers = vec![];
|
||||||
for h in (hh + 1)..(hh + (p2p::MAX_BLOCK_HEADERS as u64)) {
|
for h in (hh + 1)..(hh + (p2p::MAX_BLOCK_HEADERS as u64)) {
|
||||||
let header = self.chain().get_header_by_height(h);
|
if h > max_height {
|
||||||
match header {
|
break;
|
||||||
Ok(head) => headers.push(head),
|
|
||||||
Err(e) => match e.kind() {
|
|
||||||
chain::ErrorKind::StoreErr(store::Error::NotFoundErr(_), _) => break,
|
|
||||||
_ => {
|
|
||||||
error!("Could not build header locator: {:?}", e);
|
|
||||||
return vec![];
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
if let Ok(header) = self.chain().get_header_by_height(h) {
|
||||||
|
headers.push(header);
|
||||||
|
} else {
|
||||||
|
error!("Failed to locate headers successfully.");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("locate_headers: returning headers: {}", headers.len(),);
|
debug!("returning headers: {}", headers.len());
|
||||||
|
|
||||||
headers
|
headers
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue