diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 692c17872..3b3b1b139 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -398,6 +398,53 @@ impl Block { Ok(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, + _outputs: Vec, + _kernels: Vec, + ) -> Block { + debug!( + LOGGER, + "block: hydrate_from: {}, {} cb outputs, {} cb kernels, {} tx kern_ids", + cb.hash(), + cb.out_full.len(), + cb.kern_full.len(), + cb.kern_ids.len(), + ); + + // we only support "empty" compact block for now + assert!(cb.kern_ids.is_empty()); + + let mut all_inputs = vec![]; + let mut all_outputs = vec![]; + let mut all_kernels = vec![]; + + all_outputs.extend(cb.out_full); + all_kernels.extend(cb.kern_full); + + all_inputs.sort(); + all_outputs.sort(); + all_kernels.sort(); + + Block { + header: cb.header, + inputs: all_inputs, + outputs: all_outputs, + kernels: all_kernels, + }.cut_through() + } + /// Generate the compact block representation. pub fn as_compact_block(&self) -> CompactBlock { let header = self.header.clone(); @@ -780,7 +827,7 @@ mod test { use core::hash::ZERO_HASH; use core::Transaction; use core::build::{self, input, output, with_fee}; - use core::test::tx2i1o; + use core::test::{tx1i2o, tx2i1o}; use keychain::{Identifier, Keychain}; use consensus::{MAX_BLOCK_WEIGHT, BLOCK_OUTPUT_WEIGHT}; use std::time::Instant; @@ -974,10 +1021,104 @@ mod test { assert_eq!(b.kernels, b2.kernels); } + #[test] + fn empty_block_serialized_size() { + let keychain = Keychain::from_random_seed().unwrap(); + let b = new_block(vec![], &keychain); + let mut vec = Vec::new(); + ser::serialize(&mut vec, &b).expect("serialization failed"); + assert_eq!( + vec.len(), + 5_676, + ); + } + + #[test] + fn block_single_tx_serialized_size() { + let keychain = Keychain::from_random_seed().unwrap(); + let tx1 = tx1i2o(); + let b = new_block(vec![&tx1], &keychain); + let mut vec = Vec::new(); + ser::serialize(&mut vec, &b).expect("serialization failed"); + assert_eq!( + vec.len(), + 16_224, + ); + } + + #[test] + fn empty_compact_block_serialized_size() { + let keychain = Keychain::from_random_seed().unwrap(); + let b = new_block(vec![], &keychain); + let mut vec = Vec::new(); + ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed"); + assert_eq!( + vec.len(), + 5_662, + ); + } + + #[test] + fn compact_block_single_tx_serialized_size() { + let keychain = Keychain::from_random_seed().unwrap(); + let tx1 = tx1i2o(); + let b = new_block(vec![&tx1], &keychain); + let mut vec = Vec::new(); + ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed"); + assert_eq!( + vec.len(), + 5_668, + ); + } + + #[test] + fn block_10_tx_serialized_size() { + let keychain = Keychain::from_random_seed().unwrap(); + + let mut txs = vec![]; + for _ in 0..10 { + let tx = tx1i2o(); + txs.push(tx); + } + + let b = new_block( + txs.iter().collect(), + &keychain, + ); + let mut vec = Vec::new(); + ser::serialize(&mut vec, &b).expect("serialization failed"); + assert_eq!( + vec.len(), + 111_156, + ); + } + + #[test] + fn compact_block_10_tx_serialized_size() { + let keychain = Keychain::from_random_seed().unwrap(); + + let mut txs = vec![]; + for _ in 0..10 { + let tx = tx1i2o(); + txs.push(tx); + } + + let b = new_block( + txs.iter().collect(), + &keychain, + ); + let mut vec = Vec::new(); + ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed"); + assert_eq!( + vec.len(), + 5_722, + ); + } + #[test] fn convert_block_to_compact_block() { let keychain = Keychain::from_random_seed().unwrap(); - let tx1 = tx2i1o(); + let tx1 = tx1i2o(); let b = new_block(vec![&tx1], &keychain); let cb = b.as_compact_block(); @@ -996,6 +1137,17 @@ mod test { ); } + #[test] + fn hydrate_empty_compact_block() { + 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![]); + assert_eq!(hb.header, b.header); + assert_eq!(hb.outputs, b.outputs); + assert_eq!(hb.kernels, b.kernels); + } + #[test] fn serialize_deserialize_compact_block() { let b = CompactBlock { diff --git a/core/src/core/mod.rs b/core/src/core/mod.rs index 9677c4fab..3d6a0d04f 100644 --- a/core/src/core/mod.rs +++ b/core/src/core/mod.rs @@ -537,4 +537,24 @@ mod test { ).map(|(tx, _)| tx) .unwrap() } + + // utility producing a transaction with a single input + // and two outputs (one change output) + pub fn tx1i2o() -> Transaction { + let keychain = keychain::Keychain::from_random_seed().unwrap(); + let key_id1 = keychain.derive_key_id(1).unwrap(); + let key_id2 = keychain.derive_key_id(2).unwrap(); + let key_id3 = keychain.derive_key_id(3).unwrap(); + + build::transaction( + vec![ + input(6, ZERO_HASH, key_id1), + output(3, key_id2), + output(1, key_id3), + with_fee(2), + ], + &keychain, + ).map(|(tx, _)| tx) + .unwrap() + } } diff --git a/grin/src/adapters.rs b/grin/src/adapters.rs index 410137456..f3cf0d6ff 100644 --- a/grin/src/adapters.rs +++ b/grin/src/adapters.rs @@ -68,46 +68,43 @@ impl p2p::ChainAdapter for NetToChainAdapter { } fn block_received(&self, b: core::Block, addr: SocketAddr) -> bool { - let bhash = b.hash(); debug!( LOGGER, "Received block {} at {} from {}, going to process.", - bhash, + b.hash(), b.header.height, addr, ); - // pushing the new block through the chain pipeline - let res = self.chain.process_block(b, self.chain_opts()); - if let Err(ref e) = res { - debug!(LOGGER, "Block {} refused by chain: {:?}", bhash, e); - if e.is_bad_block() { - debug!(LOGGER, "block_received: {} is a bad block, resetting head", bhash); - let _ = self.chain.reset_head(); - return false; - } - }; - true + self.process_block(b) } - fn compact_block_received(&self, bh: core::CompactBlock, addr: SocketAddr) -> bool { - let bhash = bh.hash(); + fn compact_block_received(&self, cb: core::CompactBlock, addr: SocketAddr) -> bool { + let bhash = cb.hash(); debug!( LOGGER, "Received compact_block {} at {} from {}, going to process.", bhash, - bh.header.height, + cb.header.height, addr, ); - debug!( - LOGGER, - "*** cannot hydrate compact block (not yet implemented), falling back to requesting full block", - ); + if cb.kern_ids.is_empty() { + let block = core::Block::hydrate_from(cb, vec![], vec![], vec![]); - self.request_block(&bh.header, &addr); + // push the freshly hydrated block through the chain pipeline + self.process_block(block) + } else { + // TODO - do we need to validate the header here to be sure it is not total garbage? - true + debug!( + LOGGER, + "*** cannot hydrate non-empty compact block (not yet implemented), \ + falling back to requesting full block", + ); + self.request_block(&cb.header, &addr); + true + } } fn header_received(&self, bh: core::BlockHeader, addr: SocketAddr) -> bool { @@ -298,6 +295,22 @@ impl NetToChainAdapter { } } + // pushing the new block through the chain pipeline + // remembering to reset the head if we have a bad block + fn process_block(&self, b: core::Block) -> bool { + let bhash = b.hash(); + let res = self.chain.process_block(b, self.chain_opts()); + if let Err(ref e) = res { + debug!(LOGGER, "Block {} refused by chain: {:?}", bhash, e); + if e.is_bad_block() { + debug!(LOGGER, "adapter: process_block: {} is a bad block, resetting head", bhash); + let _ = self.chain.reset_head(); + return false; + } + }; + true + } + // After receiving a compact block if we cannot successfully hydrate // it into a full block then fallback to requesting the full block // from the same peer that gave us the compact block