mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
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:
parent
82a467ac3c
commit
8440aad7ea
5 changed files with 147 additions and 32 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue