Add consensus coinbase checks (#593)
* add coinbase check and test case Co-authored-by: Jane Lusby <jane@zfnd.org>
This commit is contained in:
parent
765e1e61e7
commit
5d6a5ca329
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue