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 std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block::{Block, BlockHeader, BlockHeaderHash, MAX_BLOCK_BYTES},
|
block::{Block, BlockHeader, MAX_BLOCK_BYTES},
|
||||||
equihash_solution::EquihashSolution,
|
|
||||||
merkle_tree::MerkleTreeRootHash,
|
|
||||||
note_commitment_tree::SaplingNoteTreeRootHash,
|
|
||||||
serialization::{ZcashDeserialize, ZcashSerialize},
|
serialization::{ZcashDeserialize, ZcashSerialize},
|
||||||
transaction::{Transaction, TransparentInput, TransparentOutput},
|
transaction::{Transaction, TransparentInput, TransparentOutput},
|
||||||
types::LockTime,
|
types::LockTime,
|
||||||
|
|
@ -14,17 +11,7 @@ use crate::{
|
||||||
|
|
||||||
/// Generate a block header
|
/// Generate a block header
|
||||||
pub fn block_header() -> BlockHeader {
|
pub fn block_header() -> BlockHeader {
|
||||||
let some_bytes = [0; 32];
|
BlockHeader::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap()
|
||||||
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]),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a block with multiple transactions just below limit
|
/// Generate a block with multiple transactions just below limit
|
||||||
|
|
|
||||||
|
|
@ -130,4 +130,12 @@ impl Transaction {
|
||||||
Transaction::V4 { expiry_height, .. } => Some(*expiry_height),
|
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> {
|
struct BlockVerifier<S> {
|
||||||
/// The underlying `ZebraState`, possibly wrapped in other services.
|
/// The underlying `ZebraState`, possibly wrapped in other services.
|
||||||
state_service: S,
|
state_service: S,
|
||||||
|
|
@ -95,6 +113,7 @@ where
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
node_time_check(block.header.time, now)?;
|
node_time_check(block.header.time, now)?;
|
||||||
block.header.is_equihash_solution_valid()?;
|
block.header.is_equihash_solution_valid()?;
|
||||||
|
coinbase_check(block.as_ref())?;
|
||||||
|
|
||||||
// `Tower::Buffer` requires a 1:1 relationship between `poll()`s
|
// `Tower::Buffer` requires a 1:1 relationship between `poll()`s
|
||||||
// and `call()`s, because it reserves a buffer slot in each
|
// and `call()`s, because it reserves a buffer slot in each
|
||||||
|
|
@ -158,7 +177,9 @@ mod tests {
|
||||||
use tower::{util::ServiceExt, Service};
|
use tower::{util::ServiceExt, Service};
|
||||||
|
|
||||||
use zebra_chain::block::Block;
|
use zebra_chain::block::Block;
|
||||||
|
use zebra_chain::block::BlockHeader;
|
||||||
use zebra_chain::serialization::ZcashDeserialize;
|
use zebra_chain::serialization::ZcashDeserialize;
|
||||||
|
use zebra_chain::transaction::Transaction;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn time_check_past_block() {
|
fn time_check_past_block() {
|
||||||
|
|
@ -499,7 +520,8 @@ mod tests {
|
||||||
|
|
||||||
// Service variables
|
// Service variables
|
||||||
let state_service = Box::new(zebra_state::in_memory::init());
|
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))?;
|
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
|
||||||
|
|
||||||
// Get a valid block
|
// Get a valid block
|
||||||
|
|
@ -516,6 +538,8 @@ mod tests {
|
||||||
// Change nonce to something invalid
|
// Change nonce to something invalid
|
||||||
block.header.nonce = [0; 32];
|
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
|
// Error: invalid equihash solution for BlockHeader
|
||||||
ready_verifier_service
|
ready_verifier_service
|
||||||
.call(Arc::new(block.clone()))
|
.call(Arc::new(block.clone()))
|
||||||
|
|
@ -524,4 +548,71 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
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