Retrieve header by output commit (#1388)

* Add get_header_for_output function to chain

* add api call to retrieve associated header for a given output

* rustfmt
This commit is contained in:
Yeastplume 2018-08-20 19:02:44 +01:00 committed by GitHub
parent 82a467ac3c
commit 8440aad7ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 147 additions and 32 deletions

View file

@ -50,6 +50,31 @@ fn w<T>(weak: &Weak<T>) -> Arc<T> {
weak.upgrade().unwrap()
}
/// Retrieves an output from the chain given a commit id (a tiny bit iteratively)
fn get_output(chain: &Weak<chain::Chain>, id: &str) -> Result<(Output, OutputIdentifier), Error> {
let c = util::from_hex(String::from(id)).context(ErrorKind::Argument(format!(
"Not a valid commitment: {}",
id
)))?;
let commit = Commitment::from_vec(c);
// We need the features here to be able to generate the necessary hash
// to compare against the hash in the output MMR.
// For now we can just try both (but this probably needs to be part of the api
// params)
let outputs = [
OutputIdentifier::new(OutputFeatures::DEFAULT_OUTPUT, &commit),
OutputIdentifier::new(OutputFeatures::COINBASE_OUTPUT, &commit),
];
for x in outputs.iter() {
if let Ok(_) = w(chain).is_unspent(&x) {
return Ok((Output::new(&commit), x.clone()));
}
}
Err(ErrorKind::NotFound)?
}
// RESTful index of available api endpoints
// GET /v1/
struct IndexHandler {
@ -74,27 +99,8 @@ struct OutputHandler {
impl OutputHandler {
fn get_output(&self, id: &str) -> Result<Output, Error> {
let c = util::from_hex(String::from(id)).context(ErrorKind::Argument(format!(
"Not a valid commitment: {}",
id
)))?;
let commit = Commitment::from_vec(c);
// We need the features here to be able to generate the necessary hash
// to compare against the hash in the output MMR.
// For now we can just try both (but this probably needs to be part of the api
// params)
let outputs = [
OutputIdentifier::new(OutputFeatures::DEFAULT_OUTPUT, &commit),
OutputIdentifier::new(OutputFeatures::COINBASE_OUTPUT, &commit),
];
for x in outputs.iter() {
if let Ok(_) = w(&self.chain).is_unspent(&x) {
return Ok(Output::new(&commit));
}
}
Err(ErrorKind::NotFound)?
let res = get_output(&self.chain, id)?;
Ok(res.0)
}
fn outputs_by_ids(&self, req: &Request<Body>) -> Result<Vec<Output>, Error> {
@ -551,9 +557,10 @@ impl Handler for ChainCompactHandler {
}
}
/// Gets block headers given either a hash or height.
/// Gets block headers given either a hash or height or an output commit.
/// GET /v1/headers/<hash>
/// GET /v1/headers/<height>
/// GET /v1/headers/<output commit>
///
pub struct HeaderHandler {
pub chain: Weak<chain::Chain>,
@ -561,6 +568,10 @@ pub struct HeaderHandler {
impl HeaderHandler {
fn get_header(&self, input: String) -> Result<BlockHeaderPrintable, Error> {
// will fail quick if the provided isn't a commitment
if let Ok(h) = self.get_header_for_output(input.clone()) {
return Ok(h);
}
if let Ok(height) = input.parse() {
match w(&self.chain).get_header_by_height(height) {
Ok(header) => return Ok(BlockHeaderPrintable::from_header(&header)),
@ -576,11 +587,20 @@ impl HeaderHandler {
.context(ErrorKind::NotFound)?;
Ok(BlockHeaderPrintable::from_header(&header))
}
fn get_header_for_output(&self, commit_id: String) -> Result<BlockHeaderPrintable, Error> {
let oid = get_output(&self.chain, &commit_id)?.1;
match w(&self.chain).get_header_for_output(&oid) {
Ok(header) => return Ok(BlockHeaderPrintable::from_header(&header)),
Err(_) => return Err(ErrorKind::NotFound)?,
}
}
}
/// Gets block details given either a hash or height.
/// Gets block details given either a hash or an unspent commit
/// GET /v1/blocks/<hash>
/// GET /v1/blocks/<height>
/// GET /v1/blocks/<commit>
///
/// Optionally return results as "compact blocks" by passing "?compact" query
/// param GET /v1/blocks/<hash>?compact

View file

@ -25,7 +25,9 @@ use lmdb;
use core::core::hash::{Hash, Hashed};
use core::core::merkle_proof::MerkleProof;
use core::core::target::Difficulty;
use core::core::{Block, BlockHeader, Output, OutputIdentifier, Transaction, TxKernel};
use core::core::{
Block, BlockHeader, Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel,
};
use core::global;
use error::{Error, ErrorKind};
use grin_store::Error::NotFoundErr;
@ -391,7 +393,11 @@ impl Chain {
/// work) fork.
pub fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<Hash, Error> {
let mut txhashset = self.txhashset.write().unwrap();
txhashset.is_unspent(output_ref)
let res = txhashset.is_unspent(output_ref);
match res {
Err(e) => Err(e),
Ok((h, p)) => Ok(h),
}
}
fn next_block_height(&self) -> Result<u64, Error> {
@ -804,6 +810,36 @@ impl Chain {
.map_err(|e| ErrorKind::StoreErr(e, "chain get header by height".to_owned()).into())
}
/// Gets the block header in which a given output appears in the txhashset
pub fn get_header_for_output(
&self,
output_ref: &OutputIdentifier,
) -> Result<BlockHeader, Error> {
let mut txhashset = self.txhashset.write().unwrap();
let (_, pos) = txhashset.is_unspent(output_ref)?;
let mut min = 1;
let mut max = {
let h = self.head.lock().unwrap();
h.height
};
loop {
let search_height = max - (max - min) / 2;
let h = self.get_header_by_height(search_height)?;
let h_prev = self.get_header_by_height(search_height - 1)?;
if pos > h.output_mmr_size {
min = search_height;
} else if pos < h_prev.output_mmr_size {
max = search_height;
} else {
if pos == h_prev.output_mmr_size {
return Ok(h_prev);
}
return Ok(h);
}
}
}
/// Verifies the given block header is actually on the current chain.
/// Checks the header_by_height index to verify the header is where we say
/// it is

View file

@ -130,14 +130,14 @@ impl TxHashSet {
/// Check if an output is unspent.
/// We look in the index to find the output MMR pos.
/// Then we check the entry in the output MMR and confirm the hash matches.
pub fn is_unspent(&mut self, output_id: &OutputIdentifier) -> Result<Hash, Error> {
pub fn is_unspent(&mut self, output_id: &OutputIdentifier) -> Result<(Hash, u64), Error> {
match self.commit_index.get_output_pos(&output_id.commit) {
Ok(pos) => {
let output_pmmr: PMMR<OutputIdentifier, _> =
PMMR::at(&mut self.output_pmmr_h.backend, self.output_pmmr_h.last_pos);
if let Some(hash) = output_pmmr.get_hash(pos) {
if hash == output_id.hash_with_index(pos - 1) {
Ok(hash)
Ok((hash, pos))
} else {
Err(ErrorKind::TxHashSetErr(format!("txhashset hash mismatch")).into())
}
@ -989,8 +989,8 @@ impl<'a> Extension<'a> {
{
let now = Instant::now();
let mut commits:Vec<Commitment> = vec![];
let mut proofs:Vec<RangeProof> = vec![];
let mut commits: Vec<Commitment> = vec![];
let mut proofs: Vec<RangeProof> = vec![];
let mut proof_count = 0;
let total_rproofs = pmmr::n_leaves(self.output_pmmr.unpruned_size());
@ -1132,7 +1132,8 @@ fn check_and_remove_files(txhashset_path: &PathBuf, header: &BlockHeader) -> Res
.file_name()
.and_then(|n| n.to_str().map(|s| String::from(s)))
})
}).collect();
})
.collect();
let dir_difference: Vec<String> = subdirectories_found
.difference(&subdirectories_expected)
@ -1161,7 +1162,8 @@ fn check_and_remove_files(txhashset_path: &PathBuf, header: &BlockHeader) -> Res
} else {
String::from(s)
}
}).collect();
})
.collect();
let subdirectories = fs::read_dir(txhashset_path)?;
for subdirectory in subdirectories {
@ -1174,7 +1176,8 @@ fn check_and_remove_files(txhashset_path: &PathBuf, header: &BlockHeader) -> Res
.file_name()
.and_then(|n| n.to_str().map(|s| String::from(s)))
})
}).collect();
})
.collect();
let difference: Vec<String> = pmmr_files_found
.difference(&pmmr_files_expected)
.cloned()

View file

@ -362,6 +362,55 @@ fn spend_in_fork_and_compact() {
}
}
/// Test ability to retrieve block headers for a given output
#[test]
fn output_header_mappings() {
global::set_mining_mode(ChainTypes::AutomatedTesting);
let chain = setup(
".grin_header_for_output",
pow::mine_genesis_block().unwrap(),
);
let keychain = ExtKeychain::from_random_seed().unwrap();
let mut reward_outputs = vec![];
for n in 1..15 {
let prev = chain.head_header().unwrap();
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
let pk = keychain.derive_key_id(n as u32).unwrap();
let reward = libtx::reward::output(&keychain, &pk, 0, prev.height).unwrap();
reward_outputs.push(reward.0.clone());
let mut b = core::core::Block::new(&prev, vec![], difficulty.clone(), reward).unwrap();
b.header.timestamp = prev.timestamp + Duration::seconds(60);
chain.set_txhashset_roots(&mut b, false).unwrap();
let sizeshift = if n == 2 {
global::min_sizeshift() + 1
} else {
global::min_sizeshift()
};
b.header.pow.cuckoo_sizeshift = sizeshift;
pow::pow_size(&mut b.header, difficulty, global::proofsize(), sizeshift).unwrap();
b.header.pow.cuckoo_sizeshift = sizeshift;
chain.process_block(b, chain::Options::MINE).unwrap();
let header_for_output = chain
.get_header_for_output(&OutputIdentifier::from_output(&reward_outputs[n - 1]))
.unwrap();
assert_eq!(header_for_output.height, n as u64);
chain.validate(false).unwrap();
}
// Check all output positions are as expected
for n in 1..15 {
let header_for_output = chain
.get_header_for_output(&OutputIdentifier::from_output(&reward_outputs[n - 1]))
.unwrap();
assert_eq!(header_for_output.height, n as u64);
}
}
fn prepare_block<K>(kc: &K, prev: &BlockHeader, chain: &Chain, diff: u64) -> Block
where
K: Keychain,

View file

@ -30,6 +30,13 @@ pub fn to_hex(bytes: Vec<u8>) -> String {
/// Decode a hex string into bytes.
pub fn from_hex(hex_str: String) -> Result<Vec<u8>, num::ParseIntError> {
if hex_str.len() % 2 == 1 {
// TODO: other way to instantiate a ParseIntError?
let err = ("QQQ").parse::<u64>();
if let Err(e) = err {
return Err(e);
}
}
let hex_trim = if &hex_str[..2] == "0x" {
hex_str[2..].to_owned()
} else {