From 2d2603cf65e7dc8ccffe80658c2c430b7f3bf55c Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 19 Feb 2020 10:46:32 -0800 Subject: [PATCH] Add a CoinbaseData field, replacing Vec. The CoinbaseData field can only be constructed by the transaction parser, so we can ensure that a coinbase input is always serializable, as CoinbaseData instances can't be constructed outside of the parser that maintains the data size invariant. --- zebra-chain/src/transaction.rs | 2 +- zebra-chain/src/transaction/serialize.rs | 30 +++++++++++++--------- zebra-chain/src/transaction/tests.rs | 2 +- zebra-chain/src/transaction/transparent.rs | 23 ++++++++++++++--- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index d771b498..37472c1b 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -14,7 +14,7 @@ mod tests; pub use hash::TransactionHash; pub use joinsplit::{JoinSplit, JoinSplitData}; pub use shielded_data::{OutputDescription, ShieldedData, SpendDescription}; -pub use transparent::{OutPoint, TransparentInput, TransparentOutput}; +pub use transparent::{CoinbaseData, OutPoint, TransparentInput, TransparentOutput}; use crate::proofs::{Bctv14Proof, Groth16Proof}; use crate::types::{BlockHeight, LockTime}; diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index 4d41de96..914fdf77 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -46,23 +46,28 @@ impl ZcashDeserialize for OutPoint { // unrepresentable, we need just enough parsing of Bitcoin scripts to parse the // coinbase height and split off the rest of the (inert) coinbase data. -fn parse_coinbase_height(mut data: Vec) -> Result<(BlockHeight, Vec), SerializationError> { +fn parse_coinbase_height( + mut data: Vec, +) -> Result<(BlockHeight, CoinbaseData), SerializationError> { match (data.get(0), data.len()) { // Blocks 1 through 16 inclusive encode block height with OP_N opcodes. - (Some(op_n @ 0x51..=0x60), len) if len >= 1 => { - Ok((BlockHeight((op_n - 0x50) as u32), data.split_off(1))) - } + (Some(op_n @ 0x51..=0x60), len) if len >= 1 => Ok(( + BlockHeight((op_n - 0x50) as u32), + CoinbaseData(data.split_off(1)), + )), // Blocks 17 through 256 exclusive encode block height with the `0x01` opcode. - (Some(0x01), len) if len >= 2 => Ok((BlockHeight(data[1] as u32), data.split_off(2))), + (Some(0x01), len) if len >= 2 => { + Ok((BlockHeight(data[1] as u32), CoinbaseData(data.split_off(2)))) + } // Blocks 256 through 65536 exclusive encode block height with the `0x02` opcode. (Some(0x02), len) if len >= 3 => Ok(( BlockHeight(data[1] as u32 + ((data[2] as u32) << 8)), - data.split_off(3), + CoinbaseData(data.split_off(3)), )), // Blocks 65536 through 2**24 exclusive encode block height with the `0x03` opcode. (Some(0x03), len) if len >= 4 => Ok(( BlockHeight(data[1] as u32 + ((data[2] as u32) << 8) + ((data[3] as u32) << 16)), - data.split_off(4), + CoinbaseData(data.split_off(4)), )), // The genesis block does not encode the block height by mistake; special case it. // The first five bytes are [4, 255, 255, 7, 31], the little-endian encoding of @@ -70,7 +75,9 @@ fn parse_coinbase_height(mut data: Vec) -> Result<(BlockHeight, Vec), Se // while remaining below the maximum `BlockHeight` of 500_000_000 forced by `LockTime`. // While it's unlikely this code will ever process a block height that high, this means // we don't need to maintain a cascade of different invariants for allowable `BlockHeight`s. - (Some(0x04), _) if data[..] == GENESIS_COINBASE_DATA[..] => Ok((BlockHeight(0), data)), + (Some(0x04), _) if data[..] == GENESIS_COINBASE_DATA[..] => { + Ok((BlockHeight(0), CoinbaseData(data))) + } // As noted above, this is included for completeness. (Some(0x04), len) if len >= 5 => { let h = data[1] as u32 @@ -78,7 +85,7 @@ fn parse_coinbase_height(mut data: Vec) -> Result<(BlockHeight, Vec), Se + ((data[3] as u32) << 16) + ((data[4] as u32) << 24); if h < 500_000_000 { - Ok((BlockHeight(h), data.split_off(5))) + Ok((BlockHeight(h), CoinbaseData(data.split_off(5)))) } else { Err(SerializationError::Parse("Invalid block height")) } @@ -156,11 +163,10 @@ impl ZcashSerialize for TransparentInput { writer.write_all(&[0; 32][..])?; writer.write_u32::(0xffff_ffff)?; let height_len = coinbase_height_len(*height); - let total_len = height_len + data.len(); - assert!(total_len <= 100); + let total_len = height_len + data.as_ref().len(); writer.write_compactsize(total_len as u64)?; write_coinbase_height(*height, &mut writer)?; - writer.write_all(&data[..])?; + writer.write_all(&data.as_ref()[..])?; writer.write_u32::(*sequence)?; } } diff --git a/zebra-chain/src/transaction/tests.rs b/zebra-chain/src/transaction/tests.rs index 3ae35a50..3ac5bf19 100644 --- a/zebra-chain/src/transaction/tests.rs +++ b/zebra-chain/src/transaction/tests.rs @@ -135,7 +135,7 @@ impl Arbitrary for TransparentInput { .prop_map(|(height, data, sequence)| { TransparentInput::Coinbase { height, - data, + data: CoinbaseData(data), sequence, } }) diff --git a/zebra-chain/src/transaction/transparent.rs b/zebra-chain/src/transaction/transparent.rs index 211f32e7..baab89f5 100644 --- a/zebra-chain/src/transaction/transparent.rs +++ b/zebra-chain/src/transaction/transparent.rs @@ -7,6 +7,24 @@ use crate::types::{BlockHeight, Script}; use super::TransactionHash; +/// Arbitrary data inserted by miners into a coinbase transaction. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CoinbaseData( + /// Invariant: this vec, together with the coinbase height, must be less than + /// 100 bytes. We enforce this by only constructing CoinbaseData fields by + /// parsing blocks with 100-byte data fields. When we implement block + /// creation, we should provide a constructor for the coinbase data field + /// that restricts it to 95 = 100 -1 -4 bytes (safe for any block height up + /// to 500_000_000). + pub(super) Vec, +); + +impl AsRef<[u8]> for CoinbaseData { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + /// OutPoint /// /// A particular transaction output reference. @@ -37,9 +55,8 @@ pub enum TransparentInput { Coinbase { /// The height of this block. height: BlockHeight, - /// Approximately 100 bytes of data (95 to be safe). - /// XXX refine this type. - data: Vec, + /// Free data inserted by miners after the block height. + data: CoinbaseData, /// The sequence number for the output. sequence: u32, },