diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 3487e608..58ec1605 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -38,6 +38,7 @@ bitflags! { } /// Block header, fairly standard compared to other blockchains. +#[derive(Debug, PartialEq)] pub struct BlockHeader { /// Height of this block since the genesis block (height 0) pub height: u64, @@ -136,6 +137,7 @@ impl Readable for BlockHeader { /// non-explicit, assumed to be deducible from block height (similar to /// bitcoin's schedule) and expressed as a global transaction fee (added v.H), /// additive to the total of fees ever collected. +#[derive(Debug)] pub struct Block { /// The header with metadata and commitments to the rest of the data pub header: BlockHeader, @@ -381,16 +383,20 @@ impl Block { pub fn validate(&self, secp: &Secp256k1) -> Result<(), secp::Error> { self.verify_coinbase(secp)?; self.verify_kernels(secp)?; - - // verify the transaction Merkle root - let tx_merkle = merkle_inputs_outputs(&self.inputs, &self.outputs); - if tx_merkle != self.header.tx_merkle { - // TODO more specific error - return Err(secp::Error::IncorrectCommitSum); - } - Ok(()) + self.verify_merkle_inputs_outputs()?; + Ok(()) } + /// Verify the transaction Merkle root + pub fn verify_merkle_inputs_outputs(&self) -> Result<(), secp::Error> { + let tx_merkle = merkle_inputs_outputs(&self.inputs, &self.outputs); + if tx_merkle != self.header.tx_merkle { + // TODO more specific error + return Err(secp::Error::IncorrectCommitSum); + } + Ok(()) + } + /// Validate the sum of input/output commitments match the sum in kernels /// and /// that all kernel signatures are valid. @@ -551,4 +557,85 @@ mod test { assert_eq!(b3.inputs.len(), 3); assert_eq!(b3.outputs.len(), 4); } + + #[test] + fn empty_block_with_coinbase_is_valid() { + let ref secp = new_secp(); + let b = new_block(vec![], secp); + + assert_eq!(b.inputs.len(), 0); + assert_eq!(b.outputs.len(), 1); + assert_eq!(b.kernels.len(), 1); + + let coinbase_outputs = b.outputs + .iter() + .filter(|out| out.features.contains(COINBASE_OUTPUT)) + .map(|o| o.clone()) + .collect::>(); + assert_eq!(coinbase_outputs.len(), 1); + + let coinbase_kernels = b.kernels + .iter() + .filter(|out| out.features.contains(COINBASE_KERNEL)) + .map(|o| o.clone()) + .collect::>(); + assert_eq!(coinbase_kernels.len(), 1); + + // the block should be valid here (single coinbase output with corresponding txn kernel) + assert_eq!(b.validate(&secp), Ok(())); + } + + #[test] + // test that flipping the COINBASE_OUTPUT flag on the output features + // invalidates the block and specifically it causes verify_coinbase to fail + // additionally verifying the merkle_inputs_outputs also fails + fn remove_coinbase_output_flag() { + let ref secp = new_secp(); + let mut b = new_block(vec![], secp); + + assert!(b.outputs[0].features.contains(COINBASE_OUTPUT)); + b.outputs[0].features.remove(COINBASE_OUTPUT); + + assert_eq!(b.verify_coinbase(&secp), Err(secp::Error::IncorrectCommitSum)); + assert_eq!(b.verify_kernels(&secp), Ok(())); + assert_eq!(b.verify_merkle_inputs_outputs(), Err(secp::Error::IncorrectCommitSum)); + + assert_eq!(b.validate(&secp), Err(secp::Error::IncorrectCommitSum)); + } + + #[test] + // test that flipping the COINBASE_KERNEL flag on the kernel features + // invalidates the block and specifically it causes verify_coinbase to fail + fn remove_coinbase_kernel_flag() { + let ref secp = new_secp(); + let mut b = new_block(vec![], secp); + + assert!(b.kernels[0].features.contains(COINBASE_KERNEL)); + b.kernels[0].features.remove(COINBASE_KERNEL); + + assert_eq!(b.verify_coinbase(&secp), Err(secp::Error::IncorrectCommitSum)); + assert_eq!(b.verify_kernels(&secp), Ok(())); + assert_eq!(b.verify_merkle_inputs_outputs(), Ok(())); + + assert_eq!(b.validate(&secp), Err(secp::Error::IncorrectCommitSum)); + } + + #[test] + fn serialize_deserialize_block() { + let ref secp = new_secp(); + let b = new_block(vec![], secp); + + let mut vec = Vec::new(); + ser::serialize(&mut vec, &b).expect("serialization failed"); + let b2: Block = ser::deserialize(&mut &vec[..]).unwrap(); + + assert_eq!(b.inputs, b2.inputs); + assert_eq!(b.outputs, b2.outputs); + assert_eq!(b.kernels, b2.kernels); + + // TODO - timestamps are not coming back equal here (UTC related?) - + // assert_eq!(b.header, b2.header); + // timestamp: Tm { tm_sec: 51, tm_min: 7, tm_hour: 23, tm_mday: 20, tm_mon: 7, tm_year: 117, tm_wday: 0, tm_yday: 231, tm_isdst: 1, tm_utcoff: -14400, tm_nsec: 780878000 }, + // timestamp: Tm { tm_sec: 51, tm_min: 7, tm_hour: 3, tm_mday: 21, tm_mon: 7, tm_year: 117, tm_wday: 1, tm_yday: 232, tm_isdst: 0, tm_utcoff: 0, tm_nsec: 0 }, + } } diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index c90ed225..f8837cb8 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -37,7 +37,7 @@ bitflags! { /// Pedersen commitment and the signature, that guarantees that the commitments /// amount to zero. The signature signs the fee, which is retained for /// signature validation. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct TxKernel { /// Options for a kernel's structure or use pub features: KernelFeatures, @@ -239,7 +239,7 @@ impl Transaction { /// A transaction input, mostly a reference to an output being spent by the /// transaction. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Input(pub Commitment); /// Implementation of Writeable for a transaction Input, defines how to write