Limit transaction size in the mempool (#2917)
* Limit tx size Zebra now limits the transaction size in the `zcash_deserialize()` method for `Transaction`. * Remove unused error variants (#2941) Co-authored-by: Conrado Gouvea <conrado@zfnd.org> * Limit tx size Zebra now limits the transaction size in the `zcash_deserialize()` method for `Transaction`. * Test the tx deserialization limit Co-authored-by: Conrado Gouvea <conrado@zfnd.org>
This commit is contained in:
parent
451448ef99
commit
2920d838ff
|
|
@ -522,10 +522,13 @@ impl ZcashSerialize for Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZcashDeserialize for Transaction {
|
impl ZcashDeserialize for Transaction {
|
||||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
|
||||||
|
// If the limit is reached, we'll get an UnexpectedEof error.
|
||||||
|
let mut limited_reader = reader.take(MAX_BLOCK_BYTES);
|
||||||
|
|
||||||
let (version, overwintered) = {
|
let (version, overwintered) = {
|
||||||
const LOW_31_BITS: u32 = (1 << 31) - 1;
|
const LOW_31_BITS: u32 = (1 << 31) - 1;
|
||||||
let header = reader.read_u32::<LittleEndian>()?;
|
let header = limited_reader.read_u32::<LittleEndian>()?;
|
||||||
(header & LOW_31_BITS, header >> 31 != 0)
|
(header & LOW_31_BITS, header >> 31 != 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -537,22 +540,22 @@ impl ZcashDeserialize for Transaction {
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
match (version, overwintered) {
|
match (version, overwintered) {
|
||||||
(1, false) => Ok(Transaction::V1 {
|
(1, false) => Ok(Transaction::V1 {
|
||||||
inputs: Vec::zcash_deserialize(&mut reader)?,
|
inputs: Vec::zcash_deserialize(&mut limited_reader)?,
|
||||||
outputs: Vec::zcash_deserialize(&mut reader)?,
|
outputs: Vec::zcash_deserialize(&mut limited_reader)?,
|
||||||
lock_time: LockTime::zcash_deserialize(&mut reader)?,
|
lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
|
||||||
}),
|
}),
|
||||||
(2, false) => {
|
(2, false) => {
|
||||||
// Version 2 transactions use Sprout-on-BCTV14.
|
// Version 2 transactions use Sprout-on-BCTV14.
|
||||||
type OptV2Jsd = Option<JoinSplitData<Bctv14Proof>>;
|
type OptV2Jsd = Option<JoinSplitData<Bctv14Proof>>;
|
||||||
Ok(Transaction::V2 {
|
Ok(Transaction::V2 {
|
||||||
inputs: Vec::zcash_deserialize(&mut reader)?,
|
inputs: Vec::zcash_deserialize(&mut limited_reader)?,
|
||||||
outputs: Vec::zcash_deserialize(&mut reader)?,
|
outputs: Vec::zcash_deserialize(&mut limited_reader)?,
|
||||||
lock_time: LockTime::zcash_deserialize(&mut reader)?,
|
lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
|
||||||
joinsplit_data: OptV2Jsd::zcash_deserialize(&mut reader)?,
|
joinsplit_data: OptV2Jsd::zcash_deserialize(&mut limited_reader)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
(3, true) => {
|
(3, true) => {
|
||||||
let id = reader.read_u32::<LittleEndian>()?;
|
let id = limited_reader.read_u32::<LittleEndian>()?;
|
||||||
// Consensus rule:
|
// Consensus rule:
|
||||||
// > [Overwinter only, pre-Sapling] The transaction version number MUST be 3, and the version group ID MUST be 0x03C48270.
|
// > [Overwinter only, pre-Sapling] The transaction version number MUST be 3, and the version group ID MUST be 0x03C48270.
|
||||||
//
|
//
|
||||||
|
|
@ -565,15 +568,15 @@ impl ZcashDeserialize for Transaction {
|
||||||
// Version 3 transactions use Sprout-on-BCTV14.
|
// Version 3 transactions use Sprout-on-BCTV14.
|
||||||
type OptV3Jsd = Option<JoinSplitData<Bctv14Proof>>;
|
type OptV3Jsd = Option<JoinSplitData<Bctv14Proof>>;
|
||||||
Ok(Transaction::V3 {
|
Ok(Transaction::V3 {
|
||||||
inputs: Vec::zcash_deserialize(&mut reader)?,
|
inputs: Vec::zcash_deserialize(&mut limited_reader)?,
|
||||||
outputs: Vec::zcash_deserialize(&mut reader)?,
|
outputs: Vec::zcash_deserialize(&mut limited_reader)?,
|
||||||
lock_time: LockTime::zcash_deserialize(&mut reader)?,
|
lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
|
||||||
expiry_height: block::Height(reader.read_u32::<LittleEndian>()?),
|
expiry_height: block::Height(limited_reader.read_u32::<LittleEndian>()?),
|
||||||
joinsplit_data: OptV3Jsd::zcash_deserialize(&mut reader)?,
|
joinsplit_data: OptV3Jsd::zcash_deserialize(&mut limited_reader)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
(4, true) => {
|
(4, true) => {
|
||||||
let id = reader.read_u32::<LittleEndian>()?;
|
let id = limited_reader.read_u32::<LittleEndian>()?;
|
||||||
// Consensus rules:
|
// Consensus rules:
|
||||||
// > [Sapling to Canopy inclusive, pre-NU5] The transaction version number MUST be 4, and the version group ID MUST be 0x892F2085.
|
// > [Sapling to Canopy inclusive, pre-NU5] The transaction version number MUST be 4, and the version group ID MUST be 0x892F2085.
|
||||||
// >
|
// >
|
||||||
|
|
@ -597,20 +600,20 @@ impl ZcashDeserialize for Transaction {
|
||||||
// instead we have to pull the component parts out manually and
|
// instead we have to pull the component parts out manually and
|
||||||
// then assemble them.
|
// then assemble them.
|
||||||
|
|
||||||
let inputs = Vec::zcash_deserialize(&mut reader)?;
|
let inputs = Vec::zcash_deserialize(&mut limited_reader)?;
|
||||||
let outputs = Vec::zcash_deserialize(&mut reader)?;
|
let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
|
||||||
let lock_time = LockTime::zcash_deserialize(&mut reader)?;
|
let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
|
||||||
let expiry_height = block::Height(reader.read_u32::<LittleEndian>()?);
|
let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
|
||||||
|
|
||||||
let value_balance = (&mut reader).zcash_deserialize_into()?;
|
let value_balance = (&mut limited_reader).zcash_deserialize_into()?;
|
||||||
let shielded_spends = Vec::zcash_deserialize(&mut reader)?;
|
let shielded_spends = Vec::zcash_deserialize(&mut limited_reader)?;
|
||||||
let shielded_outputs =
|
let shielded_outputs =
|
||||||
Vec::<sapling::OutputInTransactionV4>::zcash_deserialize(&mut reader)?
|
Vec::<sapling::OutputInTransactionV4>::zcash_deserialize(&mut limited_reader)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Output::from_v4)
|
.map(Output::from_v4)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let joinsplit_data = OptV4Jsd::zcash_deserialize(&mut reader)?;
|
let joinsplit_data = OptV4Jsd::zcash_deserialize(&mut limited_reader)?;
|
||||||
|
|
||||||
let sapling_transfers = if !shielded_spends.is_empty() {
|
let sapling_transfers = if !shielded_spends.is_empty() {
|
||||||
Some(sapling::TransferData::SpendsAndMaybeOutputs {
|
Some(sapling::TransferData::SpendsAndMaybeOutputs {
|
||||||
|
|
@ -636,7 +639,7 @@ impl ZcashDeserialize for Transaction {
|
||||||
Some(transfers) => Some(sapling::ShieldedData {
|
Some(transfers) => Some(sapling::ShieldedData {
|
||||||
value_balance,
|
value_balance,
|
||||||
transfers,
|
transfers,
|
||||||
binding_sig: reader.read_64_bytes()?.into(),
|
binding_sig: limited_reader.read_64_bytes()?.into(),
|
||||||
}),
|
}),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
@ -658,31 +661,30 @@ impl ZcashDeserialize for Transaction {
|
||||||
// > If the transaction version number is 5 then the version group ID MUST be 0x26A7270A.
|
// > If the transaction version number is 5 then the version group ID MUST be 0x26A7270A.
|
||||||
//
|
//
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
let id = reader.read_u32::<LittleEndian>()?;
|
let id = limited_reader.read_u32::<LittleEndian>()?;
|
||||||
if id != TX_V5_VERSION_GROUP_ID {
|
if id != TX_V5_VERSION_GROUP_ID {
|
||||||
return Err(SerializationError::Parse("expected TX_V5_VERSION_GROUP_ID"));
|
return Err(SerializationError::Parse("expected TX_V5_VERSION_GROUP_ID"));
|
||||||
}
|
}
|
||||||
// convert the nConsensusBranchId to a NetworkUpgrade
|
// convert the nConsensusBranchId to a NetworkUpgrade
|
||||||
let network_upgrade = NetworkUpgrade::from_branch_id(
|
let network_upgrade =
|
||||||
reader.read_u32::<LittleEndian>()?,
|
NetworkUpgrade::from_branch_id(limited_reader.read_u32::<LittleEndian>()?)
|
||||||
)
|
.ok_or(SerializationError::Parse(
|
||||||
.ok_or(SerializationError::Parse(
|
"expected a valid network upgrade from the consensus branch id",
|
||||||
"expected a valid network upgrade from the consensus branch id",
|
))?;
|
||||||
))?;
|
|
||||||
|
|
||||||
// transaction validity time and height limits
|
// transaction validity time and height limits
|
||||||
let lock_time = LockTime::zcash_deserialize(&mut reader)?;
|
let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
|
||||||
let expiry_height = block::Height(reader.read_u32::<LittleEndian>()?);
|
let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
|
||||||
|
|
||||||
// transparent
|
// transparent
|
||||||
let inputs = Vec::zcash_deserialize(&mut reader)?;
|
let inputs = Vec::zcash_deserialize(&mut limited_reader)?;
|
||||||
let outputs = Vec::zcash_deserialize(&mut reader)?;
|
let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
|
||||||
|
|
||||||
// sapling
|
// sapling
|
||||||
let sapling_shielded_data = (&mut reader).zcash_deserialize_into()?;
|
let sapling_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
// orchard
|
// orchard
|
||||||
let orchard_shielded_data = (&mut reader).zcash_deserialize_into()?;
|
let orchard_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
|
||||||
|
|
||||||
Ok(Transaction::V5 {
|
Ok(Transaction::V5 {
|
||||||
network_upgrade,
|
network_upgrade,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
|
@ -200,6 +201,60 @@ fn zip243_deserialize_and_round_trip() {
|
||||||
assert_eq!(&zebra_test::vectors::ZIP243_3[..], &data3[..]);
|
assert_eq!(&zebra_test::vectors::ZIP243_3[..], &data3[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_large_transaction() {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
// Create a dummy input and output.
|
||||||
|
let input =
|
||||||
|
transparent::Input::zcash_deserialize(&zebra_test::vectors::DUMMY_INPUT1[..]).unwrap();
|
||||||
|
let output =
|
||||||
|
transparent::Output::zcash_deserialize(&zebra_test::vectors::DUMMY_OUTPUT1[..]).unwrap();
|
||||||
|
|
||||||
|
// Create a lock time.
|
||||||
|
let lock_time = LockTime::Time(DateTime::<Utc>::from_utc(
|
||||||
|
NaiveDateTime::from_timestamp(61, 0),
|
||||||
|
Utc,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Serialize the input so that we can determine its serialized size.
|
||||||
|
let mut input_data = Vec::new();
|
||||||
|
input
|
||||||
|
.zcash_serialize(&mut input_data)
|
||||||
|
.expect("input should serialize");
|
||||||
|
|
||||||
|
// Calculate the number of inputs that fit into the transaction size limit.
|
||||||
|
let tx_inputs_num = MAX_BLOCK_BYTES as usize / input_data.len();
|
||||||
|
|
||||||
|
// Set the precalculated amount of inputs and a single output.
|
||||||
|
let inputs = std::iter::repeat(input)
|
||||||
|
.take(tx_inputs_num)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let outputs = vec![output];
|
||||||
|
|
||||||
|
// Create an oversized transaction. Adding the output and lock time causes
|
||||||
|
// the transaction to overflow the threshold.
|
||||||
|
let oversized_tx = Transaction::V1 {
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
lock_time,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Serialize the transaction.
|
||||||
|
let mut tx_data = Vec::new();
|
||||||
|
oversized_tx
|
||||||
|
.zcash_serialize(&mut tx_data)
|
||||||
|
.expect("transaction should serialize");
|
||||||
|
|
||||||
|
// Check that the transaction is oversized.
|
||||||
|
assert!(tx_data.len() > MAX_BLOCK_BYTES as usize);
|
||||||
|
|
||||||
|
// The deserialization should fail because the transaction is too big.
|
||||||
|
Transaction::zcash_deserialize(&tx_data[..])
|
||||||
|
.expect_err("transaction should not deserialize due to its size");
|
||||||
|
}
|
||||||
|
|
||||||
// Transaction V5 test vectors
|
// Transaction V5 test vectors
|
||||||
|
|
||||||
/// An empty transaction v5, with no Orchard, Sapling, or Transparent data
|
/// An empty transaction v5, with no Orchard, Sapling, or Transparent data
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue