tx pool lookup for kernel short ids (compact block hydration) (#710)

* wip - basic tx pool lookup for kernel short ids (compact block hydration)

* use the nonce in the compact_block to correctly generate short_ids for lookup

* query the tx pool based on kernel short_ids

* tests passing

* cleanup some logging

* cleanup logging
This commit is contained in:
Antioch Peverell
2018-02-16 10:42:27 -05:00
committed by GitHub
parent b82fa0ea1d
commit 5572fa075e
7 changed files with 155 additions and 68 deletions
+48 -33
View File
@@ -235,13 +235,15 @@ pub struct CompactBlock {
/// Implementation of Writeable for a compact block, defines how to write the block to a
/// binary writer. Differentiates between writing the block for the purpose of
/// full serialization and the one of just extracting a hash.
/// Note: compact block hash uses both the header *and* the nonce.
impl Writeable for CompactBlock {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
try!(self.header.write(writer));
try!(writer.write_u64(self.nonce));
if writer.serialization_mode() != ser::SerializationMode::Hash {
ser_multiwrite!(
writer,
[write_u64, self.nonce],
[write_u64, self.out_full.len() as u64],
[write_u64, self.kern_full.len() as u64],
[write_u64, self.kern_ids.len() as u64]
@@ -402,44 +404,43 @@ impl Block {
}
/// Hydrate a block from a compact block.
///
/// TODO - only supporting empty compact blocks for now (coinbase output/kernel only)
/// eventually we want to support any compact blocks
/// we need to differentiate between a block with missing entries (not in tx pool)
/// and a truly invalid block (which will get the peer banned)
/// so we need to consider how to do this safely/robustly
/// presumably at this point we are confident we can generate a full block with no
/// missing pieces, but we cannot fully validate it until we push it through the pipeline
/// at which point the peer runs the risk of getting banned
pub fn hydrate_from(
cb: CompactBlock,
_inputs: Vec<Input>,
_outputs: Vec<Output>,
_kernels: Vec<TxKernel>,
) -> Block {
/// Note: caller must validate the block themselves, we do not validate it here.
pub fn hydrate_from(cb: CompactBlock, txs: Vec<Transaction>) -> Block {
debug!(
LOGGER,
"block: hydrate_from: {}, {} cb outputs, {} cb kernels, {} tx kern_ids",
"block: hydrate_from: {}, {} txs",
cb.hash(),
cb.out_full.len(),
cb.kern_full.len(),
cb.kern_ids.len(),
txs.len(),
);
// we only support "empty" compact block for now
assert!(cb.kern_ids.is_empty());
let mut all_inputs = HashSet::new();
let mut all_outputs = HashSet::new();
let mut all_kernels = HashSet::new();
let mut all_inputs = vec![];
let mut all_outputs = vec![];
let mut all_kernels = vec![];
// collect all the inputs, outputs and kernels from the txs
for tx in txs {
all_inputs.extend(tx.inputs);
all_outputs.extend(tx.outputs);
all_kernels.extend(tx.kernels);
}
// include the coinbase output(s) and kernel(s) from the compact_block
all_outputs.extend(cb.out_full);
all_kernels.extend(cb.kern_full);
// convert the sets to vecs
let mut all_inputs = all_inputs.iter().cloned().collect::<Vec<_>>();
let mut all_outputs = all_outputs.iter().cloned().collect::<Vec<_>>();
let mut all_kernels = all_kernels.iter().cloned().collect::<Vec<_>>();
// sort them all lexicographically
all_inputs.sort();
all_outputs.sort();
all_kernels.sort();
// finally return the full block
// Note: we have not actually validated the block here
// leave it to the caller to actually validate the block
Block {
header: cb.header,
inputs: all_inputs,
@@ -640,10 +641,6 @@ impl Block {
/// Validates all the elements in a block that can be checked without
/// additional data. Includes commitment sums and kernels, Merkle
/// trees, reward, etc.
///
/// TODO - performs various verification steps - discuss renaming this to "verify"
/// as all the steps within are verify steps.
///
pub fn validate(&self) -> Result<(), Error> {
self.verify_weight()?;
self.verify_sorted()?;
@@ -1150,6 +1147,26 @@ mod test {
);
}
#[test]
fn compact_block_hash_with_nonce() {
let keychain = Keychain::from_random_seed().unwrap();
let tx = tx1i2o();
let b = new_block(vec![&tx], &keychain);
let cb1 = b.as_compact_block();
let cb2 = b.as_compact_block();
// random nonce included in hash each time we generate a compact_block
// so the hash will always be unique (we use this to generate unique short_ids)
assert!(cb1.hash() != cb2.hash());
assert!(cb1.kern_ids[0] != cb2.kern_ids[0]);
// check we can identify the specified kernel from the short_id
// in either of the compact_blocks
assert_eq!(cb1.kern_ids[0], tx.kernels[0].short_id(&cb1.hash()));
assert_eq!(cb2.kern_ids[0], tx.kernels[0].short_id(&cb2.hash()));
}
#[test]
fn convert_block_to_compact_block() {
let keychain = Keychain::from_random_seed().unwrap();
@@ -1157,8 +1174,6 @@ mod test {
let b = new_block(vec![&tx1], &keychain);
let cb = b.as_compact_block();
let hash = (b.header, cb.nonce).hash();
assert_eq!(cb.out_full.len(), 1);
assert_eq!(cb.kern_full.len(), 1);
assert_eq!(cb.kern_ids.len(), 1);
@@ -1169,7 +1184,7 @@ mod test {
.iter()
.find(|x| !x.features.contains(KernelFeatures::COINBASE_KERNEL))
.unwrap()
.short_id(&hash)
.short_id(&cb.hash())
);
}
@@ -1178,7 +1193,7 @@ mod test {
let keychain = Keychain::from_random_seed().unwrap();
let b = new_block(vec![], &keychain);
let cb = b.as_compact_block();
let hb = Block::hydrate_from(cb, vec![], vec![], vec![]);
let hb = Block::hydrate_from(cb, vec![]);
assert_eq!(hb.header, b.header);
assert_eq!(hb.outputs, b.outputs);
assert_eq!(hb.kernels, b.kernels);
+21 -3
View File
@@ -138,6 +138,15 @@ pub struct TxKernel {
hashable_ord!(TxKernel);
/// TODO - no clean way to bridge core::hash::Hash and std::hash::Hash implementations?
impl ::std::hash::Hash for Output {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
let mut vec = Vec::new();
ser::serialize(&mut vec, &self).expect("serialization failed");
::std::hash::Hash::hash(&vec, state);
}
}
impl Writeable for TxKernel {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
ser_multiwrite!(
@@ -206,7 +215,7 @@ impl TxKernel {
..self
}
}
/// Size in bytes of a kernel, necessary for binary storage
pub fn size() -> usize {
17 + // features plus fee and lock_height
@@ -449,7 +458,7 @@ impl Transaction {
/// Primarily a reference to an output being spent by the transaction.
/// But also information required to verify coinbase maturity through
/// the lock_height hashed in the switch_commit_hash.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Hash)]
pub struct Input{
/// The features of the output being spent.
/// We will check maturity for coinbase output.
@@ -571,7 +580,7 @@ impl SwitchCommitHashKey {
}
/// Definition of the switch commitment hash
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Hash, PartialEq, Serialize, Deserialize)]
pub struct SwitchCommitHash([u8; SWITCH_COMMIT_HASH_SIZE]);
/// Implementation of Writeable for a switch commitment hash
@@ -678,6 +687,15 @@ pub struct Output {
hashable_ord!(Output);
/// TODO - no clean way to bridge core::hash::Hash and std::hash::Hash implementations?
impl ::std::hash::Hash for TxKernel {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
let mut vec = Vec::new();
ser::serialize(&mut vec, &self).expect("serialization failed");
::std::hash::Hash::hash(&vec, state);
}
}
/// Implementation of Writeable for a transaction Output, defines how to write
/// an Output as binary.
impl Writeable for Output {
-8
View File
@@ -608,14 +608,6 @@ impl AsFixedBytes for ::util::secp::pedersen::RangeProof {
return self.plen;
}
}
// // TODO - is this (single byte) so we do not ever serialize a secret_key?
// // Note: we *can* serialize a blinding_factor built from a secret_key
// // but this needs to be done explicitly (tx kernel offset for example)
// impl AsFixedBytes for ::util::secp::key::SecretKey {
// fn len(&self) -> usize {
// return 1;
// }
// }
impl AsFixedBytes for ::util::secp::Signature {
fn len(&self) -> usize {
return 64;