mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21: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()
|
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
|
// RESTful index of available api endpoints
|
||||||
// GET /v1/
|
// GET /v1/
|
||||||
struct IndexHandler {
|
struct IndexHandler {
|
||||||
|
@ -74,27 +99,8 @@ struct OutputHandler {
|
||||||
|
|
||||||
impl OutputHandler {
|
impl OutputHandler {
|
||||||
fn get_output(&self, id: &str) -> Result<Output, Error> {
|
fn get_output(&self, id: &str) -> Result<Output, Error> {
|
||||||
let c = util::from_hex(String::from(id)).context(ErrorKind::Argument(format!(
|
let res = get_output(&self.chain, id)?;
|
||||||
"Not a valid commitment: {}",
|
Ok(res.0)
|
||||||
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)?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outputs_by_ids(&self, req: &Request<Body>) -> Result<Vec<Output>, Error> {
|
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/<hash>
|
||||||
/// GET /v1/headers/<height>
|
/// GET /v1/headers/<height>
|
||||||
|
/// GET /v1/headers/<output commit>
|
||||||
///
|
///
|
||||||
pub struct HeaderHandler {
|
pub struct HeaderHandler {
|
||||||
pub chain: Weak<chain::Chain>,
|
pub chain: Weak<chain::Chain>,
|
||||||
|
@ -561,6 +568,10 @@ pub struct HeaderHandler {
|
||||||
|
|
||||||
impl HeaderHandler {
|
impl HeaderHandler {
|
||||||
fn get_header(&self, input: String) -> Result<BlockHeaderPrintable, Error> {
|
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() {
|
if let Ok(height) = input.parse() {
|
||||||
match w(&self.chain).get_header_by_height(height) {
|
match w(&self.chain).get_header_by_height(height) {
|
||||||
Ok(header) => return Ok(BlockHeaderPrintable::from_header(&header)),
|
Ok(header) => return Ok(BlockHeaderPrintable::from_header(&header)),
|
||||||
|
@ -576,11 +587,20 @@ impl HeaderHandler {
|
||||||
.context(ErrorKind::NotFound)?;
|
.context(ErrorKind::NotFound)?;
|
||||||
Ok(BlockHeaderPrintable::from_header(&header))
|
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/<hash>
|
||||||
/// GET /v1/blocks/<height>
|
/// GET /v1/blocks/<height>
|
||||||
|
/// GET /v1/blocks/<commit>
|
||||||
///
|
///
|
||||||
/// Optionally return results as "compact blocks" by passing "?compact" query
|
/// Optionally return results as "compact blocks" by passing "?compact" query
|
||||||
/// param GET /v1/blocks/<hash>?compact
|
/// param GET /v1/blocks/<hash>?compact
|
||||||
|
|
|
@ -25,7 +25,9 @@ use lmdb;
|
||||||
use core::core::hash::{Hash, Hashed};
|
use core::core::hash::{Hash, Hashed};
|
||||||
use core::core::merkle_proof::MerkleProof;
|
use core::core::merkle_proof::MerkleProof;
|
||||||
use core::core::target::Difficulty;
|
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 core::global;
|
||||||
use error::{Error, ErrorKind};
|
use error::{Error, ErrorKind};
|
||||||
use grin_store::Error::NotFoundErr;
|
use grin_store::Error::NotFoundErr;
|
||||||
|
@ -391,7 +393,11 @@ impl Chain {
|
||||||
/// work) fork.
|
/// work) fork.
|
||||||
pub fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<Hash, Error> {
|
pub fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<Hash, Error> {
|
||||||
let mut txhashset = self.txhashset.write().unwrap();
|
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> {
|
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())
|
.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.
|
/// 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
|
/// Checks the header_by_height index to verify the header is where we say
|
||||||
/// it is
|
/// it is
|
||||||
|
|
|
@ -130,14 +130,14 @@ impl TxHashSet {
|
||||||
/// Check if an output is unspent.
|
/// Check if an output is unspent.
|
||||||
/// We look in the index to find the output MMR pos.
|
/// 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.
|
/// 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) {
|
match self.commit_index.get_output_pos(&output_id.commit) {
|
||||||
Ok(pos) => {
|
Ok(pos) => {
|
||||||
let output_pmmr: PMMR<OutputIdentifier, _> =
|
let output_pmmr: PMMR<OutputIdentifier, _> =
|
||||||
PMMR::at(&mut self.output_pmmr_h.backend, self.output_pmmr_h.last_pos);
|
PMMR::at(&mut self.output_pmmr_h.backend, self.output_pmmr_h.last_pos);
|
||||||
if let Some(hash) = output_pmmr.get_hash(pos) {
|
if let Some(hash) = output_pmmr.get_hash(pos) {
|
||||||
if hash == output_id.hash_with_index(pos - 1) {
|
if hash == output_id.hash_with_index(pos - 1) {
|
||||||
Ok(hash)
|
Ok((hash, pos))
|
||||||
} else {
|
} else {
|
||||||
Err(ErrorKind::TxHashSetErr(format!("txhashset hash mismatch")).into())
|
Err(ErrorKind::TxHashSetErr(format!("txhashset hash mismatch")).into())
|
||||||
}
|
}
|
||||||
|
@ -989,8 +989,8 @@ impl<'a> Extension<'a> {
|
||||||
{
|
{
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
let mut commits:Vec<Commitment> = vec![];
|
let mut commits: Vec<Commitment> = vec![];
|
||||||
let mut proofs:Vec<RangeProof> = vec![];
|
let mut proofs: Vec<RangeProof> = vec![];
|
||||||
|
|
||||||
let mut proof_count = 0;
|
let mut proof_count = 0;
|
||||||
let total_rproofs = pmmr::n_leaves(self.output_pmmr.unpruned_size());
|
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()
|
.file_name()
|
||||||
.and_then(|n| n.to_str().map(|s| String::from(s)))
|
.and_then(|n| n.to_str().map(|s| String::from(s)))
|
||||||
})
|
})
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let dir_difference: Vec<String> = subdirectories_found
|
let dir_difference: Vec<String> = subdirectories_found
|
||||||
.difference(&subdirectories_expected)
|
.difference(&subdirectories_expected)
|
||||||
|
@ -1161,7 +1162,8 @@ fn check_and_remove_files(txhashset_path: &PathBuf, header: &BlockHeader) -> Res
|
||||||
} else {
|
} else {
|
||||||
String::from(s)
|
String::from(s)
|
||||||
}
|
}
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let subdirectories = fs::read_dir(txhashset_path)?;
|
let subdirectories = fs::read_dir(txhashset_path)?;
|
||||||
for subdirectory in subdirectories {
|
for subdirectory in subdirectories {
|
||||||
|
@ -1174,7 +1176,8 @@ fn check_and_remove_files(txhashset_path: &PathBuf, header: &BlockHeader) -> Res
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|n| n.to_str().map(|s| String::from(s)))
|
.and_then(|n| n.to_str().map(|s| String::from(s)))
|
||||||
})
|
})
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
let difference: Vec<String> = pmmr_files_found
|
let difference: Vec<String> = pmmr_files_found
|
||||||
.difference(&pmmr_files_expected)
|
.difference(&pmmr_files_expected)
|
||||||
.cloned()
|
.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
|
fn prepare_block<K>(kc: &K, prev: &BlockHeader, chain: &Chain, diff: u64) -> Block
|
||||||
where
|
where
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
|
|
|
@ -30,6 +30,13 @@ pub fn to_hex(bytes: Vec<u8>) -> String {
|
||||||
|
|
||||||
/// Decode a hex string into bytes.
|
/// Decode a hex string into bytes.
|
||||||
pub fn from_hex(hex_str: String) -> Result<Vec<u8>, num::ParseIntError> {
|
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" {
|
let hex_trim = if &hex_str[..2] == "0x" {
|
||||||
hex_str[2..].to_owned()
|
hex_str[2..].to_owned()
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue