Add consensus coinbase checks (#593)

* add coinbase check and test case

Co-authored-by: Jane Lusby <jane@zfnd.org>
This commit is contained in:
Alfredo Garcia 2020-07-09 20:13:44 -03:00 committed by GitHub
parent 765e1e61e7
commit 5d6a5ca329
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 16 deletions

View File

@ -3,10 +3,7 @@ use chrono::{DateTime, NaiveDateTime, Utc};
use std::sync::Arc;
use crate::{
block::{Block, BlockHeader, BlockHeaderHash, MAX_BLOCK_BYTES},
equihash_solution::EquihashSolution,
merkle_tree::MerkleTreeRootHash,
note_commitment_tree::SaplingNoteTreeRootHash,
block::{Block, BlockHeader, MAX_BLOCK_BYTES},
serialization::{ZcashDeserialize, ZcashSerialize},
transaction::{Transaction, TransparentInput, TransparentOutput},
types::LockTime,
@ -14,17 +11,7 @@ use crate::{
/// Generate a block header
pub fn block_header() -> BlockHeader {
let some_bytes = [0; 32];
BlockHeader {
version: 4,
previous_block_hash: BlockHeaderHash(some_bytes),
merkle_root_hash: MerkleTreeRootHash(some_bytes),
final_sapling_root_hash: SaplingNoteTreeRootHash(some_bytes),
time: DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc),
bits: 0,
nonce: some_bytes,
solution: EquihashSolution([0; 1344]),
}
BlockHeader::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap()
}
/// Generate a block with multiple transactions just below limit

View File

@ -130,4 +130,12 @@ impl Transaction {
Transaction::V4 { expiry_height, .. } => Some(*expiry_height),
}
}
/// Returns `true` if transaction contains any coinbase inputs.
pub fn contains_coinbase_input(&self) -> bool {
self.inputs().any(|input| match input {
TransparentInput::Coinbase { .. } => true,
_ => false,
})
}
}

View File

@ -50,6 +50,24 @@ pub(crate) fn node_time_check(
}
}
/// [3.10]: https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions
pub(crate) fn coinbase_check(block: &Block) -> Result<(), Error> {
if block.coinbase_height().is_some() {
// No coinbase inputs in additional transactions allowed
if block
.transactions
.iter()
.skip(1)
.any(|tx| tx.contains_coinbase_input())
{
Err("coinbase input found in additional transaction")?
}
Ok(())
} else {
Err("no coinbase transaction in block")?
}
}
struct BlockVerifier<S> {
/// The underlying `ZebraState`, possibly wrapped in other services.
state_service: S,
@ -95,6 +113,7 @@ where
let now = Utc::now();
node_time_check(block.header.time, now)?;
block.header.is_equihash_solution_valid()?;
coinbase_check(block.as_ref())?;
// `Tower::Buffer` requires a 1:1 relationship between `poll()`s
// and `call()`s, because it reserves a buffer slot in each
@ -158,7 +177,9 @@ mod tests {
use tower::{util::ServiceExt, Service};
use zebra_chain::block::Block;
use zebra_chain::block::BlockHeader;
use zebra_chain::serialization::ZcashDeserialize;
use zebra_chain::transaction::Transaction;
#[test]
fn time_check_past_block() {
@ -499,7 +520,8 @@ mod tests {
// Service variables
let state_service = Box::new(zebra_state::in_memory::init());
let mut block_verifier = super::init(state_service);
let mut block_verifier = super::init(state_service.clone());
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
// Get a valid block
@ -516,6 +538,8 @@ mod tests {
// Change nonce to something invalid
block.header.nonce = [0; 32];
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
// Error: invalid equihash solution for BlockHeader
ready_verifier_service
.call(Arc::new(block.clone()))
@ -524,4 +548,71 @@ mod tests {
Ok(())
}
#[tokio::test]
#[spandoc::spandoc]
async fn coinbase() -> Result<(), Report> {
zebra_test::init();
// Service variables
let state_service = Box::new(zebra_state::in_memory::init());
let mut block_verifier = super::init(state_service.clone());
// Get a header of a block
let header =
BlockHeader::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap();
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
// Test 1: Empty transaction
let block = Block {
header,
transactions: Vec::new(),
};
// Error: no coinbase transaction in block
ready_verifier_service
.call(Arc::new(block.clone()))
.await
.expect_err("fail with no coinbase transaction in block");
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
// Test 2: Transaction at first position is not coinbase
let mut transactions = Vec::new();
let tx = Transaction::zcash_deserialize(&zebra_test::vectors::DUMMY_TX1[..]).unwrap();
transactions.push(Arc::new(tx));
let block = Block {
header,
transactions,
};
// Error: no coinbase transaction in block
ready_verifier_service
.call(Arc::new(block))
.await
.expect_err("fail with no coinbase transaction in block");
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
// Test 3: Invalid coinbase position
let mut block =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
assert_eq!(block.transactions.len(), 1);
// Extract the coinbase transaction from the block
let coinbase_transaction = block.transactions.get(0).unwrap().clone();
// Add another coinbase transaction to block
block.transactions.push(coinbase_transaction);
assert_eq!(block.transactions.len(), 2);
// Error: coinbase input found in additional transaction
ready_verifier_service
.call(Arc::new(block))
.await
.expect_err("fail with coinbase input found in additional transaction");
Ok(())
}
}

File diff suppressed because one or more lines are too long