diff --git a/crates/derive/src/batch/mod.rs b/crates/derive/src/batch/mod.rs index 790b7909..38b654bb 100644 --- a/crates/derive/src/batch/mod.rs +++ b/crates/derive/src/batch/mod.rs @@ -19,8 +19,7 @@ pub use span_batch::{ RawSpanBatch, SpanBatch, SpanBatchBits, SpanBatchEip1559TransactionData, SpanBatchEip2930TransactionData, SpanBatchElement, SpanBatchError, SpanBatchLegacyTransactionData, SpanBatchPayload, SpanBatchPrefix, SpanBatchSignature, - SpanBatchTransactionData, SpanBatchTransactions, SpanDecodingError, FJORD_MAX_SPAN_BATCH_BYTES, - MAX_SPAN_BATCH_BYTES, + SpanBatchTransactionData, SpanBatchTransactions, SpanDecodingError, MAX_SPAN_BATCH_ELEMENTS, }; mod single_batch; @@ -102,7 +101,7 @@ impl Batch { Ok(Self::Single(single_batch)) } BatchType::Span => { - let mut raw_span_batch = RawSpanBatch::decode(r, cfg)?; + let mut raw_span_batch = RawSpanBatch::decode(r)?; let span_batch = raw_span_batch .derive(cfg.block_time, cfg.genesis.l2_time, cfg.l2_chain_id) .map_err(PipelineEncodingError::SpanBatchError)?; diff --git a/crates/derive/src/batch/span_batch/bits.rs b/crates/derive/src/batch/span_batch/bits.rs index 94f7859e..7f8fd265 100644 --- a/crates/derive/src/batch/span_batch/bits.rs +++ b/crates/derive/src/batch/span_batch/bits.rs @@ -1,6 +1,6 @@ //! Module for working with span batch bits. -use super::{errors::SpanBatchError, FJORD_MAX_SPAN_BATCH_BYTES, MAX_SPAN_BATCH_BYTES}; +use super::errors::SpanBatchError; use alloc::{vec, vec::Vec}; use alloy_rlp::Buf; use core::cmp::Ordering; @@ -16,29 +16,11 @@ impl AsRef<[u8]> for SpanBatchBits { } impl SpanBatchBits { - /// Returns the max amount of bytes that can be stored in the bitlist. - pub const fn max_bytes(is_fjord_active: bool) -> usize { - if is_fjord_active { - FJORD_MAX_SPAN_BATCH_BYTES as usize - } else { - MAX_SPAN_BATCH_BYTES as usize - } - } - /// Decodes a standard span-batch bitlist from a reader. /// The bitlist is encoded as big-endian integer, left-padded with zeroes to a multiple of 8 - /// bits. The encoded bitlist cannot be longer than [MAX_SPAN_BATCH_BYTES]. - pub fn decode( - b: &mut &[u8], - bit_length: usize, - is_fjord_active: bool, - ) -> Result { + /// bits. The encoded bitlist cannot be longer than `bit_length`. + pub fn decode(b: &mut &[u8], bit_length: usize) -> Result { let buffer_len = bit_length / 8 + if bit_length % 8 != 0 { 1 } else { 0 }; - let max_bytes = Self::max_bytes(is_fjord_active); - if buffer_len > max_bytes { - return Err(SpanBatchError::TooBigSpanBatchSize); - } - let bits = if b.len() < buffer_len { let mut bits = vec![0; buffer_len]; bits[..b.len()].copy_from_slice(b); @@ -60,14 +42,8 @@ impl SpanBatchBits { /// Encodes a standard span-batch bitlist. /// The bitlist is encoded as big-endian integer, left-padded with zeroes to a multiple of 8 - /// bits. The encoded bitlist cannot be longer than [MAX_SPAN_BATCH_BYTES] or - /// [FJORD_MAX_SPAN_BATCH_BYTES] if fjord is active. - pub fn encode( - w: &mut Vec, - bit_length: usize, - bits: &Self, - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { + /// bits. The encoded bitlist cannot be longer than `bit_length` + pub fn encode(w: &mut Vec, bit_length: usize, bits: &Self) -> Result<(), SpanBatchError> { if bits.bit_len() > bit_length { return Err(SpanBatchError::BitfieldTooLong); } @@ -75,10 +51,6 @@ impl SpanBatchBits { // Round up, ensure enough bytes when number of bits is not a multiple of 8. // Alternative of (L+7)/8 is not overflow-safe. let buf_len = bit_length / 8 + if bit_length % 8 != 0 { 1 } else { 0 }; - let max_bytes = Self::max_bytes(is_fjord_active); - if buf_len > max_bytes { - return Err(SpanBatchError::TooBigSpanBatchSize); - } let mut buf = vec![0; buf_len]; buf[buf_len - bits.0.len()..].copy_from_slice(bits.as_ref()); w.extend_from_slice(&buf); @@ -174,19 +146,13 @@ mod test { use super::*; use proptest::{collection::vec, prelude::any, proptest}; - #[test] - fn test_bitlist_max_bytes() { - assert_eq!(SpanBatchBits::max_bytes(false), MAX_SPAN_BATCH_BYTES as usize); - assert_eq!(SpanBatchBits::max_bytes(true), FJORD_MAX_SPAN_BATCH_BYTES as usize); - } - proptest! { #[test] fn test_encode_decode_roundtrip_span_bitlist(vec in vec(any::(), 0..5096)) { let bits = SpanBatchBits(vec); - assert_eq!(SpanBatchBits::decode(&mut bits.as_ref(), bits.0.len() * 8, false).unwrap(), bits); + assert_eq!(SpanBatchBits::decode(&mut bits.as_ref(), bits.0.len() * 8).unwrap(), bits); let mut encoded = Vec::new(); - SpanBatchBits::encode(&mut encoded, bits.0.len() * 8, &bits, false).unwrap(); + SpanBatchBits::encode(&mut encoded, bits.0.len() * 8, &bits).unwrap(); assert_eq!(encoded, bits.0); } diff --git a/crates/derive/src/batch/span_batch/mod.rs b/crates/derive/src/batch/span_batch/mod.rs index b71312be..b32afb19 100644 --- a/crates/derive/src/batch/span_batch/mod.rs +++ b/crates/derive/src/batch/span_batch/mod.rs @@ -10,19 +10,9 @@ //! txs = contract_creation_bits ++ y_parity_bits ++ tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases ++ protected_bits //! ``` -/// [MAX_SPAN_BATCH_BYTES] is the maximum amount of bytes that will be needed -/// to decode every span batch field. -/// -/// This value cannot be larger than MaxRLPBytesPerChannel because single batch cannot be larger -/// than channel size. -pub const MAX_SPAN_BATCH_BYTES: u64 = op_alloy_protocol::MAX_RLP_BYTES_PER_CHANNEL; - -/// [FJORD_MAX_SPAN_BATCH_BYTES] is the maximum amount of bytes that will be needed -/// to decode every span batch field after the Fjord Hardfork. -/// -/// This value cannot be larger than MaxRLPBytesPerChannel because single batch -/// cannot be larger than channel size. -pub const FJORD_MAX_SPAN_BATCH_BYTES: u64 = op_alloy_protocol::FJORD_MAX_RLP_BYTES_PER_CHANNEL; +/// MAX_SPAN_BATCH_ELEMENTS is the maximum number of blocks, transactions in total, +/// or transaction per block allowed in a span batch. +pub const MAX_SPAN_BATCH_ELEMENTS: u64 = 10_000_000; mod batch; pub use batch::SpanBatch; diff --git a/crates/derive/src/batch/span_batch/payload.rs b/crates/derive/src/batch/span_batch/payload.rs index fc7bd3db..b97fa938 100644 --- a/crates/derive/src/batch/span_batch/payload.rs +++ b/crates/derive/src/batch/span_batch/payload.rs @@ -1,6 +1,6 @@ //! Raw Span Batch Payload -use super::{FJORD_MAX_SPAN_BATCH_BYTES, MAX_SPAN_BATCH_BYTES}; +use super::MAX_SPAN_BATCH_ELEMENTS; use crate::batch::{SpanBatchBits, SpanBatchError, SpanBatchTransactions, SpanDecodingError}; use alloc::vec::Vec; @@ -20,58 +20,40 @@ pub struct SpanBatchPayload { impl SpanBatchPayload { /// Decodes a [SpanBatchPayload] from a reader. - pub fn decode_payload(r: &mut &[u8], is_fjord_active: bool) -> Result { + pub fn decode_payload(r: &mut &[u8]) -> Result { let mut payload = Self::default(); - payload.decode_block_count(r, is_fjord_active)?; - payload.decode_origin_bits(r, is_fjord_active)?; - payload.decode_block_tx_counts(r, is_fjord_active)?; - payload.decode_txs(r, is_fjord_active)?; + payload.decode_block_count(r)?; + payload.decode_origin_bits(r)?; + payload.decode_block_tx_counts(r)?; + payload.decode_txs(r)?; Ok(payload) } /// Encodes a [SpanBatchPayload] into a writer. - pub fn encode_payload( - &self, - w: &mut Vec, - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { + pub fn encode_payload(&self, w: &mut Vec) -> Result<(), SpanBatchError> { self.encode_block_count(w); - self.encode_origin_bits(w, is_fjord_active)?; + self.encode_origin_bits(w)?; self.encode_block_tx_counts(w); - self.encode_txs(w, is_fjord_active) + self.encode_txs(w) } /// Decodes the origin bits from a reader. - pub fn decode_origin_bits( - &mut self, - r: &mut &[u8], - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { - self.origin_bits = SpanBatchBits::decode(r, self.block_count as usize, is_fjord_active)?; - Ok(()) - } - - /// Returns the max span batch size based on the Fjord hardfork. - pub const fn max_span_batch_size(&self, is_fjord_active: bool) -> usize { - if is_fjord_active { - FJORD_MAX_SPAN_BATCH_BYTES as usize - } else { - MAX_SPAN_BATCH_BYTES as usize + pub fn decode_origin_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { + if self.block_count > MAX_SPAN_BATCH_ELEMENTS { + return Err(SpanBatchError::TooBigSpanBatchSize); } + + self.origin_bits = SpanBatchBits::decode(r, self.block_count as usize)?; + Ok(()) } /// Decode a block count from a reader. - pub fn decode_block_count( - &mut self, - r: &mut &[u8], - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { + pub fn decode_block_count(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { let (block_count, remaining) = unsigned_varint::decode::u64(r) .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::BlockCount))?; // The number of transactions in a single L2 block cannot be greater than - // [MAX_SPAN_BATCH_BYTES] or [FJORD_MAX_SPAN_BATCH_BYTES] if Fjord is active. - let max_span_batch_size = self.max_span_batch_size(is_fjord_active); - if block_count as usize > max_span_batch_size { + // [MAX_SPAN_BATCH_ELEMENTS]. + if block_count > MAX_SPAN_BATCH_ELEMENTS { return Err(SpanBatchError::TooBigSpanBatchSize); } if block_count == 0 { @@ -83,11 +65,7 @@ impl SpanBatchPayload { } /// Decode block transaction counts from a reader. - pub fn decode_block_tx_counts( - &mut self, - r: &mut &[u8], - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { + pub fn decode_block_tx_counts(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { // Initially allocate the vec with the block count, to reduce re-allocations in the first // few blocks. let mut block_tx_counts = Vec::with_capacity(self.block_count as usize); @@ -97,10 +75,8 @@ impl SpanBatchPayload { .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::BlockTxCounts))?; // The number of transactions in a single L2 block cannot be greater than - // [MAX_SPAN_BATCH_BYTES] or [FJORD_MAX_SPAN_BATCH_BYTES] if Fjord is active. - // Every transaction will take at least a single byte. - let max_span_batch_size = self.max_span_batch_size(is_fjord_active); - if block_tx_count as usize > max_span_batch_size { + // [MAX_SPAN_BATCH_ELEMENTS]. + if block_tx_count > MAX_SPAN_BATCH_ELEMENTS { return Err(SpanBatchError::TooBigSpanBatchSize); } block_tx_counts.push(block_tx_count); @@ -111,11 +87,7 @@ impl SpanBatchPayload { } /// Decode transactions from a reader. - pub fn decode_txs( - &mut self, - r: &mut &[u8], - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { + pub fn decode_txs(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { if self.block_tx_counts.is_empty() { return Err(SpanBatchError::EmptySpanBatch); } @@ -126,23 +98,18 @@ impl SpanBatchPayload { })?; // The total number of transactions in a span batch cannot be greater than - // [MAX_SPAN_BATCH_BYTES] or [FJORD_MAX_SPAN_BATCH_BYTES] if Fjord is active. - let max_span_batch_size = self.max_span_batch_size(is_fjord_active); - if total_block_tx_count as usize > max_span_batch_size { + // [MAX_SPAN_BATCH_ELEMENTS]. + if total_block_tx_count > MAX_SPAN_BATCH_ELEMENTS { return Err(SpanBatchError::TooBigSpanBatchSize); } self.txs.total_block_tx_count = total_block_tx_count; - self.txs.decode(r, is_fjord_active)?; + self.txs.decode(r)?; Ok(()) } /// Encode the origin bits into a writer. - pub fn encode_origin_bits( - &self, - w: &mut Vec, - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { - SpanBatchBits::encode(w, self.block_count as usize, &self.origin_bits, is_fjord_active) + pub fn encode_origin_bits(&self, w: &mut Vec) -> Result<(), SpanBatchError> { + SpanBatchBits::encode(w, self.block_count as usize, &self.origin_bits) } /// Encode the block count into a writer. @@ -161,8 +128,8 @@ impl SpanBatchPayload { } /// Encode the transactions into a writer. - pub fn encode_txs(&self, w: &mut Vec, is_fjord_active: bool) -> Result<(), SpanBatchError> { - self.txs.encode(w, is_fjord_active) + pub fn encode_txs(&self, w: &mut Vec) -> Result<(), SpanBatchError> { + self.txs.encode(w) } } @@ -177,7 +144,7 @@ mod tests { let encoded = vec![2; block_count / 8 + 1]; let mut payload = SpanBatchPayload { block_count: block_count as u64, ..Default::default() }; - payload.decode_origin_bits(&mut encoded.as_slice(), false).unwrap(); + payload.decode_origin_bits(&mut encoded.as_slice()).unwrap(); assert_eq!(payload.origin_bits, SpanBatchBits(vec![2; block_count / 8 + 1])); } @@ -186,47 +153,27 @@ mod tests { let mut u64_varint_buf = [0; 10]; let mut encoded = unsigned_varint::encode::u64(0, &mut u64_varint_buf); let mut payload = SpanBatchPayload::default(); - let err = payload.decode_block_count(&mut encoded, false).unwrap_err(); + let err = payload.decode_block_count(&mut encoded).unwrap_err(); assert_eq!(err, SpanBatchError::EmptySpanBatch); } #[test] - fn test_decode_block_count_pre_fjord() { - let block_count = MAX_SPAN_BATCH_BYTES; + fn test_decode_block_count() { + let block_count = MAX_SPAN_BATCH_ELEMENTS; let mut u64_varint_buf = [0; 10]; let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf); let mut payload = SpanBatchPayload::default(); - payload.decode_block_count(&mut encoded, false).unwrap(); + payload.decode_block_count(&mut encoded).unwrap(); assert_eq!(payload.block_count, block_count); } #[test] - fn test_decode_block_count_pre_fjord_errors() { - let block_count = MAX_SPAN_BATCH_BYTES + 1; + fn test_decode_block_count_errors() { + let block_count = MAX_SPAN_BATCH_ELEMENTS + 1; let mut u64_varint_buf = [0; 10]; let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf); let mut payload = SpanBatchPayload::default(); - let err = payload.decode_block_count(&mut encoded, false).unwrap_err(); - assert_eq!(err, SpanBatchError::TooBigSpanBatchSize); - } - - #[test] - fn test_decode_block_count_post_fjord() { - let block_count = FJORD_MAX_SPAN_BATCH_BYTES; - let mut u64_varint_buf = [0; 10]; - let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf); - let mut payload = SpanBatchPayload::default(); - payload.decode_block_count(&mut encoded, true).unwrap(); - assert_eq!(payload.block_count, block_count); - } - - #[test] - fn test_decode_block_count_post_fjord_errors() { - let block_count = FJORD_MAX_SPAN_BATCH_BYTES + 1; - let mut u64_varint_buf = [0; 10]; - let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf); - let mut payload = SpanBatchPayload::default(); - let err = payload.decode_block_count(&mut encoded, true).unwrap_err(); + let err = payload.decode_block_count(&mut encoded).unwrap_err(); assert_eq!(err, SpanBatchError::TooBigSpanBatchSize); } @@ -236,21 +183,14 @@ mod tests { let mut u64_varint_buf = [0; 10]; let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf); let mut payload = SpanBatchPayload::default(); - payload.decode_block_count(&mut encoded, false).unwrap(); + payload.decode_block_count(&mut encoded).unwrap(); let mut r: Vec = Vec::new(); for _ in 0..2 { let mut buf = [0u8; 10]; let encoded = unsigned_varint::encode::u64(2, &mut buf); r.append(&mut encoded.to_vec()); } - payload.decode_block_tx_counts(&mut r.as_slice(), false).unwrap(); + payload.decode_block_tx_counts(&mut r.as_slice()).unwrap(); assert_eq!(payload.block_tx_counts, vec![2, 2]); } - - #[test] - fn test_max_span_batch_size() { - let payload = SpanBatchPayload::default(); - assert_eq!(payload.max_span_batch_size(false), MAX_SPAN_BATCH_BYTES as usize); - assert_eq!(payload.max_span_batch_size(true), FJORD_MAX_SPAN_BATCH_BYTES as usize); - } } diff --git a/crates/derive/src/batch/span_batch/prefix.rs b/crates/derive/src/batch/span_batch/prefix.rs index f546db41..51376f5c 100644 --- a/crates/derive/src/batch/span_batch/prefix.rs +++ b/crates/derive/src/batch/span_batch/prefix.rs @@ -1,10 +1,8 @@ //! Raw Span Batch Prefix +use crate::batch::{SpanBatchError, SpanDecodingError}; use alloc::vec::Vec; use alloy_primitives::FixedBytes; -use op_alloy_genesis::RollupConfig; - -use crate::batch::{SpanBatchError, SpanDecodingError}; /// Span Batch Prefix #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -30,11 +28,6 @@ impl SpanBatchPrefix { Ok(prefix) } - /// Returns if the Fjord hardfork is active based on the relative timestamp of the span batch. - pub fn is_fjord_active(&self, cfg: &RollupConfig) -> bool { - cfg.is_fjord_active(self.rel_timestamp + cfg.genesis.l2_time) - } - /// Decodes the relative timestamp from a reader. pub fn decode_rel_timestamp(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { let (rel_timestamp, remaining) = unsigned_varint::decode::u64(r) @@ -87,17 +80,6 @@ mod test { use alloc::vec::Vec; use alloy_primitives::address; - #[test] - fn test_is_fjord_active() { - let mut cfg = RollupConfig::default(); - let prefix = SpanBatchPrefix { rel_timestamp: 10, ..Default::default() }; - assert!(!prefix.is_fjord_active(&cfg)); - cfg.fjord_time = Some(11); - assert!(!prefix.is_fjord_active(&cfg)); - cfg.fjord_time = Some(9); - assert!(prefix.is_fjord_active(&cfg)); - } - #[test] fn test_span_batch_prefix_encoding_roundtrip() { let expected = SpanBatchPrefix { diff --git a/crates/derive/src/batch/span_batch/raw.rs b/crates/derive/src/batch/span_batch/raw.rs index 365f36fa..9e5ef614 100644 --- a/crates/derive/src/batch/span_batch/raw.rs +++ b/crates/derive/src/batch/span_batch/raw.rs @@ -1,10 +1,8 @@ //! Raw Span Batch -use alloc::{vec, vec::Vec}; -use op_alloy_genesis::RollupConfig; - use super::{SpanBatch, SpanBatchElement, SpanBatchError, SpanBatchPayload, SpanBatchPrefix}; use crate::batch::{BatchType, SpanDecodingError}; +use alloc::{vec, vec::Vec}; /// Raw Span Batch #[derive(Debug, Clone, PartialEq, Eq)] @@ -50,28 +48,16 @@ impl RawSpanBatch { BatchType::Span } - /// Returns the timestamp for the span batch. - pub const fn timestamp(&self) -> u64 { - self.prefix.rel_timestamp - } - - fn is_fjord_active(prefix: &SpanBatchPrefix, cfg: &RollupConfig) -> bool { - let timestamp = cfg.genesis.l2_time + prefix.rel_timestamp; - cfg.is_fjord_active(timestamp) - } - /// Encodes the [RawSpanBatch] into a writer. - pub fn encode(&self, w: &mut Vec, cfg: &RollupConfig) -> Result<(), SpanBatchError> { + pub fn encode(&self, w: &mut Vec) -> Result<(), SpanBatchError> { self.prefix.encode_prefix(w); - let is_fjord_active = Self::is_fjord_active(&self.prefix, cfg); - self.payload.encode_payload(w, is_fjord_active) + self.payload.encode_payload(w) } /// Decodes the [RawSpanBatch] from a reader.] - pub fn decode(r: &mut &[u8], cfg: &RollupConfig) -> Result { + pub fn decode(r: &mut &[u8]) -> Result { let prefix = SpanBatchPrefix::decode_prefix(r)?; - let is_fjord_active = Self::is_fjord_active(&prefix, cfg); - let payload = SpanBatchPayload::decode_payload(r, is_fjord_active)?; + let payload = SpanBatchPayload::decode_payload(r)?; Ok(Self { prefix, payload }) } @@ -137,7 +123,7 @@ impl RawSpanBatch { #[cfg(test)] mod test { - use super::{RawSpanBatch, RollupConfig, SpanBatch, SpanBatchElement}; + use super::{RawSpanBatch, SpanBatch, SpanBatchElement}; use alloc::{vec, vec::Vec}; use alloy_primitives::FixedBytes; @@ -177,13 +163,11 @@ mod test { fn test_decode_encode_raw_span_batch() { // Load in the raw span batch from the `op-node` derivation pipeline implementation. let raw_span_batch_hex = include_bytes!("../../../testdata/raw_batch.hex"); - let cfg = RollupConfig::default(); - let mut raw_span_batch = - RawSpanBatch::decode(&mut raw_span_batch_hex.as_slice(), &cfg).unwrap(); + let mut raw_span_batch = RawSpanBatch::decode(&mut raw_span_batch_hex.as_slice()).unwrap(); raw_span_batch.payload.txs.recover_v(981).unwrap(); let mut encoding_buf = Vec::new(); - raw_span_batch.encode(&mut encoding_buf, &cfg).unwrap(); + raw_span_batch.encode(&mut encoding_buf).unwrap(); assert_eq!(encoding_buf, raw_span_batch_hex); } } diff --git a/crates/derive/src/batch/span_batch/transactions.rs b/crates/derive/src/batch/span_batch/transactions.rs index c092f52e..90514067 100644 --- a/crates/derive/src/batch/span_batch/transactions.rs +++ b/crates/derive/src/batch/span_batch/transactions.rs @@ -3,7 +3,7 @@ use super::{ convert_v_to_y_parity, read_tx_data, utils::is_protected_v, SpanBatchBits, SpanBatchError, - SpanBatchSignature, SpanBatchTransactionData, SpanDecodingError, + SpanBatchSignature, SpanBatchTransactionData, SpanDecodingError, MAX_SPAN_BATCH_ELEMENTS, }; use alloc::vec::Vec; use alloy_consensus::{Transaction, TxEnvelope, TxType}; @@ -40,73 +40,46 @@ pub struct SpanBatchTransactions { impl SpanBatchTransactions { /// Encodes the [SpanBatchTransactions] into a writer. - pub fn encode(&self, w: &mut Vec, is_fjord_active: bool) -> Result<(), SpanBatchError> { - self.encode_contract_creation_bits(w, is_fjord_active)?; - self.encode_y_parity_bits(w, is_fjord_active)?; + pub fn encode(&self, w: &mut Vec) -> Result<(), SpanBatchError> { + self.encode_contract_creation_bits(w)?; + self.encode_y_parity_bits(w)?; self.encode_tx_sigs_rs(w)?; self.encode_tx_tos(w)?; self.encode_tx_datas(w)?; self.encode_tx_nonces(w)?; self.encode_tx_gases(w)?; - self.encode_protected_bits(w, is_fjord_active)?; + self.encode_protected_bits(w)?; Ok(()) } /// Decodes the [SpanBatchTransactions] from a reader. - pub fn decode(&mut self, r: &mut &[u8], is_fjord_active: bool) -> Result<(), SpanBatchError> { - self.decode_contract_creation_bits(r, is_fjord_active)?; - self.decode_y_parity_bits(r, is_fjord_active)?; + pub fn decode(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { + self.decode_contract_creation_bits(r)?; + self.decode_y_parity_bits(r)?; self.decode_tx_sigs_rs(r)?; self.decode_tx_tos(r)?; self.decode_tx_datas(r)?; self.decode_tx_nonces(r)?; self.decode_tx_gases(r)?; - self.decode_protected_bits(r, is_fjord_active)?; + self.decode_protected_bits(r)?; Ok(()) } /// Encode the contract creation bits into a writer. - pub fn encode_contract_creation_bits( - &self, - w: &mut Vec, - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { - SpanBatchBits::encode( - w, - self.total_block_tx_count as usize, - &self.contract_creation_bits, - is_fjord_active, - )?; + pub fn encode_contract_creation_bits(&self, w: &mut Vec) -> Result<(), SpanBatchError> { + SpanBatchBits::encode(w, self.total_block_tx_count as usize, &self.contract_creation_bits)?; Ok(()) } /// Encode the protected bits into a writer. - pub fn encode_protected_bits( - &self, - w: &mut Vec, - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { - SpanBatchBits::encode( - w, - self.legacy_tx_count as usize, - &self.protected_bits, - is_fjord_active, - )?; + pub fn encode_protected_bits(&self, w: &mut Vec) -> Result<(), SpanBatchError> { + SpanBatchBits::encode(w, self.legacy_tx_count as usize, &self.protected_bits)?; Ok(()) } /// Encode the y parity bits into a writer. - pub fn encode_y_parity_bits( - &self, - w: &mut Vec, - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { - SpanBatchBits::encode( - w, - self.total_block_tx_count as usize, - &self.y_parity_bits, - is_fjord_active, - )?; + pub fn encode_y_parity_bits(&self, w: &mut Vec) -> Result<(), SpanBatchError> { + SpanBatchBits::encode(w, self.total_block_tx_count as usize, &self.y_parity_bits)?; Ok(()) } @@ -156,35 +129,28 @@ impl SpanBatchTransactions { } /// Decode the contract creation bits from a reader. - pub fn decode_contract_creation_bits( - &mut self, - r: &mut &[u8], - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { - self.contract_creation_bits = - SpanBatchBits::decode(r, self.total_block_tx_count as usize, is_fjord_active)?; + pub fn decode_contract_creation_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { + if self.total_block_tx_count > MAX_SPAN_BATCH_ELEMENTS { + return Err(SpanBatchError::TooBigSpanBatchSize); + } + + self.contract_creation_bits = SpanBatchBits::decode(r, self.total_block_tx_count as usize)?; Ok(()) } /// Decode the protected bits from a reader. - pub fn decode_protected_bits( - &mut self, - r: &mut &[u8], - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { - self.protected_bits = - SpanBatchBits::decode(r, self.legacy_tx_count as usize, is_fjord_active)?; + pub fn decode_protected_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { + if self.legacy_tx_count > MAX_SPAN_BATCH_ELEMENTS { + return Err(SpanBatchError::TooBigSpanBatchSize); + } + + self.protected_bits = SpanBatchBits::decode(r, self.legacy_tx_count as usize)?; Ok(()) } /// Decode the y parity bits from a reader. - pub fn decode_y_parity_bits( - &mut self, - r: &mut &[u8], - is_fjord_active: bool, - ) -> Result<(), SpanBatchError> { - self.y_parity_bits = - SpanBatchBits::decode(r, self.total_block_tx_count as usize, is_fjord_active)?; + pub fn decode_y_parity_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { + self.y_parity_bits = SpanBatchBits::decode(r, self.total_block_tx_count as usize)?; Ok(()) } diff --git a/crates/derive/src/errors.rs b/crates/derive/src/errors.rs index 33d1f8e5..654ece49 100644 --- a/crates/derive/src/errors.rs +++ b/crates/derive/src/errors.rs @@ -1,6 +1,6 @@ //! This module contains derivation errors thrown within the pipeline. -use crate::batch::SpanBatchError; +use crate::batch::{SpanBatchError, MAX_SPAN_BATCH_ELEMENTS}; use alloc::string::String; use alloy_eips::BlockNumHash; use alloy_primitives::B256; @@ -162,10 +162,8 @@ pub enum PipelineEncodingError { /// A frame decompression error. #[derive(Error, Debug, PartialEq, Eq)] pub enum BatchDecompressionError { - /// The buffer exceeds the [FJORD_MAX_SPAN_BATCH_BYTES] protocol parameter. - /// - /// [FJORD_MAX_SPAN_BATCH_BYTES]: crate::batch::FJORD_MAX_SPAN_BATCH_BYTES - #[error("The batch exceeds the maximum size of {max_size} bytes", max_size = crate::batch::FJORD_MAX_SPAN_BATCH_BYTES)] + /// The buffer exceeds the [MAX_SPAN_BATCH_ELEMENTS] protocol parameter. + #[error("The batch exceeds the maximum number of elements: {max_size}", max_size = MAX_SPAN_BATCH_ELEMENTS)] BatchTooLarge, } diff --git a/crates/derive/src/stages/batch_queue.rs b/crates/derive/src/stages/batch_queue.rs index 12e7a77f..63b0b0fa 100644 --- a/crates/derive/src/stages/batch_queue.rs +++ b/crates/derive/src/stages/batch_queue.rs @@ -512,7 +512,7 @@ mod tests { use alloy_rlp::{BytesMut, Encodable}; use kona_providers::test_utils::TestL2ChainProvider; use op_alloy_consensus::{OpBlock, OpTxEnvelope, OpTxType, TxDeposit}; - use op_alloy_genesis::ChainGenesis; + use op_alloy_genesis::{ChainGenesis, MAX_RLP_BYTES_PER_CHANNEL_FJORD}; use op_alloy_protocol::{L1BlockInfoBedrock, L1BlockInfoTx}; use tracing::Level; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -523,7 +523,7 @@ mod tests { let file_contents = &(&*file_contents)[..file_contents.len() - 1]; let data = alloy_primitives::hex::decode(file_contents).unwrap(); let bytes: alloy_primitives::Bytes = data.into(); - BatchReader::from(bytes) + BatchReader::new(bytes, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize) } #[test] diff --git a/crates/derive/src/stages/channel_reader.rs b/crates/derive/src/stages/channel_reader.rs index 94db1198..2890d610 100644 --- a/crates/derive/src/stages/channel_reader.rs +++ b/crates/derive/src/stages/channel_reader.rs @@ -12,7 +12,9 @@ use alloy_rlp::Decodable; use async_trait::async_trait; use core::fmt::Debug; use miniz_oxide::inflate::decompress_to_vec_zlib; -use op_alloy_genesis::{RollupConfig, SystemConfig}; +use op_alloy_genesis::{ + RollupConfig, SystemConfig, MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, MAX_RLP_BYTES_PER_CHANNEL_FJORD, +}; use op_alloy_protocol::BlockInfo; use tracing::{debug, error, warn}; @@ -72,7 +74,16 @@ where if self.next_batch.is_none() { let channel = self.prev.next_data().await?.ok_or(PipelineError::ChannelReaderEmpty.temp())?; - self.next_batch = Some(BatchReader::from(&channel[..])); + + let origin = self.prev.origin().ok_or(PipelineError::MissingOrigin.crit())?; + let max_rlp_bytes_per_channel = if self.cfg.is_fjord_active(origin.timestamp) { + MAX_RLP_BYTES_PER_CHANNEL_FJORD + } else { + MAX_RLP_BYTES_PER_CHANNEL_BEDROCK + }; + + self.next_batch = + Some(BatchReader::new(&channel[..], max_rlp_bytes_per_channel as usize)); } Ok(()) } @@ -182,9 +193,24 @@ pub(crate) struct BatchReader { decompressed: Vec, /// The current cursor in the `decompressed` data. cursor: usize, + /// The maximum RLP bytes per channel. + max_rlp_bytes_per_channel: usize, } impl BatchReader { + /// Creates a new [BatchReader] from the given data and max decompressed RLP bytes per channel. + pub(crate) fn new(data: T, max_rlp_bytes_per_channel: usize) -> Self + where + T: Into>, + { + Self { + data: Some(data.into()), + decompressed: Vec::new(), + cursor: 0, + max_rlp_bytes_per_channel, + } + } + /// Pulls out the next batch from the reader. pub(crate) fn next_batch(&mut self, cfg: &RollupConfig) -> Option { // If the data is not already decompressed, decompress it. @@ -210,9 +236,15 @@ impl BatchReader { (compression_type & 0x0F) == ZLIB_RESERVED_COMPRESSION_METHOD { self.decompressed = decompress_to_vec_zlib(&data).ok()?; + + // Check the size of the decompressed channel RLP. + if self.decompressed.len() > self.max_rlp_bytes_per_channel { + return None; + } } else if compression_type == CHANNEL_VERSION_BROTLI { brotli_used = true; - self.decompressed = decompress_brotli(&data[1..]).ok()?; + self.decompressed = + decompress_brotli(&data[1..], self.max_rlp_bytes_per_channel).ok()?; } else { error!(target: "batch-reader", "Unsupported compression type: {:x}, skipping batch", compression_type); crate::inc!(BATCH_READER_ERRORS, &["unsupported_compression_type"]); @@ -239,17 +271,10 @@ impl BatchReader { // Advance the cursor on the reader. self.cursor = self.decompressed.len() - decompressed_reader.len(); - Some(batch) } } -impl>> From for BatchReader { - fn from(data: T) -> Self { - Self { data: Some(data.into()), decompressed: Vec::new(), cursor: 0 } - } -} - #[cfg(test)] mod test { use super::*; @@ -268,7 +293,10 @@ mod test { async fn test_flush_channel_reader() { let mock = TestChannelReaderProvider::new(vec![Ok(Some(new_compressed_batch_data()))]); let mut reader = ChannelReader::new(mock, Arc::new(RollupConfig::default())); - reader.next_batch = Some(BatchReader::from(new_compressed_batch_data())); + reader.next_batch = Some(BatchReader::new( + new_compressed_batch_data(), + MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize, + )); reader.flush_channel().await.unwrap(); assert!(reader.next_batch.is_none()); } @@ -277,7 +305,10 @@ mod test { async fn test_reset_channel_reader() { let mock = TestChannelReaderProvider::new(vec![Ok(None)]); let mut reader = ChannelReader::new(mock, Arc::new(RollupConfig::default())); - reader.next_batch = Some(BatchReader::from(vec![0x00, 0x01, 0x02])); + reader.next_batch = Some(BatchReader::new( + vec![0x00, 0x01, 0x02], + MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize, + )); assert!(!reader.prev.reset); reader.reset(BlockInfo::default(), &SystemConfig::default()).await.unwrap(); assert!(reader.next_batch.is_none()); @@ -327,11 +358,20 @@ mod test { fn test_batch_reader() { let raw = new_compressed_batch_data(); let decompressed_len = decompress_to_vec_zlib(&raw).unwrap().len(); - let mut reader = BatchReader::from(raw); + let mut reader = BatchReader::new(raw, MAX_RLP_BYTES_PER_CHANNEL_BEDROCK as usize); reader.next_batch(&RollupConfig::default()).unwrap(); assert_eq!(reader.cursor, decompressed_len); } + #[test] + fn test_batch_reader_fjord() { + let raw = new_compressed_batch_data(); + let decompressed_len = decompress_to_vec_zlib(&raw).unwrap().len(); + let mut reader = BatchReader::new(raw, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize); + reader.next_batch(&RollupConfig { fjord_time: Some(0), ..Default::default() }).unwrap(); + assert_eq!(reader.cursor, decompressed_len); + } + #[tokio::test] async fn test_flush_post_holocene() { let raw = new_compressed_batch_data(); diff --git a/crates/derive/src/stages/utils.rs b/crates/derive/src/stages/utils.rs index 7d23a271..6e7c297b 100644 --- a/crates/derive/src/stages/utils.rs +++ b/crates/derive/src/stages/utils.rs @@ -1,6 +1,6 @@ //! Stage Utilities -use crate::{batch::FJORD_MAX_SPAN_BATCH_BYTES, ensure, errors::BatchDecompressionError}; +use crate::{ensure, errors::BatchDecompressionError}; use alloc::{vec, vec::Vec}; use alloc_no_stdlib::*; use brotli::*; @@ -8,7 +8,10 @@ use core::ops; /// Decompresses the given bytes data using the Brotli decompressor implemented /// in the [`brotli`](https://crates.io/crates/brotli) crate. -pub fn decompress_brotli(data: &[u8]) -> Result, BatchDecompressionError> { +pub fn decompress_brotli( + data: &[u8], + max_rlp_bytes_per_channel: usize, +) -> Result, BatchDecompressionError> { declare_stack_allocator_struct!(MemPool, 4096, stack); let mut u8_buffer = vec![0; 32 * 1024 * 1024].into_boxed_slice(); @@ -47,7 +50,7 @@ pub fn decompress_brotli(data: &[u8]) -> Result, BatchDecompressionError let new_len = old_len * 2; ensure!( - new_len as u64 <= FJORD_MAX_SPAN_BATCH_BYTES, + new_len <= max_rlp_bytes_per_channel, BatchDecompressionError::BatchTooLarge ); @@ -68,13 +71,15 @@ pub fn decompress_brotli(data: &[u8]) -> Result, BatchDecompressionError mod test { use super::*; use alloy_primitives::hex; + use op_alloy_genesis::MAX_RLP_BYTES_PER_CHANNEL_FJORD; #[test] fn test_decompress_brotli() { let expected = hex!("75ed184249e9bc19675e"); let compressed = hex!("8b048075ed184249e9bc19675e03"); - let decompressed = decompress_brotli(&compressed).unwrap(); + let decompressed = + decompress_brotli(&compressed, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize).unwrap(); assert_eq!(decompressed, expected); } @@ -83,7 +88,8 @@ mod test { let raw_batch_decompressed = hex!("b930d700f930d3a0a8d01076e1235e0c33674a449c13fc37ee57f9ea065bf41af3aa03d5981f1432833bd0b0a0652a19cd927ae4a22e8f8069385002252d78e1c3cc91a59ac188708b7074449184766cbcf3f93085b903ee02f903ea82014d884062b70d4e215ee885019d47a37c8543ae9f382a8310c97b9451294f5cd6e52c003ecfb412ca8b42705c618d29883782dace9d900000b903690d669b0cd98174ac3b57393839029ac04ad36454109851443b4f6580664fe06766a7dea5b1ed31e14e7c11aa738eecb86e979f874873cd3d7ca9481681b4b17d134316e7bbe828ef69339ef85c6f0e9dcdfe1dc85309effb487569383d5464b519bdc1c85fffc72bfe93d4081a3e1b75e5dd39f95a91df0997a22d8fbdeca57a8b35b4f0e277ec8502cc55581a94eec1d1000b2921b4d7c3985ace205713641d03c3975e4049e13b3d2c5926b224684e38beb3b8d2e5d4060b109aafc3f2d144783aadf6086aa1d5a931d21282711484a9c0537bd4981fc222444f2c057211708e70dc4223063cbf39e4af0b795d3ec0dfba32391611d151145c1b6bb33d53ce2bb7983bd7b6c1516f7a1a719fd876f4b20910aba76c16dbfc57199a60e2ab938bc285613c3802c17aa03cb9654f5142d607bac01293c9aaf4e58b422c543f7e5e458af0b7cf57f33109558bef71e8b5506da723d996eb8e2c265b1cae43dba571d07d3ea1bcfdcb73089597e3744344e049bf21b4244d5aff60d559010b69a6335f4bb21178de504f50808204da652c7767dbf11f2a34b4fb710e6df9ad8810aa75dcdb2c99dfe9bf898912817e490b4982d44fe09f8adb43e0da2a0c824a9069ce8cc36b5fb0074c2db895ee92d92fa6b7efdf5c97ae05ae27556bc07ddc9d9d6261a53e3a10c350c3b1da26b27b345768e17da7dabfe6e30e019c88ef4a0e8df840bbd3fbbb639edf775449d8be7510cc811564789b861372fe97f7b5b1389f20c9872517634e9225669ee80cf077f9c8606cdbad53819a875ecd9f7b6d778c1dc302ca19ae67ffb054eb99206fc90eacbac8177712d0b4c72700df3f5e2c88fb4e9c8284cefa66390a78605ad9320aee34f72f3cb263020204393d9359a65f48b0e6e942b016a1f2c5bd6579f0a65997635ab15fa38db76ae8a5d3be516441499819bfaf730ebaec389db082e41443660dcc6280315154888b9e726b971237fae5e06b01958aac081398c814e446a003039dd090c0efa5d39735ed0ab46c7b4e4c960ae414b045fd19117089e65aaf3779cc9045d6e62538b1b75c2689d23ba3c08ceed46d4fdf9b969b34a1903ebd96a3a6b091842480e638b095c1ec11bb5c599668ea1b0a5a714d13462edb39dfd992b569897ac8f45c587182770631c262fc459afa6f23d5670eee2aac2ddaa89314607d30c6bfd408980c082749ad6b48a5310ac75b880cc080a00b5d23a075615f50233ce278d11b7b0ba0ad6a01486dbf31c54aae096f0f066aa02d9feeb4771b5a37d1247a4cc58a64d392f3916b5602d9d41d97b52b391ffd47b9011801f9011482014d88a793ab3f17510b308821f5d9030532aae9831708c1940b6f262f685c8d0ff7dfc9ba9686d8f75b78923c80b89f7644852b70713a788b69f191c54ec8368a7f2675623b2369f9078516605d0d4550ff9f5b92b9da2147fa3a24cc17605f30cccedc5bacafb2bb86e2640db6654a514b8eb13d3c3ab6b5e344498de0c709dd9bef58a8af16d3efcd2c0b2cb69d6089d0af8d42baab434dea885253e42050aeec01f233e64289b2e894c680fbab4f25a653745dbd89edb19d97e35bdd4293794c69503b0e60ed9cffe7e9ab3cbbc080a0dd08ebab0802fc61ccf26c357b638a55cbcd6b366251c17e2fa52d328d9d59e5a027d334772553048d6b76fc39ddee5f85363810c235219356cb4c5c3dbf9661d5b90298f9029588e383f18817bb0d1c882c58aa6b12de88f3830a7831945c1c1314ed944220436fad3742023cba2a71c4a2886124fee993bc0000b90219fb039c014cd76a327bb9b3f59e8176f377249385e67cb1681f8eacff1dee5a5a949511438ce370f8ad6618f3af81cb1f775a0b365546dd7791b0ad71fb1f2f29154265a8175b7e518580732a5a46dae3752e1234ff779d4eb614af2c66beec964181ecd0cfd1640bb2ca2b860649c41930a60de0cc754884a780488f05d1d5833a381670b368c85bf08d6650e26122f6714056382a006fcd5f9c97f55a98d68dd9293bb1be24823eaa8cb007481dc78a7a670123976e7b6e81fc223f42637759a0c933b73ba89a1d902c0874fedeb0a97dfab298972a18378539c2894ca6df9c0a423c2e98df4c133e5e808809849785b069e323640bf93d4b82a0917aaea8fda9a3072ab9a00a4b8b9b7b3a3eb326e54231d0f6a064cdf4a1fc06c961e5087359c029b13e229fb477d6651bad52c75e503ac45002a803a7457488966cc16bbc9be5c1c9a797d0377710c028e4f05a6cb929cc1fd4018912929252e04e107ffbcbd4c81ba01ab4b11faa90be0f9f9a6a22c87257e4a2aa8283e6f71d7b9e03b5308b16525c4d79705bb0906be0e947e8075ac6ce2235356aa0a66bec39e918e47a6220b322e326bf8fd65e47778e14074c47cb62b7ef8ef956c996097d2919df7aac8ea2ed69c1fd9f1d96b6b82b411c524cacec0f4a4269821fd6766d24954b8870fb1d85f5cda0528ae18419915a8b30b25baf6a162978a4bec86009cece83017d50667a202b3fad18f8ed8b5140c97fa74e91be608fdb788202bea05f469660e363ec580825d1e2bf753c01db044279f862720a27831744b91494f5a050fa7445e0e6156dfdb712a647ef73a2dd35b73d5cc988430c831352d4ac7e8bb90458f9045588a106e4c16d06833a881973c4c642fba1bb83068f2294050c84206ba9d32d93d144884644e5bd36fc92d0883782dace9d900000b903d9b303f8efb68766822d7eea21ca4b7c5dd79dce832c4893247f6784fe47cd7a18caea7b5b4d8bdf02da0276aca185add01fa2d16c2f1188ff7cbf6fb8c6308999037b2b92d725094d8faed86f0b1a45b55de4f36dbb71dcbf4be12fe624077213e0c170afbbbb546a343ac3f2a1333a7a7a7db7be46640a73d61b3aabc805b022be416198d809b62f99d26cf4a3bf555d40686f4b8970ec15386462bec5f2b728de0da047d6b3f3ea51f571507f32f047322fa204f0c5697cbb56b4b5c7792acaa40f02926651fa715a40e1f212c78cd4ecca285ada2c8cbb6e5dcfa3823725b44e29aacbeb9b6224f90fbc895a5980d63da46688832e9776b0666e90deacbcf8a4c559b625cf004cd04c686aaf9d7d6e2d394f5d36311f7afdcec5033daccc63c0540935f59514c9aa8ac3c2aeff48f624f2dbd38062fcd046651e92fc7ffce4dd914bb0dae704e5b26a8b73b3baef8ea022881e15666fada8e43fd621793713cb8c867775b9cdcf3b066582fc9baa705a0e1dc61a4b33b1b33ad3ba3bd0cc41b5850cadc04654dec222178709910209c6ac3db9054ef91facae2d729d7ee54898a18411b6d20d599a3de14d5375e5a9c90f3bce78479cb0f20afca895e40b576940e063587f451a8828ec2dd4a8538b4bebc39f72a6c54e379a07b7d5e0c02ccd57dbff13729bbfe5e78498c01cea12e830944fd0a123b7383fdcda97d8d9cc831e542ab6d9b36774d540b180c2bd52d46ca7f0e17d400cf3cd559b1b4e51ba93cd954777ba27a9f0327eb6c68aafe74fabca4610210db7498aecffd3164c5eef8cede655e1b42d5f54f5a52b4f5fe9698a4463f30f20693263d41074d0403a737c4d4986f0ee7fee828fb7072a80603613fb4d6c219dfa47adad433af6b437dd199f3bbc651487718b2e6d42728034c242672a98a9f36fab6d4162f4e8eb7bf2a9868cead8ad657a67f0aa50286113db972936260323d7b11353328151e80691d551bbe1f7f11774e15db4f175aeac5b91668a712c3c2399a977abb9fd9c2b53c5ba68f2c0ea353028416b36a47028f78918e2b205bf9b3bce6f1a08bd4448abc3f12a240482b4be98dcb77c74fff47e92d833735e802465e50b79d51de5a7fe45a95b650b051c61a529d5f51cd0c603a2de67a3123be1c52263e1c9167765b13ad1e01cfb27531c9203f39e8913fe0cab9d8c14b17bad0100b76c41d41d68ae3b7aeef5f6af4f66d113fd29eb9c4bf994f04decad13880d9d1eb3865a30e2540e86923b36369c121ef2a6a43a618aa4b15560fa806601a85be361468bd09c6dca39ad7ec44809adc0907dd0458177343a7c23330605b802f3ffd3ae61b3be952ca2effae8222e9ed0b6ea4240728a7800e4882efa7dd1ef8202bea05db690cab7dc8c52c2c375428c0aa9ead02bf44e2b1f8ee06e1cf7af25eecc13a07d967fb12e1f0073adac46e0676a6006b30d780e6a1387afec76cbd1f07016e3b9012401f9012082014d88df6f092495b7f4148840c5b5541d013c63830408e194aef36f2041e560a641af89e0ba2799ea630a9592881bc16d674ec80000b8a3afb9380f9228224c1aa59eab115ed4172b471aa2ee11b3d4ac93f4b6a33518007a798170801f4f582e188b489005d8f108e2a4acd6f7ac28852580e73b6a1590ea1af1443666f1d14affb0a9d0655a5c57cd4190b2a00c07276054641ee4204ed8a806ded2b3aaa7453c24e442992434d060b51d2255c1cc2a002264b5dadb32057f4a5d52626e0ff453e2f05f1e0d8294614916c00110853462d51d9ab7e03b7019c6c001a06028ddc42f0d3e1cd6cb1ed7377d518480626d56c80e6d15eacd42ecf2f30957a03f6e1098b300b6329997bacc5e667eeed72a38f6c4e1db7199483bc9a18267d8b90222f9021f88c0988653bce0e07388fbc67f04e5c6772e8311bd5c94eeecd6da1ee441093ef70d8c86a26f4dc4da11588853444835ec580000b901a349e745c1cca19957c43f15309935f7bf49547884332dfe6d5b8b9d61542dd88ecc61187fda813a7f700ca96e8847a33bf8552690d91ec8e8fa70c21b380c9c681b54e859add36c3c19e7fda3075ec1a3cf47ed39c89241bb73f206d7497f93c47db9a85be7135948e19809c195ccd4c9a379ed464bf77ec562e360c52b9225f103d323364a72e8a725ad2b34a355928acc6aa563b67d120ddf54cf68f710624499ddeb30b0c94b8722ef2d641ae49f17f4a916d54350ec483ec5bcfd9748e0a228c3e73cee9ea248ad85060ac51b3e6834e1f771f725a466affa28453ad3726d794caab223fa76c8b994ac5d3a1e8ee830e4fadfe0786174364af3109c04d7d607aca17933c4366d44d9c5376ca34febaaa612707eec4e2fc5c6b1668b3450340938d17e5552df96ae84a905d069f9e3455bccab30640a0720f9b4598d8f82ebd19bd32b7e82165303123a0ed80c57375174c08d32ad3ae354251c97316b2977f3a2fdf2dba1c595093c88275badc54e3aad65f77c56f55d04b1e6d668406058ea01da2364fc207659b028d9c55371c776f732e63255dd177b95f857e3cbdb4c66fabd8202bda060830662664d96755362addcc0908287c99c60761cf9c7a613058894eab6e599a059cd2461d4a89458dc68adf287fee71a783dab0aaa05587a21b4aba1ca4f5efeb9017801f9017482014d88d15c09b7ee8f9562880ae58585f383aacc831e72f6808853444835ec580000b9010a2e818d2c4fa7a974f5c3acf3c0f9439f4c83721b2bb9df4fa290c7fa57bc1f9f77e4b80866845a8bbbf8030b707b1f07a54a0ab901188eb2e1262a45618a08517f943cb032eeec926e4343d5d3089c145da1d53128ae901ce91a813c205c615bc1ce9b8658a9da4c2d258fe36f6ffb6289df910566386dd1a9f73b44053bb64523d8faf7b9055c592695fc426c360479c1e2d1f68ca5c7965dd20b6879989606cea7c0db28f27ead4a591ee264f755b7358146586c6a1a8530ec463dd754f100fac603ec3360c0440874c12bb179c43a23e40957bd446f2573af413f3314e9f0668af2491de96156a9bf35bc469d51935305f4df051580b84e98ec8395fbd42fc0c3f3e7410ac4719af4c080a09a774db7e3a26966edb91c1f7956a091425044ead1589f435c8d04aac9533764a04325d5543464929773cc6ac555f5ce1830c997f4d26f2dad5a7e056db6f0a2e6b9032d02f9032982014d88828a67bc288355d78498c2cc318542aa1a60df8305fbb6808853444835ec580000b902bd082cb3f3fa41ebf06fbb17afeed9ccdcf3d2999e2fdd1e1171e0b1549c06de17dffc4ee7785232184a698311c7487fdf090e34b9954a41affc0d0ad44104f70750f6a896b1b2b5ff1024de66ba877c5494e67735cdfd45f9ec0df1c198b357b60e4d840abaa72c5667074c43bfa5e1f07b5970f018820db6fc2bf84341cd024cefe455c92426f876e51aec0fedded8d4aa4003aaf6970c48d898d8d82a8411990e73c8ec792a2cc4a129e526d0fa34a54c37ac13ecf4e3c597304cdbd327704fc97f2ba0b110afee78da5c3f46d3354bd20f56cb91b7ba8d302422428082748faf8b4828ba925ab1a02ba695e686da4d1e759b6456b0388ac8fd769f3b726332be36d3153ebee040b5d822fe62d73b629a6251c8e49a988cdfe599762759df03c9100db5f7a87ce7102ddd21831e0736924f230ffe6aaf6b012423e351627e118f2bc12736a3694b5468858ec6310017b10de24fe75ff0abc060b1e60271dc5274b4bbf0b755a0a617bc23f57ee2286c805086d5824ca4bb6297545c5c1ccaf03be03b7df33c953ddb183730313f09c88392e4bdf688f1d2b730318cc9b148e488c2f1e383505a383672755a221ee7dffec5a4f77e7efe66043d686a126480ea01a8ef0f72f9a5799e03e863a85b7aa56c88b7575d6ebb9df809a240969d3a2b2e086e742130e38cfe7870db79bbd281849912fa611e04b8dd0dea9b7da5d16a66969e54ab9def159b9c1d351d719a93821c40ad6c6014644c5f77374cbd486d6a7cfe75d7d849ce240ac86a1c0843aab27fba4d317c725eb101752803ea67d3e12b784bb424eee6f766e33d6664ca113af63c54ba27b8a8e904c572dc3fd09848cca3499c403a1c601db77a7f36d244024ceacfd9d6ae494b7e7e0f92fa5f83458d5da139eb127709e3dd75c88fd5f75244e15f1bb8cdbd3056bfa56139442c0bacbf3263f29ef34946e928b9a4f1c085e5df3b09f31c6e87397bd939c001a08b9ac3bc299eff8eedc51ed3ff077e49da6fb145a0c495f430964581fd4d230ba05fef2837a800e231a3178226f59a981d2c4bcebc4b4cfba9680371da1e2c1a61b9042bf904288821c649ab1ae8ea668896d6c78054ad7a6583121a8994e3294b628e98892fc56ae3fcbce852265aa657e7884563918244f40000b903ac0177c66fecad5135344e89f45ec7e083130a3e5eab1abb75bab0aa357cf044c0582542047a3f9985d3439a6f850466061142af44a9208656e278b7ad1bd0e03539cc019d6ebf8758bde3e0489ba540c523f178a0b055c1fedc3627fee427467ab67545c154106bb9e0c12a7120c175d66f9e3eb9183ae5c7640d4cb4bd3dc94c7b4e0c9fe70e692c3fd027e0ebb46bb32b73a269037a76731a9f114343ea0584c3f7e9cb4530d086609b59ab6b72e7dc6c2c0c95699091e06a33af5ba200a168ef483fe11056330e84da4f2a59db72d5d697d262b9565fe81a738a48d24a9f1c8c49a671101bb7db5eb64deb454a117eb00f4ccc31bc93c061e975ab6d375967544a2a06ff8b9d59bfe1ecb1dc47d5536c645d764028c5de77f3f34d6c7999785b70b187d9ec4631e83cc69499a4ff8ace98a6f17b77f648ab7a07d5ee0558a8efc19d4601573156a0264d2e6574e867c1eca423eac1fdbfe0967bb8f02524cc2d9933141acf619ffe99483305fbdd6913f1e1feb78a17fc6b81c705c81eb08d5602b097ddec64f6c334509caeed7525e3e34845b21e56e4424aa9609f4df8bb13f31c5448b6bdede84d9a9aeba9fcc38a3c8eb1f3f31b80918e045266c7d69b252c86f8b5711b2cf7136e2c3d86d1301608c7c16655c3ffe6d04014dfd55a9563c2a307525088fd017486ffeaeed45873013a7940a7a91442b975065c765c32546aee9b001ba78d8563e039c8edc24a92f9f457ae28172eb29e16cc588d52c8e75a565aad1a8f9d6d341189a24718c26c19a83c6cfe1bbec2f4b878759a7dbeb4ffc0568b902b1dfb18af00c7014f2822965ddfb56d7aec508822531834ad2c869affba1f95bf3dfdf1d1dd1c2994d904b9c5133900962c8137d7fce9f0b9a7d0474dff9173edbcefb4bf355539dfa791241031e90770c8f09af595eb1aa0d083bac4fb9b929ad7e23c0fc8d3ecc7458a0790929cf7588cc255916a6c16811f09d0c972b294dee6e1f739c5e9d3eab8016b565c8570e41bcddeef2dfbbf95910ae6a46a2834919742ec599b9ed204d1f86ce6baa534039ed308d8be0d289824303deb54af5f9f50d88807134b8f42485cec121432e58b83c8aecb32fc62623b06c39c3f1e0e921b1bb880d2eb017578e5f33a25a335a813f02259e1b12b8a76a90a65d015bb214032a095cd8918b78003d310a06a246ac95c126188911bda8a6623407c0dad308e25a438f78c7409267b729413b7d248a6a88cd64c73118999f00981aa4f6b639e4252d39b1706c686c7763ae9c41aea7b46fdd48bc490502ae876175e5aff8361ccc530ad8202bea0b0209fabc8a5c0e2a5bd08e9a6b532d51670f41513cf007781f27e49b070ccdba0795755f4fe231840196d847d100e7cf1e5650ae172890c469428269cb105c16cb9031ef9031b882565c357c3279f0c88e90114422a470a4682e988808829a2241af62c0000b902b424fb91666edaa16addea67f72c9e0bc7a8053bda59776ede2a0ec3f7c78ffac0eee97ff259f92b21378193aeeadd0253b08897a14f10ab537db63202a4c9f78eb4b399d55c5a256a8414f58f45b109e6228a75ed1eb09627f44b56eb539c334df412b30ee6f4ea39a04aa671aee9e7157b9cb69aad4ab1d9d75c6d90f3488342b29bb59c97ecfd2bec4f991b095038b9e20eeb591b641f64e32e5020130f8a8daf7c51caf93ca460a4e60132835119f99d0484529cf541ab9f922bf15a782521a0f6739c1edb8d4bc26a07e63790087b4c098e4df74534340bf7815039326d1bdcafa53932deeaff03a31e97c6733cc702cdd42be18e4716dd0d014f3e916b0cee3a16bd52cf717f5efb59fb7e41c8e4c0d7eee8ba92ee5b293b25612ee9a3b0043664e918a2aa2b602accd357c8f22f382b16f637b57f2fedb7d8f66172f22e67cc04f230e28ec96b928f449fba63b7862bc3102181d6c7bf063d9376363b8be8200169aa88c46732c5ab1e19dcbd8abeb34f1e1cbc632484d9864e630c4567c0f04a2bf5895d3cafae1b0e70e4c1ea28d4d9578a82611f09ddb22c3c4440e8236be2bf9cecd3fa64b19930af8664d78d6f10aa9c913be537bf2b539e3a9042d5744eb3d1bbc16d98564488a51ba45edb2713b466beac560789c4eda3c0961bab002b95eba9f512108dee2e39a8759c04b18a923f2f2aab2e1ca30ec7361b25ae71923027c950c089469820a4ec3ec60529f1509b92ef04fb7fac70f25d3e5ea5c6a28226fe19317bd4d0f42085884020a2b22dcb0ed8e5600ac969b4f910e54f617597a84b05774776d694ba38ccd3d1055a7245334cddb1ca20d7e001285a57001d03b2fc1ff893ab044612dba9b311247528d7490a9a7f3e7c3ed8531844d3b829de3604e8546ee8d4c3d7a308d32035159aecfa20ae4660e6dc94b6a155aa78150a01fb0e6c48b660a0f051ab59accaf4508202bda080d51bfef036fd4c4ebe7151b2755d6606122e565323878701113b84fc86548fa06fb34b02deb66359ae8095d3c339673ab2a8b138fcf9aed2d4276c8a16435a60b88801f88582014d88bbd39acc70c3229d884ec80fa5565439d283119a84942d89ae04c33fcbd75e3c6c43b826b266625b854f883782dace9d9000008911d1f14d3a721904f1c001a046bf61e70c69943c277ef7d09ce5e779a10e3671cfec81423e0f951254dfaad2a012fa75748afaa79673d94a17d35666009001775a2b868b9b839c77065649bbebb90143f9014088e1cba06e2ce482dc8804b98caf86fcf0898305c61980880de0b6b3a7640000b8d9854e530ac567b7d29eedd91690a0d2397591c6a1b1f5068bc292b740f6aa5d38003a933c0560971d4701b31d537fb7c1ff68c40ef07221089f37671b101309000e0eccbc42284732aa002f2cb3197def9947c2b2fe47d3fea2efc71b1f3cd681082d043dbc1471a56a5d0a5c757b8c115277a2af2e044e56e5e3c2cf8756dbe51a347096a4ead46fe53f4c03fc100fe0009f6b2fd6ade28fc89230602e9221962f4512740857b87f415f134a224c5149e374fe22f3048f0620f1bddbc9acdc268a5de1296d265bac65fc2650b3de55e6bcbc26bc4d01dbf7548202bda03e35d4429ee24e44134f7f51b32fb69691a16c60a0347d9283a8e593d5a095baa01c590af4c1fcd3aca728bb5aaf03f48aca22c756a87607b4153a5ac6be59ebb5b9029002f9028c82014d88aab881c6fe3d0b7484b0da2b368542c231bfe483115994808829a2241af62c0000b90220a8317aae8cca53d039d79f09934b9c5d0b07bf13ceeffacf1011fda22a85505eb7c717168c18d8fb230a7a3f166a4e93326fa82884ad3093b5e07b4edee095d98bb92f357fd4a98201be26960d4253da6fcd09874b364595a47b95d2b50f8cd45921931469a302be9699779775b59f27deea2aaae41a010a47b825a46103b7d355f1c154b3422b4fbe4e62c71c5b6b98b627beb82014ad990bda2b6c06ddd237543b3652c7a029928153a8cec540311406260fd3a55cc5788610321d66c29f168ffe5d93f92378359231ff89492db2bd2e90a4d9c28263d75b77842584d253fd7316e61c27f71771ac7e7a3c8ae6921ff2280c459c36348e0a098fe8da94c1546c15db7968d6b2821b24edced45a7ca8f2bfb2b9bb7a497b950bdaaf771bd777e918887c0d2d6ad3b72c168228f49fae155862e0baef308ace6952606a660beee10da3fd2d29b5ac31f2d55e34da94a4274e1bd679fa42bccc5db074a070b899e28948680d82c7229223d846a1a2c19143dd99c78bc42c33490b85be5067a25f6361d6b803b315519de254191557ec691967ccc3d087b8799dfa5888ad748b7a6e164da0c726bc1f916110b6fe6a013ce0e28b79bee045d250657a70211dc11a5dee69a2c05e9eedde536a9911883e5ef2ee76729ff8fbc3aae0fa13a36daf01199a7ac60b21c7fcac00d7c6a80f5ce10b79f4666d69a1a45b3ec864a57f1f6fd492223c539351326d7a25b18bcfd8697f55e972607b9675b1d40dea3ba4c0b3c080a0e69a3802e5dbe5284f817eaa05c76127a3898633d4524f3da9ba8d7e7b98af23a05a2672729a0136c572a68b494cdd49ce47c2c0e33582b601632b3a1d15f3cc38b9016001f9015c82014d889e607b89f9d2717488ee3a5d83a713a9fa831ab7e68080b8fb754cefe26136c37abae044d7be8e1a3b8aa3ff230de4579b08bf12020e9ea66a2f282ef549cd7f72d056ded10c2fa21fe339fe56715960a4bacb65525bde1671a0a691f44c0ed582e64d3799c4ee453a4fbb700cc130eef66cc66913d919b6a96bd31efc3d77e4accf3a7c695275188ed2e5a76526e4706bea7df44cf6a36fb9e43d0e37cf5d6e3c5b984062e57ceeb1c5e6a9d0c418a5a83b77c4c99e8799fba27bd884e51d5df3db1562fa0b13cb1051ef5d5269b4215078384fa84cbcdd93cd7e67d166ebfb88eadc77cfab6a09fd1ea8f82f530ecf62d60d176d3bdf4f2eebf57b45b532ba6471fb53312e32c3452ac69c7b0ce227a61e69cac080a0434df311dffabb4af9df6fd81f48814ad8f5363567d421c5466423bf3bdacc05a0032341e2314432f05701cb222c2868894039e6e156ee6872ebc8739a4c45a43db9027d01f9027982014d880843386325d71bf988456fca4e1ec42cda830601c994c5e72917d21e4aa0f724ed1cbe014171f1be66ff80b90203e082cfea48d8bbd73dc4f299c37a26fcfe1286a62d17e6bfd13084a47fbccd302a44770baa03092d7aa3bf8f15281bde3418b5a6f610199a7ca97fc11df8058de81fdc05527047d32e0e4527db10cddaa2e1a190d7dde1987c0501a200df8eea07d61ea0028930e7422451b44295ce91f79de155d6169bd64c0cadae791e59b67544023e5fcde77eb509d6418daa17dba99d0f09c23c7df78d609f4af7c1ad95b01c26edae2080556b8e63ac632d78b87eb57ef23791c2336775ccf12f62dba46b65a5b5c7017068194fd2b7bff11923ac2dba3ba0d7e28c1ed2ef1c5d2069e189c09bc51efb571c63f2891acacd6a327dc810180290f9699541f4b65bdd8935e074f80887d3f6f4c3ecd75a54c95476b26b42f02964c16ae02532433d48fb5b5f779562224d1bc099f51d332c67cecb1e619bcda1aee26011a463952719987f705b12fbbbf34e3989d6b5c5182bddc569fb545de391ef10031bf1b0f673f0ea1a9763f652624852bee8f09dd517250da77dd194f8310086ba52032212ed38e014a9bb3f47d8a16cd463a977a443ee02d5548ebb5c518e5a0125c6645f2ad2d52f99aec5c88cf4aba79167cb8f7012386916fe2b863da27d16a7c3c350442ebf9b54a569ccfcfe4f4e64853fd810e6a5b3b3cba9ac8525a260505d12492b99437309f94b91dd68c7658291052e2c4d414f87c1d7b7bde565791fdf99004316f02ef4d7c001a05044b928ccada6036e32565da0b9ac1b51d4a0eb5d702efb781a832c120665aca027befe34f4cf0deb37ef259882c20be1af0efa2ab726e06eb33736ab2f0b34e5b90186f90183881a09a2f1c8cde2c488c2eb098e1a51326d83159c2580884563918244f40000b9011b643c223acabd55c37efc426850758db45eb7a0ccb908d9e2ab6a122d812921618aaf4e30c377ed8c7c5b829846b473702496e87f2fac0a78fe92a7602239414117ba9d42c354b05e5561f234e4fc76ecf8285abc17060e980e1713a3f0ab031a53c6757c972e363485581436b20fcb4aa524281e6765ae59362fe284cb6c9c26e3980cec0a9b2f61d1446e9a1679fd055fca089b838872a26f866cb09ceaa5a57a061440ba3a342807d83a5a83589a7297afba2c456c628954a3daa451cb42207f9de22fd5dad066647b8e8ed43fccd3f335298291601fd8737a2ed69cb89e0573fc8eef594568c236f8f976870f2da93c65f77aeda9ae17d812e16dae936ca069e489d3d820580c636f12164c73795e287db92ddcc73dd6b341408202bda0b8ad8ad3d5218e0e27145286459b952ffce119c42b7b143d3ae68f08991c6198a07bd60b6dd3efcb39d42fbd3b15f2f65f9561ed6106484285f3a9d235d2962c2cb903a9f903a6883c0753f96351f096886eb111ddc0775d1c8308a6ae80881bc16d674ec80000b9033e6cc26ae2edabe8f726535a61e77b09496c76d81407ade4466993d4785c16ae669c39a5f9ee18875389a6004576a39465d66329e18646036b9ff5657ba1ec659bb2acedda2862458a642949d15f2108c9c9a712216e2d9d13077a134a69c64daa48018d835b542cfa7861a12febf7b79023af48f860377d4d8bf99639ba627ae9844ddd982438e2a508b6cb89c87d4b78f31e42f842f62af9cd59a69f4e899720156f7a2adf1d348e9b665481165af600a3f781aceea0589215f06dc022fd28fc6025ff85e3d4b7c25c358f35ed5f5f025eb2b0ec5511634494515a197f3e06f4e8a2fef699f33f58ab71376581b455cbf592e1e657115448db5237d010399045e023d0d69797131720de65ffba81c41037657951db3bd5fcc555b8bf6944a67f1fc0ae9ddecbdbb955743a86d2ca82b6239a47f0d37759cb3bcca9d95d7ad084bd8269d06f6cee9effb2173096ef22875db79714328f2d80beac6cff4b3f8fbde3ea1a1040b6885d86bc92390ed2efa52181d3fcf6b761c0a14b8417ea3878d311d3690f93258e57848e926364fc0a60dcaa161a1cd9ea4fda657c5e868f59bc6d2ded1e264a100ff752fbc32d30728f13d74f60a1931cf1cd302aec02f4ca94541335c0f0717cda44c966db4c2c1e522794e0cc5a9dd84ed6355f979c4931231225096d3f651aa1970fd8a6de80325a6b7b3362b11eeeb3401df138bf8742bb94fca940ed45f8b4937d1645c98adad12836b19e09b59dd1e4cf020a2d4efeae49aff02a0c92537dfbcd4a560e876d0a3da71a38302efd5986e70a0592c02c4a8e5638869db811e47ce514bbe71acb864580d9f3be29e73f8af1584130a448b85c0a4a790d750a3d67a4f1c3e52b0db1c7ec28b891c66570c894b9955f0914981f28efef48616b004ca747fcdb448d0a1b6d7196e2ca002e17cfe65e7bb08027b95bea17ba0dd5b9a479726b5cd32a0fe24052c2afb163e60733e6ab77f8d1d2f606de15a31a2db1c8b7827434b64f794b808287f612854c7df802822340442cb00b8c508eb8d74a6334da415319557d4a8cb58247a7e65c74ef2238843fd02d24d6a859f02c547fab6e35903f69394659a2b1bb02fb89a613733cce7c4af817f6b8cf2ce38f425fa8b59b3fea76273664b8215d0503198393443c926b578202bda0115d2f3409265aaa2d214d11e19f314193884ce34c3274f4258d5f09a97172fca0418e2cf579d94373b0a81e66636160ad2f1de4597445af60d0ec37e9a97770deb882f880880f511ab07ca9dce1889745de5325aa780e8311fec19424eb7935928d6e5fc275944276ee070e90b9619e8853444835ec58000086428a36f8feba8202bda0d3d221e5abc91d1bf4721d9f51100bdb7e25f4e1b2eb363d200aa1b0c09727bba07688424185824dde9b365f31e258987ffcdbf3c850f9992ed80d0e71e54712ffb902d702f902d382014d88e4400f9aa703b1f98501db23a8d88543ec7b3d868309954b94e59842fa49a842609ce51ec1a4e9f75a00da8e1280b9025a30fadb0cd19a05ca7d20dbd28ffd1ec743d59a1169a730091be383f6c571c51a8514f9ddf9961a588f38bd388786c9e7efc5d0e71ca89e7f24a73201839f40e9378e5305f4174752c6eef07273a2c51009f04350abed1b6dbfff400ac6f790013028b56aa08f5090e4483b7bfd1b08042b8651dfb27520b3167e9b912e37bbefe7f13153571ef8ae23f2034df09ae737e672bd09d896bb01cc035322407ab3ca2a026f1d8d5beab70178c580a650874a57787d92b6f31f7f86ee939bf8fac22b23c6b6666b5e0241fb55dd4d397f1c78fe6da9fc3e66c2e34058e223a4567d259e3e1a3560bae9f5e2e3e7df1b7384b6af9a4155f1eeb61a6bf4b5e149db22109c635cbe9a4266ef48c211fe1236becc472cb7869906e27166f3f017ce75d188fa708e037fe1a5729b43892460458478cdaa91af1f9367cd1164204b240212101e631cbd027c814efd1e46368b37041836964dc6a76701c38810f36cc02ae93eddd5ebe83c24527244a55eceec6d47ec8df4b158fd1166a7d0d7bbee043632852ecd8e5aab24d71717a232eae9facb45b534f75103fc57f5cd8f978a362249a16e6b3783443bc5100bd1d8bbbd45144b7c63393f5d8169c4381f645bbbabc899e022d58e7b4293125d6c4d7ef75436b4542618636fb247b48ff823f52f416348fb767f6146c1f443147baeea5c6ca7fdcfe3795e09112224301f87c5667027b74b54dcc0f3c4e149a1e67aa6f8a940e1f2891980a6e565821a1f06d522eee5803650f6c0b8c8f5452804f9c456550cb8f1d4827c7fd1c8fe77b71aca3aef9be16494a4bf7d40b274d28ed9cd92a2169b6de5fdfa3ed1b6ef8318c080a008c406d42212f12e384b8f8bb7bb40d0c4660b67026646436ca589d143edc5a9a055fb6596377274cd6af52d95a127c503c0af5b7df6df59ec493d2bf15cf02bcbb9046102f9045d82014d8822e3c64dba5192b7843cffd35685424e576804831aa2e894b002add3a6fe3cfc260c378a187213b6bac436f3887ce66c50e2840000b903dd35dffee48e5855b9f4e7d47630f215334f242c738b2aaccc6e4a815ad70d29a94bd5fea67cd0cc855835ab9bf81c789806e311f744dfc370960d5246099d70e509571437c3c61e11c2971782d7ebbe3dd231c3025966d5ae37fea256ab601339db76c325884b7939ac8e772ff54c8196d35cb823cd42287ccad89e0f1a8092caae92612bc897cee16c73c18a39a5b1ba5bc5df73beb108cf5c896a420837ff53f6e601052ec017e75d3554c0ada83b7874ded4edab8b1a25e39c56c4666ae2812fe82f65f5f7d423ab3a173261ff29495a5ed0851171d1c261129b2062fffa4fc682cb41394f5ebe335bc2220abe7e950d9afa85f305eac439eec8eba9227352f592804f5b47208c262b220c1eb39d6ef89a92ec3ef051e9cca642658a8d8e55b35e78583d7a6cfc01bc5b9d579a1514c201d34230684e4385a1774f8b5f38b5191682a8b91b536ccd3821ee409028180d0f5eabf6e1e2e3dcbeeae0d92cd83e52ae68842bf781824cb7dc8c1507361d7d03b03bb15f7f7a0a9bf12171e01408f60b35722a5a819d7d9107fcea1b94184160cd9890f1f510207d47752fc27f58729ca8490b81ea720d5fcae71db92a9b140099047f45526d26af5da8bfe3e41beffe14d5d1cbe31bd1e50b9c38b9b393ef4b1b5514050e4a934d9501fc70d9ee3720a22fe18533b420cda21aea8c483e5bd3cb4786d6ce2d0f97d1a653253efd1c0283772e8ae43013dba4990bb6c7d9c7087c0d9b2fd3b79decd9a775989c81b87ccbb1e2d6b3c4df6dbe1b7e3a147dd8ff6998a0dcbe3f517899f2dbbbc788d5004d2de3d23224268406d02fecb0ba553123528c6b41f6f55aeaf8f32aa767a9f3113ca91d92e2dcf656cdef77f966a6b2cba83340658aa5c26aa0cb8ce54ae3a55b1eaafef66763ff4de971cd6a0b65a680169837dac945b0a7f13864795670922c99dfc6b5a5465e5043ad1b3205e4579cfc0e037f0b4e0a8b22b5d6ddba7d24b31388620d4aba83f84c5a1334261955d52294bd8b56d7175afbae015933ab1e0ef91e8161468f8eaa76a6f7a9bb8c8fc1195b9d8ff5dc4a51ff73a74b0640999bebcecb6036ef676c65e9fa5b1be22872082989c55a789fc4c2252452f786a13c4e868b85fbcd09bab689bb66dfae14c2ea7024647ad97728deed03314b007dbe461c1836e97f928308d39e5afc43ee3ae22ff47fff183553f56711880cc5ef72c5d66b4e2c6f651c57311d48fcc0aec762fae6444a5be11793be04c85ba97450673687734e681a1f3c64699686880d32d4cf87202b49ce13fbc8771fcf30d5593b41ffa61462c64061449b2c0a24ad8a03d280500bc86049bd55a27a05d70b12c7fd700454dbf3869b329a1ffa9994ecc2a6ec9572e3adaa0056c080a013fed42f6ecae05ccdb9bd8dc88ed44579b6a8871118710058f72c29f6db3b8ea03d200c0fb3e4416a51538d2ba41be88cfe830fa74c280e8b4b66cc3fad24ec06"); let raw_batch = hex!("1bd930e08e94a89daf73710d130fc039db221fa427e3e9d10b5ff602fca4577fc203ad9313f493c51668a017c2a4ed1260401ae0dd8967eb390d13f2fab12f43bdb0cf432a6630bc76a84c50bedb2a48e562bff35eeabe9cc219de13de55412f6692e1708609ce3440ac1909a693fdf68b581342ecf8d480342c3e3b435349a5d903609718170fa9a4702fc7df772fec119dd097c017e8531040192c66d18eaa4261721c01c8932d0e8890ac2be0630cc398f04f556750355a3a608612f9d782f52746c2c5c83c8e01cc0b5afb9b97080505da0ed526076535d4a34650979f8f1f98ddaf306fa58591a92e25a86a1d62a3ba6d6b53be59da78c1b1a3128059e51e7fdef133a3e0979cfbb47040a51c6e684b6320b624ee51f731fd95ddf7fc672367b4bce94f92714dc4ab37394f3b3e612dab56829e8171d3af31a6cf940504421122cf830dfe1783a42dc48c2296849ef352bf18ee96eb5deff308e094b61e61eae5c02c14320345cbf250a6c15f725d6c2b12e8a10c1331f91d4161667dda26ea1f2a7cbdcd1d73070b70c818d9f543b7b3523e02b58f08f6858b951c735820579cf0ca7e4dff854cb2414a29556658374c977897ff125470427dfcfbf5c8bec622fd5b5d9cfcb898b3ea3846440ecdc29a7f99da330597db06d49dfd085d0b56bcee9b1031aacb1d71d7df7509b2cd76ab53620623cc85f880037e10a14e6b55758925f8ae7eac9489aafb831809662dd12013e9e8ebf67fba771c88da3157aec7ad6a4ee554abe967f1ccb486c47592eba5ae33812285bf3f26dd11d232f63c24a5b6e5fb285aa8950dbecc16f501c87665df4d159b307d36d554d54240306bd6ccdeb6eb37648c5c2d6fae684e2fb5608c2acfffebcc595b277d515158a141f2c8f2a005d5ed82e875c9ed3546149042a2dddfd82107d3067825968eb4cbe455b6b2f6ab2da38c3ad83a3a6d87fec0ff797916e6a5220218436a438d6bb44dfe5cba3f7602cbd7fa0ef7d000b9e02b05b4b867b1eef9b76ecbfc2d6f2df9955e4f8ca9d06f563e3991d86e9f194fad8d7c05e413bf68f02c5592696cf28f51aebd5fc6cd1cd76b3543b37f994c17f83b79c7920c01ff10d4d97e35689d65913b4fa0d5748de37963cdb48cd1416d899a3083df547241e17f5f6df8917ccc0c5639912eb99ed8849a2c8140187ee114fd3253b986c3138906dcc2db911e6bdfeb32fd0c4b8346d3e2b876fbe3d2f95e752b71f94c82be7a77b4ae73bebc06d03e8ea40dea94450887ba163826dfcd21038bf7f560db0190165d83809d398eb32f038186ce9b49ecbf2a9dcfe0be406a71f457514a47dac76990fe20c074893a34a8e7f59d4a945e3aa4e16b6c37a28d9a132cee8fbd5c7052ddca49cfe12a4c14e9492f2e6b480aa70e39e46b481b38c7ec36d24fff714a8464e0aa8c2dc3bacebfb59adc6a17e5377e6fa4e70af286e318b47897ce7e75a65ab445bb64ac6159ab48c1310b641fed5b40c84441a093af75902be5401a3304a3f48740908da9209ee6a66a5442bb3eb344fec8905a7b809c531fc788421da2333a9c3d84a5e0b2c59bc8807796da4f6924da6a3ef92ec94107b8ba4092d1cac44ff621db09c007bc007040006570794ab5289e3a323b98e261151a96b3ea240c0f612015d99996ed87511cfad3d644577ae4ca93a14fb250484781975404938bab804f8cdd4dd288ca384f7430ada7852095dd0b7c04ae9931aab4da57816172e71a85ecab00f5149e9929fbd4dfff8635f54ddd91bb56a86dd60aea8af18dc242026dad7b52f271db63881b39577a15f5b8f357d3ccc8cc6d79665133f571125dd592caa7600dcd7d72b5ba73c0edf74389a8a6e3d4d190b76a559a324d0fe39ea88bc6bc8c3dc30d89145f253b354134b38bdcafa3936aa1eefe10c806c2593502f0dd7cead691dbdf325a7b72da81c7427d2088ad9485332e4fff004237cfe54da30913e7e0f5cebf71691ac1c38731c84d91a233a96424dc976ebed809cc7c01a681f7c26ec078dda8c46066bd2a07ac4df05d18920f47aa113136ce45aa04b9a4732daf0450a88bd175b8086c4efd7992f21b0a0a90e00d3a17a0b46ccfe9dfd9fc901fea75e74d9d127118d0f8832cbee68be4d2c020350d533276cfe5b9d606ffae3e7492ccdb0099475b66c33ba9a1d6f58d8c8de19b8475059e61907a44883ba381ccda9e272b16d797779e4a1b4e3db34def79ba78e8f9ccbf592be4a63f4c9170f2c304ec65a8db539e72e1e5217209b0b38b61027cb82ecd3fc60dafe36cd476cd291f5dc574f818a19ca74d73331e0c3297e25619041b7ba9412255b10df0722463d17eb600aa8c9ffe3f43df2945252cbdf52113dfdb052bb2491299113c3e371b2a035f9b323318f17923f807a394cab6729124845833b794b0454c42c088e119110d767b5456c82fc28a2048925f5dc54765313c632704493126c75f40a499f6408263e61162357d5ff80e37617e80e0aedfcfd0284259d0e2bd644d54ab3166a22630ac06ac802e97f600a73b0e38fcce39189828cf98e1f5c6e8a7dfbf3670ec6498225b00446125276b6cab6004bf4d2e8c1341085b1ac9aa127bd10bb2ed29c7dd74f78baa4061874f24fef9d0adec31b81a46cabe2e860d890edb27b2c7f006a37f29b9b9ed21650ee7fc27f8fb7e16e4cd947bb47d094b26b2def138f04ab29316ed57f12f3a13e988810c045b7e35f1451776031f0524e96d1d4ce2c41a4a35e7e80a127620b2252f27ea3445b0cb1b49c4c33444237a279c20c92086bdc9b0de1e97c1a7a477dc0cf1efdf3040a09a8d1f3993682dfef3458cbad84470b94a52af59c2ba0f08d80b31954937dbb33cd743a099ddedf31402acc348f83e5bb821d185e14975e2a43e40d45e3da4b70fbf397db46395c95eb9176d70b70b1b4d802551c2b035166a82623a61f45e60b4c18570fb034e7061026002f7e15189b7c2ee30b804ca545894707287ca7996945929b08cd4410fcf7bf28c385be9abcdd0cf576dbf6c402c41a7147f14038c97f3fe8631cba55007db867fca4efbe1ff39f537548ed902ae01bd6a0a236a67c88a661dd930c15f017dce1da3ec5159d0fe4cc9cb3488ca09752bcec884d2adc6fb774eddaefffb1477d80ea9e1ddb0b7075ceabbbbb5ecb904866e0bbf0bf8f905b6f7ca5821b92f1109548fc33650f68a9b67ae20b6b165cd39de17f7691b8bfd70568c7239ffc66765d13b72db4ebf890a915d6abe3b557f70550be6bc96e5642b82b91eb10be8d669691df365fc53820e4cb6517f753510dbb9c51a8b5d38ff436fb0c61cdbfdd3f85f318897a64585a16af22cc782fa05fd7794817ec89270890d388c35c3abc1e667e266cdefe79211fd369a7f504a334a3fecebf3027fb2f0ab1af37090f97dfc1d8116ae99b2ecd742e47e48c399a88a1e1aacfb927ba4be5d9f0fb1789f91b1264d7e0f7edfdf48526c583b823968b28f716feeba8a87508249bfd938d756ec8b2e51f8f2624fc6467a7b764eff1384b306bde754b918a0918c122a7e6f6c1698ef129c99126f8d40a9ed97d1da1ca4c4fb859804441cad11ee84557921aba96371cb0b3a90cb2c0cc76c9b43d5cf16de51d6f43ca89c4017fceb239bdb708bf45e91b68fac6b27b66da9172c4d08a63f6759a8d08c513c1b2a702b1b51e1cd866f5fdcee679ed65dffc276cbe93b380acfec273ec53a664f559d29a46ae713fdbf96b1b23a1546aac5d8b6da6cebb128d61832d8a3b1e0587ebd1328867237ad9d43a4a2de95329d26ebdd455779cd19d4361a5d7fa45afd47068302b55d3efafc6b1e57c9e42af6e2507ba785c554eba19449d5f4c42e5acaf20e9ddc8ed37201c363464cc03d40593ef2fa32f81294d00ecf1862c683fda6ec4891f72a5b5b2b29f0d8c2bb415020f8db1ae7976b0cab93845b08d7a0842d6366e59d73b593b8c5fdf199ff6d6564ece94aadb59fed75951abd39f67a06030f2d34d57223b62667a8fa315cd2a27af7ced30d9ec78e71cb8d675d8d61924db42bb3105556a57775e7472e93e648d78fdbfe536e767a71079e1217faa728fcdd26d8be1cc1bfce84083d5272d543378cd430a096deccffed011e5ff741c92bdfdd4d42a8ad0f907d17490eca3fa52b0dad916189cd4b19161f886746a18b366d8bb1047746282d772670bdad1b0566b789dfe8348993a1eff2a3b03f51aaf362711afd6b0150ed8ee20b243fea04fd2e1f1eeb556d66b13f18ce72155f52af95cf6bb1c1a879a4cd9106ecbb5a6891c9823c3cb958a4b7652502e6d1258dda66af2136800ac33d739998995ca73ffcb541c37288b5fd898133d2a1de5c020154dfe1603b80775ff375e6cdbd69cc4557afc794acf9336da712626ed13e50fb60d6d7c0d92b10b01762dc96f8a7fd7facc6e090a7442c52e5e90cd3bd0a1359fcf64fe2a77a9acb296c48607a70232b19947b6d8dccb6adbd195c33aa0f9a3df6affa73afc9d96b17dcbd4e0035e005400e022883b79c11a9d3daef71c06223ad5a240021cb3018849dd4ba3b6772f103b332f1faa8ed2ebaac534ba4b46430d18093adca381454c5f59d7ce8c9f4944a84a5f9d598260b784cb284459798cd0b3529f76dc5dcf8507ebea12e2164aa7aacf8317289b02b3708bb25354b4f35f41134214782f6df124f096fa4786c6e6615be1a2a67ac0d8c74a7c5139b2028f074665a56a4fbe42a2b15709b73cd55e5d242d4fb1259d45c3366ad2494da03538c509456ad6beb9cb0c10ac61a163fd1ef3577af4d495141a9e6f2b8fd008c082e8b4592ecf66d411782d17e00c48c7e63980d5584786992749937503d3cc4c249671ccde9dbe9b4c4f9ed1da22e44f427466633541b675646d794894dd0e53223dfe3f0ceba6b969ce04421c876a51348f9022403f767466afedede7607bf8d06c31c8c7ab38661f618a55e9e2fad91ee8b238a3ca1c64616392b0faf61ea8135a5e4b8cff5a0a0008ae58fa407a60ab3748745bfb167713ff5c96bf9847f67f974328cc933d76259899f32c70f5e0b15087641a9fc09962d167cd6a64d5c251d3f7e751924e243c9fd41a475ac5f3bef284470f4510c6f3250fc4ff6827f3c59bcdbfd166e593e386538b0b3c2f0085b5f6e271371206d6a61a2d8f74246f12968c462cf6c842999e6067a9e8a47c1edb89ca69689ab583b397acabed4b22d100b754bebdf8f270c0ba9ac8d33f68609c55f94572c5684fb0578f795b88b926ae7722223bf3f32e4b68be8878e842ef38be46a23e0904688447e70ed3cb93ed194d8d4bfd24b0bccbb39f92a553551bd7a8d77a6d6180b90c61fb3efbf6e6dfb987bf028dc61e4c22c2fc1d714fa7e1fe671925a1de1752c563dab2ac372093a57611b196db489e152e342e49b0dd2d6d84aaf0baf849db17bd993369caa66b74282277f69d18f4b009dcde6cc3305817035a1b104d056507479d53dfae3386b05f6b4688833381c18bcef8a3e6ed70b47d21085c07486b5232a02b5d64f013a0fc6308d874b3fc4ccf44e016b5456efe45efa0df4ab239aae635e4f9c879cda1b78fe69cfba7b93eb4a36af3d20600fc42c0ccec24639dd53d3a2f67f7f22e8d744ae9917f1cb5819362c38f5b4ed200ba23f4d6dbe5091aaf7ff47ededafcf23421fe16aa42a583d3f8a96eac23faa269f9d001fc00bc003045006cf1a21b65f26a45980910e2222eec2aaa6c248dd1e433ae25f22b186c631ab96577a3c0cd5dcf5bf48162885b91131756ea916258ebdeafe262bf0deef40b0093788e97e864676f127832f5540ea04e0c737edd0324a9b4723a807a70a35705e9e27ff94945c9c47c8c5312e5ce4a0af4b243e210c15223732371cf89b13a957b9a6c44293b0e7ecfc6611b595046bc3e7345bf92428052bd8264db5f2fad4096ba44f9bf62ee1c803e33bb03bfb185b3a966e3c87fcc337331dee6f79ff3afd6d50ad823ee9aed593763b77a88c9ea33d6104fbb98cf0b2d60dd4eb28f4f977b37e29048f01a646df6101aa7d44dd1e29671af77a71d1ef3827d736d1b7f22427e63a957ddcbf65f2d4533461efb760bf8574a8649e87a5bd2db0f50fdd1d89230dbb66dff78740b2bd95dbf78aec6c2e3a89c97c752049126a52a7b37a059246713055139abc5610499a452d2eabe40cf729fb11ed87bff8ec1319f773ce2cb50641b04e6dd745879dd02cc01768061040190c8ab6fd4d1fa6bd1c9e3938c51121514568b61506fbd696f91b12600f0273f3ddabf8d9b573375efde5ead4ffbbb9ac7cb60d524cd7ed46ad5cb84dbcad7795231f0d4e7c05bb30cc31b9e02d4434aece3405f1fa7754a40571982778b5c78af4c6a6d62f0cea4d9bae5f015aa987dedcd31fd22fe7a8370399cdba6d68cae1485de5cc3ab6f04a927da53bd7fefa2ed7f820d4b677a66749f169a0d2d5bef60435edb3d701e139fac5e6ca42951874d563068adc4ae6ca0a633866169afbf8b92f23f37021c301edcc2b57a9126f0df6f9fdde4806bbd2fa3c9d8bea443013a411a3fed267cd4854669e5b710e5d6732a9bd2b8e9d9a522204e491501f2347df956cd008612a4b3b8c5c5326f5ccb1d269e08b1efff02a1074b3e4ece599ff26d2bb2dd6ba42f969b12c68916da13ebe9f9d19bb7590e545a7bf053d8181dafa54117084c1b24111460acf93ac4a85fe695fef00a0a6da53b708c24c601aa0e329b653d4fa11113fca0185d788baab7a647a5ddd6fd6780874fdafe1d1d27dddae0d29c3fb4df510b44bef18a216b908522ae9b6c8d0323222fe732db82d1878279426bc8ecfcbce218a381e96bcdff308be996b67e7889d6894db070fdeec85a919f0f1b8791a50921e6d7d8e943c05057ddac008ffb0c7b20a3905545ca1bbbd94fae6431f5b5618fa953a82db758d7f76e73d231689a5e70930b122fcf4a060df8bfdf47159f7ed9e0b0dcfc27a352785e9d8403dcd092c9db5b749cfd7aacebbfa96934bc24de29a9d022216ab7534c3b15232f5e655ea9173b20ff8f45c5e91ff4b8d346e4f8c2059d514dca5cc11e066d208f0a4873eb59ddf61f2516ca1be3c7cb2d913b6b1fa8329f028a4d545d751710233e2f65f7426536eaa583e574c80d88ca4dd2f98674e0aa874fa6f75a94e5e3128083df9d5344c3aceb890ff0ccb1b716fc3733c61f149436ac794a863ba875da7afd49c5f8a19b9a68fd3f236ff4e5ee684beb3e4a63fe2604b10f18ef8e72f7eff55fe7e0024267be83743fe57fcc508e9fc177c90fc9a73a3346438ed9e3d5d3af443990a19627a45cf5b01b5cb518c07a27dc8ce246156fcfb5b51e9adf207b4eb1a2933a179270cd30b0c3d986254be9af0f8d4069cbe3416a255eb671d86451895bac7a068119f19c53662bff7fefb5883d6a04cf7082c6d990492ba8782025d03f01e753eaf55e7e65289ba3719db0ec3461231a926ecf6ec6aa8e20eb896ead7a39180f113cd8a9897cc768e80b181c394a897aa248fd4d9f569af259ad9e6e69f02e4fdecfba5d7b3b72d97532a364275e30369d01ef8fccf43f7b94f27e3d7e6293da085e1d0b93dc0e84a3ee0b9e49c2fd2892f70306685aad4d2233ca1e4af8252708466c72c3a43b77dd6e2d0cce45e6407ede7e54e58802929790a1b3ef4743229cd3e136996a35fede076f4df911925cd2e3169dbfe7bbd611154e18f2b39d11d0c9def68e16baa8cfaeb6e8b4b1973169d3aa6c784eed172730a05c4b1f265ae1844edeb266dca67d20a98410de84a531cbf53facd4f3cab9d78f56db51418e1be62f2f4fb76ce1bffcb2e6a3a5a197b89d18f6c7adfdd293bfa66f918ba34fe5a3d97e138161a4dcd2af98afe9b5976e3effd2857ed07bf7809ab577135902703d0e5d081d02ab35a7b1cdb0e9c97509d0e7cf46da7fb775cd3504fb1647dc721fc675ef09925f71df66dc30efb66e7b33d1aefdd21740c769cb4214e07d890b1716ef538c4a5965b77e149b3b72727dd44aab32fa1506956a0fcdc8d7d47ac25d7d67371ac9c9d7d56f93e142d14df7877471492140fa36133b69443c31cf9dcea4ac4fc84fd93593872961d17616cc0467be8eb70460c676bd120cd72b0185e430dfc01f088fc3abd5cd0730708f88a9557e248747ac2197919716ad95fe6401195c745586ef38f5f0c2a24bfdcebd6d1e3b136e5e34ee9c5698c1f19e818d41226e43971614615c9e20f3a125408397e12f50ede77f8786607f6b67cf5ebc4243291bce1d7438d0154e929d38db75a9dfcced5c0949af85cb5cc91d95f5d64697dc21f37b31bc40ca9ab309d23d8fc50e9cca1bccbef27d79de533b2ca5f49ee17bfaf5afab8b5f9b7ca93a831384ee05dc6afb31fd2ce082133615dc36f39c9d9cbbb42e8e3f3e763d2d1f089c9b94f7ab183da49f68eb8a1648833136e4da99b873b4ffc2327f3a71d00071da308977da2e9cd2b96b7beb424a4c3127b7aaea40c8973fd9cfc3998d967c7c3ae522ee8fc7984955e54fa4c6a76e133ad7ad302b515303cb66282849cd139160ee7414cd878dd24e7bb858520dc50ae28295a32115147c8dc19c0d3e7e04e80a698bb02fb9a527fa79129daab12c97ae65b37851827246d3a0abf3d047a1e03624f6d3f6184650e4e225a8bb6a1120b40ad658fa729e17b8af540a4f5774bc56e9f932bab885d5272c78ccaba460cad5275b0cc97d098cfc1831b8d1cf3123819263cf597f95888194e54633cf6c23331f80a339f1a61af05017b210de405d5e3a5fdbba53d082765ad9c8bb82ef7dfb0ff417987de06c937b84cad437c75b5ef3fa9f0c5089cc20331d0026e0eac9176dca2506452e969731b61071c3ba1495fa089c034d643ba43740528e013008e04a32c920ce8041c026628a2267c648682026ca17e4bb2f9b95668bf716afbc49c8f3c56012bb8a6effd7393116de8692ce1b5fff224f856ff8589823734a5ee7403a8d900ce2854c5a8d60c6ce304964c3cc5b734672d1a19d0d887e33c244837221e52467b5e9036a4d3dd2bea9c69e67e57bec76a463bbc3fe5872894b9d69d1c7df3cf6dcbae55685c5d36724abc930b9368ca69cdcd38ff603a57cd224254e3ebdc453bd327b222b3da635523c7468f8eab0f50fff3225462567208e00c532778c98309d7c87d10af2e4866ba31f0a1a1803cbae792aec7290edac31ff22622f82b21c62b3f497371213f85aaf1733a11fdaf2fe5e7dc3cfd822e26cd1875171a034e2f30edc4cbe26ea0025445921c502e05707b34feb9069bbce9bf05898feff72f5f1e77255f1a3208d298b39e1437c0f0589de017553199314ecdeb8edfb2f131e13ef2b606b35db4af3c9abbddb2da4ec1dcb2efc64f38242748157459b647320d6150842e8df5c109a778f108c61c9303ecaac0c3b69c23d5a404ff7ba27b9b5549897f5b5287af46aa58a248dbf65f2b303d44190bd5d711a1b9ec0f9cd22facea4683ccc910379a9885a4c48ea91c76fb72cfe75fad1ab3d8eb23ad96e39c31aa7293040f78fb9834f3051225163d549a67bb079c3275a4c0a5442526eb74d82d36bd353af051c317c5fde944d5504c6967950f58187197acfc159eb9308dd1c9a26c8cd5acc4c568633c443475aec9ec74136afd513299e425e722a3b00c376a39c957306fc1352eb7c62226a5a34520da4f020eff85997bf208b018795113cb24daca8119d2845dcb0bc681aab967468522acb7acd7526a17dfd4fc2cac819bf477a58dc63fe4cbdb007a035d2812e8a677b2e7946a1819acf5ca664c6a4ffa6579a4ec60910091154d7ca9f90e864d1e9863ad9fc70b43cbc508f3e4dcdfb2cf5fc9eb64cc0effa7b6156a57f97c4302bca139cab59941aed5abf56bfedcab81803d909045a2cf6b9e0f25955e57f5264f631b382c561d4daa5fbf009882e1ef915a0910e76645e0669ba57e5d48dafc10bfad40534523dffb4bdddc029d6334aea481590718f01c01022883bbe7b3a75c8628f3c02ae3e8a53a5afc736198d9e1a92c51753043a293cb26428e921db44d36168611aaffb96e38e6ec8db2801b01cc4d3f0022d3677e8462972a4417f434937b70e45b88c6e3faef3c5442043d0d4b6bab6a0e82f5eae911fd5a9eeebeaa8037af63039508f036608a8cc909cbf586d391ef3eeb0448be00c4c03b93909fccfba0ff6098ced8fac8f7eba830d851821030ea765b73b9151454ab112a9a4823b6ed73f917abb88990397ecfa4d1c2c607b898c1e476b1c72a633e2881142158b30c12594033896670fbc0d78f61b46b370a84025e5b220c6c442834b4a9df12f4b29c55506ccccd04815759b2834d9fb2f39f4557634464424ba1082c30c2bf715c4bed8d918c3cfd633135bc8bea596154740ef606fffdd2593f20e472492f395d703e1055827ec740df862a70605baadd4d184f6637634da6486793c6f240d0ed081637c556a0545297dff3f8a4bc83498023bfe9599fa8f94f1b6dbcc3e0446b5863fd4eabd6bca97df8fb37ed6f65c0fa9356316944b81724f27755a4b05583d59bd9dad2930a1dcd205c81c9611507298b90b42e08b13ed2fdc0fb7c4d397db7413df47df41fca319d0a2ff8966f0206a3bdfa67ad9dc044e00b301699aa8ed0d14f61648ff08635269e0889418ebe7d04fdd4a1e711915770f8d5c5fed19ce15f2e404c51cc354686efc3fe7bf5fa0f03f3a3883142cda47d0c0f37167fe58d0ac94f14d75e2585d3b9823ebc963da575db5f65733b6d35a6938d3b78a11204e8a54d4795d05c739e46fca5c8239d56e29f36d78a1ebba04f918263570cec4dcff2cef06c2da0db3c65acda270420c976d0949843b7cf6bdf0c68354b30a9f6aa588c111b3a64b6d1f57690e3d46621af3139c26dccf16a09f2688c41189243352bfe8e8871e0c0d1a2cf971a8df844627092d80c16e0267c1aa7bc50f97027737c45c9b334f5b02696ac0e822c970dbdc369c2e7343fdc710f89e99ef05c6b82fc84a20f93c96ee951a47379b8e29110138ed75207b41cbfadfbfe586a0211515ffc5d3008a8b8ed6beaecf693f74f435eabf7265af63ec10707a0b2d8cfb733e382e8ef0beabee9596c775db147ffb5d330b3b741bedc412dfe606168ade1b85d34e15a4b2da153215af27d95f83d65dce00171e9c8da8d92fb810a1aa34ca65a91292c1a4892dcc81a8b1966fe2e8f1cd1ab665b646a69bed401ddfc4d3d6f578be09beeb91d81edd4d0ccaf0edfbc573d70ad478cdf4f5c65c818ed6fb224738cb64f7b80d0f66e8c6fefb6f49c9ab59f0b05c900a1f1a55d51bf49fec5a6a67d162658c4e4f6d2cbade0f96da86afb15bbd8a91e4090ed378a4c31f65e03b53c5a816eb483ec7b6fe36457586228326e551a4ce6bc904c29a499a2cee9e447d318f36fc52e58fbc4cdcc3fddb37101f554b0a4bfb93f047298cd073c583fa0570d0daa821d33a72b8e8afcbea0a12a5cd91517e49f594f0531a07573cb06f08cb895c5c82b6dc8ff951decdfe306b5012c990448bedd4df17502cc002f00231040199c6fe61ff532e7ea01be14300e2bc7ae7c0d236c3f0c09e978f354bada35e719f2ebda965aee8d27148c2efea242ddd7cd41e8c302a2e597d9b3d1b33ec84f07bbcde86ccea2b01591edf17feab8ea9b95744d5a6b186ee2ba42ca92ee95d0164187cabc59c397d202aadd2e2803ec978f8ea376d8ab046d950ef3a4efc2defb35ff0402ec343cf1e3e70ecaad69f75d1c4e03ef951e8b9d3bf785d178ff19ff1432cc14b33808b86c1c39ac9c19c62fad10f41e9ec8ff95f556e4bf127e40627cb7fecde215197b1243fdca58c3ae8542cd874fb542e9f746ca7490edaccdd91bf8f4bff7bf6a7bc40fd28a67364db47f164ba8784e825baaac670ffed2ad9c5d56ae6f9a1cac9c43d28ca3b9fe28bb7465a4767ffa432092ca77985bafdd0a2f5bce2b6472a10a2b0f3cdcb60b14233256547d826b53b010682af15d0e29ce6b5dc0242533fd8f2831fec31d9dcb1f6e67e3eed94ad225c29dc040c00bd0170450062348f259d22c13bd80a59dffa8900e33af85c1012652478e18f0e64815204fd417c4bd0071d79b5e9baf904e20f436e8dfa9dc4af7b2f0f06dd6901fedfb275664190bde61df2c7b7849f0ef697646296fe42a684416bfe2be846ff4449b5cf8ad658f1804d90195c10324cfb071c764ff61355c64e759d1a4e9d631a6d78a760a139737763203600145505bf1a7f04ba4106014fb9104b57a8b44dadfa4a7bc1ded25dc9c252594da3a5f52fd364f29a088e9f451502be292785c15de7a651e3ae2a050e0539c5981c2d3406d5a0331ed451d7988b643bc658d258b4f47506bd02d6fd2e0775bcfa91b368bf51207ebd2d63180cb0f02d5b9f6be1b02aa1a962e41c8f26e2ce9dc15b131b9dd4e547fce08e99b2eb1e56d14e19f697bcec0710c7c60e28b5d9af87d9be14614f7b6c733c2aff9c7fba1f36503ad092daf2607896b06ab01fb6d1a4e4961b9374353ef340b4a65a2feac0792efccb67f2749cd73a60beb76cd304b2cd3e80832835d0cb1debaef54f8a3965a47f0993646ecdeb48cf792ae30a0896e1a1eddaf4f09332c1f352ce4347a8faff316f16850cb0367539f39a022bb34a029b12ef8b6712abb4565570a1d172c2bbd4b242f818b5af1dd46eaf106009a512b53c6b945b6acba91f1d8fbeaf224dbc904172c3e3bdc4fa648e1a240dcf2a1213529d6be1cad52bba9f74f5515ab08d1158cc3d2e6e6c9ca9a089a223335632c79a62c4977c417c5a48d1f63d6a0245856666571d55f03cbed3d07d6be645b595092b8d7acf7cbfd00889a5427fd546d19f44f4e1d6348670d91fa02e4ccd885f5cd87308c190bceba0642d7fbc975ff0ff58cf78a26133488bc538ff6cad84ebbfdb39997a79a0d99eba01310f9020803132216dd8c4fef0e8307cf10e309d5399dc2bee5d2845cdfd30320b212a214f8d3a33d14f42ef143cd33aec5a41d32d589b0ba5b6d8fc512ca40611e5dafc23ee47d111b6008ca94697177a14e3f0e66ab41f2f94c2e37a3e41717c7ebca9318d26a30d136bfe5da7ff73a7fa637f88d0787968986875d7c5d0d4da839ea1990c1cac315a187c3d3843ea9504a4d4f6a6b5da7cfc3b61b3ee9984bbb9789728e94c3663e2bf5331bd7f703d6f40f424e18d8adc839d2b121f7b4b4d40f0e47ac4b808b1e7e45c0204c2fdb2da3be8b59dad1224aab78ad447d52823c386f976d716dc6c6ca3f3e7e41746afe8e9b01946446b6e2eec7ba94db910febfe1e7fa52ffd6390e7f9c5eb173a4ba590f593df45651dde0ad68e535d8a23c46e3f6a7e855c0fc5d2190b57c9ddd7843eb093e5ff98f052b3b81808d803e9a88d9e5fa48847a3c3d18894ce49637bdf211866f2c71116384c40c82236cf84f82f213bcd5f4df22fb0f5087ebb7d344d33bf3087939388b8ab9ce39e4b6766ee84ae7c812e030bc16bbe58aa5fa7837f36626ef47b1b13872194d585381f3b17b488e3a0fdee45f5f113ada681f9913fa2bca3d70a0e7cedbe8c5dca828f116e9d4d2e7fe9cb25fe2fcda8322869afe254eb254b869d4819688782076bffc273eb9ca69a8b357a0be9682b06f959530a848989722c8a9f2c79d8f07ed80a76a3ca557280b830432de6571ff342b6b3a7f644aa7ab96733de40e5989774fe2e0bbf9370e58d4c6abedd5284b6c9a8f140459f3b5289678947c69769fdb20295a80cf26fce7e8c5b245cf139365aa1b8068f50c54fa1359710bde46fd74efd941ff4ef66345f117b0bed8346dc09dc17a6a64093a686736d4c4ba9547503826011b8fbe3a2cfc7ac3b51bd6a575eebd016edc987e49d435d4c2db9750dde190cfe44e96daa99a58c0c6c3cf24debafaed0610c5e6b5ff575c1c5f711ed8e3e3ba9b260b2febabccdc9f54e6d0f81c8b93fa036aa6cd9eb796ffd49c1a9ca5563bf99f01e90dd8cb3e365fe57131e7bfa15e52fe3f60c1cce049690de1ac72f45d1849ebd53420cb136b071e4ef647eb4b9ab528ad0f9f7c776f3fe42c570bc533e9f79cc793bf4149a78ddf0f8644bab7724b3ff55b884c4aba7aa6bd601269109fabf9d582e48691530d09f34de8658d8dd12b09755cd0a53886fa6d919cf81f77f52b5b0b9fbaf5d03d6a4266cd695984935e852b7a70cf2565496b84d3372e539a8b068109d44ad090b33d057ca3643380d1cfcbf5b34925e368b91dcf0f5fd92e84e7daf14bb907e6e4909c4959e885e9ba5e769cc476ccd9bddff07446251f9ac93afbf664449d60c7c9b56d041fbe584245c4ec8c7cefaa11f7984049bdccfac10afc31799d781b91f7080d37d443819291db27ad7cb70241a7da327ce5e22d76184a4e08bea89246c5b723374c084da38764edf91346aed329eda99668a889349467f752567ee00a5542efbbe2e158744e4e49abefb078a15efdfa1897f43085da7e1295e17ea626789af9b83d13c23faae98c6607da3e521fcf7c36aefe7d9b947b8cd6fc5842c8de3ed200fae36555fc510d0af47ac08a5c06720884a4c8ab90139562dbe6359c1926b4d5c93403b5021b615245b7e68e47145c9172e3ac342bd54a17fcdac155cfd933b51d48e5f46bfa8b11bf8165586ed2ef43740e119efb1e31ff35e828469456b8ee8a9171d8f550785312c3441588a9450b2832e08d803c13466e342a435a862a150c6dc29e0104f012bed29717adcd3c4992256bababd43c4e3991f7c5725dd1b2a486d2ccdcd6ad948f6f53da4ebcd66ce794f2abd5d363b40cf21607475c28680caff3be00ce94d4d9a2a1fb430cccf8154ea335feee4b89fa6839ea9125e97f068899de6f916004e229ae7f9b32b009af9398a83ea0912a27b379202750ce4f5209afb9da6331e6172a4c286fe0cba6e881758423c02a4bc99c363cab1ab9719fcbe7e37aef692c5ba828ae67a208bf5d5095c06be00e7b786da7d31f4ec72e8c69708c03a55c54a84e4b9bf706418629a62ea41a6c4ab7c858459ee01e940c9c99301d45a3c16b5c980fd751d65361bf64f20f9fa5cc207e998e46236a65d393b22d15ed8e388eb086104cecbc64b3aa15e025f0fdfafc889a3ab919923e28afc60ea724e405881d8096fbc26ec3d9eacc6e8e5b59b7bd10806e2b5a45af5059153df9718d2e85322809dbed51e92a096b82f27e8ff418400c314a9bed5e449de8c1b9493c4cadb7d7574c3a1a94bfa5c47750bad7c9d7dc47be8d683b892de0d9882d6a414f36bfec308742689333c6d07f88c8494a9fd52d0f5094c6ebb230b8ebc4cc9797e1d64a21a6db37130018abf696f28f30713fb7c3a55be8bd80cee89e9ed5295e804d2e48b10729b759d3cecee1d6d11b987b7d5678b6bdf5cb6113adcb7eec8af5362e45a1af5664bd85cc90d627e62f1f44ec932b74766c1edbadadc5de3fdf59c05bfbac44307ef94bae54846519b4987fb4c5725d593a5d84e635a912b5203b130482d897b8001a12a1fa4323c31bc30f83ce9caa3e5b6802130c69d633fe389c8c6e2d9b110b5869b54a9c9df7327d9f3b8fb46bd0f4c9bf299e5ee4b181ece08d6e978836aea653cbc22ced393d749e956ae2775e877dd87e9848c681e4af9c29f0ebc6152822318c8b32bdd3dd2388a0196fca2c6a176c37c645686ddd5e359db948bf1fe122958c68eca414f5a3c2d5ab4f896ce4db22d09cf540f6ce296726f5ec1e63203f79238fe75a7468ee51ffff67c4d103129a9d9c97e8dd8b8d0b52b6afdefbf1bde912f3cf42b7bae14dbb98d2208293bb0061192c12d525e1e84f0a83df6778c3f48d3c3bc0ceb68a374dc2c80028267c73fbddb09ff085ce5ec58a596f4058a3579ccc5af4e2717e1e6381d7cbc8accb65d85e1f787401086e11628b16e58f9141362dabbc566866d906d813632928ea551b39217239510ed37eb745e378f69fb0796b442ba11e8fe7ac3c0c72dfd737961a61ba36ba6c94e1873e00b8c3108a00ca6dc1b55ee524f6e0f17fa9ad7899050d1fd01134658749cda00ac9d2ffb147aa745e18dc677c36eeb1ae6b903071c3aaaed860ba4c06f706f7deec7eb6977de1f2d78b1df7efbae4acdec1ec35833f55321d4601995a15271f1b32c60662a428fbb3ae799d827136e0ff3496a6bc8251d55430631cfe500511787776894147330030fb47cd62b3cc73104d4b759ace3fd2cd2a936c3e65ff71aa2012bfaf2d7c47bb33d2885a6cf1b75504d4bd007fc59c947270c49fe53976cce349ef177c7d17d209abfb4b1cb7064cbbdb711e19f5194bd0402ef97e6e3210096b51fefc8985babbfb642d0c76373e1a23a8690662f5767d8c67e3794ed98cdfae16981aa5a008fd3fc8b41dec0642602d37576d01c2b87dce2eb5575143429ceaf6ad2fbdd709012a937280d35fb35e20ef67498ff72fcac92d25de3213944d550963c9696891285b439efe77376f2b9c8b5fac954998475745cbc76b3898f9eb09fe33be0b7619e6ef6379c41c0bc04fb0f15c988426fd51853be56025c50452791a6e3341fc5a558223d3e2aba49f5e3ceeedeffded3ed55e615118dba1fe14c4fa120a5f6ffb1dfe0794802a11b041b4d83fd90726e285cb771101e91b9dd180d42f30293e0df4f8952f1c5cda633136f1e30c803653dd90683f5dc722be491434fdd504dff1c917432e6e04065c1044b6b38d1d61b57f4eded135d7de22cce4eee11cd1e20e7f27536a75c291269e3c0a229a428a701de5d562f79c98bd87622beb7904f17119ec6ca8918ce4fca462efd6541cf982dd3a411f920068679b346efb363af976421b78dad8e2104a0e6b0cdb7e79daf967b66e68676044c36ee2e350f6f39f5120509e004ae7cd96542fef78aaafb64ddae778f8117a19459f6e638a969c3e166d8ce1bbab439a834621dc41f1f0c4e9fef18cb6d2bef30852a499277ff3fea4c5f79bfd894354d567c17b38e2e1db4874cc61e28ec951a92567d3eda5a7e299fb84edb235b9785e066f2ae4d483794dff059f9eab82433676d8db696ca98a849d61271c2eeebe6bea3410723ab20c550b62e6d7405523763832d5015bac29e950cb0b96809b41729537b627496f10cdeea00fadfab49d15e4843cd6512e2abbcca9e2abb631306080cf3121efe2ba87fb9972bc28965e59cfd9d34e3b9b275b43e793524daa5774360881a31f029181ea4a1d6788a2c1452898c89789b46ec6a8beab6d9aac3193c75a1b6f25bf5a6dfbc80e650840aec9521c6e739094e0e4398cf377897bc14d865bb0ffec2e7d67cb0c504a38c5cb98d2c39a8303b5b13eb7290c8bdf7d78d59bc1e4a2918eee0a5f4c28c1b567aa8d9f2fc7257f94148266465971e0946e55cf8f78b9c49fe2ff6dbb837d93ff6457d41f1af321c8b513173a91c6624eada68e8b91035e47133f91eed223fd86564acb10f1718adf5bbc81cce6cb2d7acd4f3c1b2f334b7bdda2a289dfe1008f6e702dbcf3fdb46d39d3d71e3f10bd2be6d15bf30f15da1f49e98191ed705e321c2e428e8cdfbe2f6ea9a714c2544c7b19f61e8e54af468318a3653a2b5d4e770"); - let decompressed = decompress_brotli(&raw_batch).unwrap(); + let decompressed = + decompress_brotli(&raw_batch, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize).unwrap(); assert_eq!(decompressed, raw_batch_decompressed); } }