diff --git a/consensus/src/operations.rs b/consensus/src/operations.rs index d4808e16fc..342090196c 100644 --- a/consensus/src/operations.rs +++ b/consensus/src/operations.rs @@ -53,6 +53,7 @@ pub struct CallParams { pub generator_pubkey: node_data::bls::PublicKey, pub to_slash: Vec, pub voters_pubkey: Option>, + pub max_txs_bytes: usize, } #[derive(Default)] diff --git a/consensus/src/proposal/block_generator.rs b/consensus/src/proposal/block_generator.rs index e557c8addb..9003ff54e8 100644 --- a/consensus/src/proposal/block_generator.rs +++ b/consensus/src/proposal/block_generator.rs @@ -8,6 +8,7 @@ use crate::commons::RoundUpdate; use crate::operations::{CallParams, Operations, Voter}; use node_data::ledger::{ to_str, Attestation, Block, Fault, IterationsInfo, Seed, Slash, + MAX_BLOCK_BODY_SIZE, }; use std::cmp::max; @@ -99,6 +100,18 @@ impl Generator { faults }; + // We always write the faults len in a u32 + let mut faults_size = u32::SIZE; + let faults_hashes: Vec<_> = faults + .iter() + .map(|f| { + faults_size += f.size(); + f.hash() + }) + .collect(); + + let faultroot = merkle_root(&faults_hashes); + let to_slash = Slash::from_iterations_and_faults(&failed_iterations, faults)?; @@ -107,6 +120,7 @@ impl Generator { generator_pubkey: ru.pubkey_bls.clone(), to_slash, voters_pubkey: Some(voters.to_owned()), + max_txs_bytes: MAX_BLOCK_BODY_SIZE - faults_size, }; let result = @@ -119,9 +133,6 @@ impl Generator { let txs: Vec<_> = result.txs.into_iter().map(|t| t.inner).collect(); let txroot = merkle_root(&tx_hashes[..]); - let faults = Vec::::new(); - let faults_hashes: Vec<_> = faults.iter().map(|f| f.hash()).collect(); - let faultroot = merkle_root(&faults_hashes); let timestamp = max(ru.timestamp() + MINIMUM_BLOCK_TIME, get_current_timestamp()); @@ -145,6 +156,10 @@ impl Generator { failed_iterations, }; - Ok(Block::new(blk_header, txs, faults).expect("block should be valid")) + Block::new(blk_header, txs, faults.to_vec()).map_err(|e| { + crate::operations::Error::InvalidEST(anyhow::anyhow!( + "Cannot create new block {e}", + )) + }) } } diff --git a/node-data/src/encoding.rs b/node-data/src/encoding.rs index 95b987bf7a..33c874cda2 100644 --- a/node-data/src/encoding.rs +++ b/node-data/src/encoding.rs @@ -11,7 +11,7 @@ use execution_core::transfer::Transaction as ProtocolTransaction; use crate::bls::PublicKeyBytes; use crate::ledger::{ Attestation, Block, Fault, Header, IterationsInfo, Label, SpentTransaction, - StepVotes, Transaction, + StepVotes, Transaction, MAX_BLOCK_BODY_SIZE, }; use crate::message::payload::{ QuorumType, Ratification, RatificationResult, ValidationResult, Vote, @@ -47,19 +47,33 @@ impl Serializable for Block { // Read transactions count let tx_len = Self::read_u32_le(r)?; + let mut body_size = 4; let txs = (0..tx_len) .map(|_| Transaction::read(r)) .collect::, _>>()?; + body_size += txs.iter().filter_map(|t| t.size).sum::(); // Read faults count let faults_len = Self::read_u32_le(r)?; + body_size += 4; let faults = (0..faults_len) .map(|_| Fault::read(r)) .collect::, _>>()?; - Block::new(header, txs, faults) + body_size += faults.iter().map(|f: &Fault| f.size()).sum::(); + + match header.version { + 0 if body_size > MAX_BLOCK_BODY_SIZE => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "Block body too large {body_size}/{MAX_BLOCK_BODY_SIZE}" + ), + ))?, + + _ => Block::new(header, txs, faults), + } } } @@ -87,6 +101,7 @@ impl Serializable for Transaction { let tx_type = Self::read_u32_le(r)?; let protocol_tx = Self::read_var_le_bytes32(r)?; + let tx_size = protocol_tx.len(); let inner = ProtocolTransaction::from_slice(&protocol_tx[..]) .map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?; @@ -94,6 +109,7 @@ impl Serializable for Transaction { inner, version, r#type: tx_type, + size: Some(tx_size), }) } } diff --git a/node-data/src/ledger.rs b/node-data/src/ledger.rs index 1423fc1c0a..c1bf9fa5c7 100644 --- a/node-data/src/ledger.rs +++ b/node-data/src/ledger.rs @@ -8,7 +8,7 @@ mod header; pub use header::{Header, Seed}; mod block; -pub use block::{Block, BlockWithLabel, Hash, Label}; +pub use block::*; mod transaction; pub use transaction::{SpendingId, SpentTransaction, Transaction}; diff --git a/node-data/src/ledger/block.rs b/node-data/src/ledger/block.rs index 49273dd743..3007f84dea 100644 --- a/node-data/src/ledger/block.rs +++ b/node-data/src/ledger/block.rs @@ -6,6 +6,10 @@ use super::*; +pub const MAX_BLOCK_SIZE: usize = 1_024 * 1_024; +pub const MAX_BLOCK_HEADER_SIZE: usize = 1530; +pub const MAX_BLOCK_BODY_SIZE: usize = MAX_BLOCK_SIZE - MAX_BLOCK_HEADER_SIZE; + pub type Hash = [u8; 32]; #[derive(Default, Debug, Clone)] diff --git a/node-data/src/ledger/faults.rs b/node-data/src/ledger/faults.rs index 960ffc0357..6d8ee1b204 100644 --- a/node-data/src/ledger/faults.rs +++ b/node-data/src/ledger/faults.rs @@ -35,6 +35,28 @@ pub enum Fault { DoubleValidationVote(FaultData, FaultData), } +impl Fault { + pub fn size(&self) -> usize { + // prev_block_hash + round + iter + const FAULT_CONSENSUS_HEADER_SIZE: usize = 32 + u64::SIZE + u8::SIZE; + // signer + signature + const FAULT_SIG_INFO_SIZE: usize = + BlsMultisigPublicKey::SIZE + BlsMultisigSignature::SIZE; + + const HEADERS: usize = FAULT_CONSENSUS_HEADER_SIZE * 2; + const SIG_INFOS: usize = FAULT_SIG_INFO_SIZE * 2; + let faults_data_size = match self { + Fault::DoubleCandidate(..) => 32 * 2, + Fault::DoubleRatificationVote(a, b) => { + a.data.size() + b.data.size() + } + Fault::DoubleValidationVote(a, b) => a.data.size() + b.data.size(), + }; + + HEADERS + SIG_INFOS + faults_data_size + } +} + #[derive(Debug, Error)] pub enum InvalidFault { #[error("Inner faults have same data")] @@ -64,7 +86,7 @@ impl From for InvalidFault { } impl Fault { - /// Hash the serialized form + /// Hash the serialized form (returning the bytes hashed) pub fn hash(&self) -> [u8; 32] { let mut b = vec![]; self.write(&mut b).expect("Write to a vec shall not fail"); diff --git a/node-data/src/ledger/transaction.rs b/node-data/src/ledger/transaction.rs index 7a2b413535..5890a5b5a8 100644 --- a/node-data/src/ledger/transaction.rs +++ b/node-data/src/ledger/transaction.rs @@ -15,6 +15,7 @@ pub struct Transaction { pub version: u32, pub r#type: u32, pub inner: ProtocolTransaction, + pub size: Option, } impl From for Transaction { @@ -23,6 +24,7 @@ impl From for Transaction { inner: value, r#type: 1, version: 1, + size: None, } } } diff --git a/node-data/src/message.rs b/node-data/src/message.rs index 9f70161ff3..1f84d14aa4 100644 --- a/node-data/src/message.rs +++ b/node-data/src/message.rs @@ -493,6 +493,20 @@ pub mod payload { NoQuorum = 3, } + impl Vote { + pub fn size(&self) -> usize { + const ENUM_BYTE: usize = 1; + + let data_size: usize = match &self { + Vote::NoCandidate => 0, + Vote::Valid(_) => 32, + Vote::Invalid(_) => 32, + Vote::NoQuorum => 0, + }; + ENUM_BYTE + data_size + } + } + impl fmt::Debug for Vote { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (desc, hash) = match &self { diff --git a/rusk/benches/block_ingestion.rs b/rusk/benches/block_ingestion.rs index 1bcb328320..d1f92e3a70 100644 --- a/rusk/benches/block_ingestion.rs +++ b/rusk/benches/block_ingestion.rs @@ -42,11 +42,7 @@ fn load_phoenix_txs() -> Vec { let line = line.unwrap(); let tx_bytes = hex::decode(line).unwrap(); let tx = ProtocolTransaction::from_slice(&tx_bytes).unwrap(); - txs.push(Transaction { - version: 1, - r#type: 0, - inner: tx, - }); + txs.push(tx.into()); } preverify(&txs); @@ -65,11 +61,7 @@ fn load_moonlight_txs() -> Vec { let line = line.unwrap(); let tx_bytes = hex::decode(line).unwrap(); let tx = ProtocolTransaction::from_slice(&tx_bytes).unwrap(); - txs.push(Transaction { - version: 1, - r#type: 0, - inner: tx, - }); + txs.push(tx.into()); } preverify(&txs); diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 8757b4b807..f09feaab2e 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -33,6 +33,7 @@ use execution_core::{ BlsScalar, ContractError, Dusk, Event, }; use node_data::ledger::{Slash, SpentTransaction, Transaction}; +use node_data::Serializable as NodeSerializable; use rusk_abi::{CallReceipt, PiecrustError, Session, VM}; use rusk_profile::to_rusk_state_id_path; use tokio::sync::broadcast; @@ -129,6 +130,9 @@ impl Rusk { let mut event_hasher = Sha3_256::new(); + // We always write the faults len in a u32 + let mut size_left = params.max_txs_bytes - 4; + for unspent_tx in txs { if let Some(timeout) = self.generation_timeout { if started.elapsed() > timeout { @@ -150,6 +154,15 @@ impl Rusk { continue; } + let mut buf = vec![]; + unspent_tx.write(&mut buf)?; + let tx_len = buf.len(); + if tx_len > size_left { + info!("Skipping {tx_id_hex} due size greater than bytes left: {size_left}"); + continue; + } + size_left -= tx_len; + match execute( &mut session, &unspent_tx.inner, diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index a4d1df42e5..4352ffad54 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -20,6 +20,7 @@ use node_data::{ bls::PublicKeyBytes, ledger::{ Attestation, Block, Header, IterationsInfo, Slash, SpentTransaction, + MAX_BLOCK_BODY_SIZE, }, message::payload::Vote, }; @@ -127,6 +128,7 @@ pub fn generator_procedure( generator_pubkey, to_slash, voters_pubkey: None, + max_txs_bytes: MAX_BLOCK_BODY_SIZE, }; let (transfer_txs, discarded, execute_output) =