From de60e3ea3799bbffe3ac1cc2f713b46e32c3b58d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= <164224824+marcospb19-cw@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:51:15 -0300 Subject: [PATCH] enha: tidy rocksdb storage (#1906) * enha: tidy rocks - store BlockNumber as u32 * chore: rename LogMinedRockdb to LogMinedRocksdb * enha: tidy rocksdb - writing txs - don't write block number and hash those 2 can always be loaded from context, in this case, the header of the block * enha: tidying rocksdb - log index and tx index as u32 * enha: rocks tidying - txType u64->u8 * enha: tidying rocksdb - don't store block number and hash for each tx log * enha: tidying rocksdb - don't store log index * enha: rocks tidying - don't store tx hash or index in log * update snapshot tests --- src/bin/historic_events_processor.rs | 20 +++++-- src/eth/follower/importer/importer.rs | 4 +- src/eth/primitives/block_number.rs | 6 ++- .../permanent/rocks/rocks_permanent.rs | 8 +-- .../storage/permanent/rocks/rocks_state.rs | 27 +++++----- .../storage/permanent/rocks/types/block.rs | 49 ++++++++++-------- .../permanent/rocks/types/block_number.rs | 14 ++--- .../storage/permanent/rocks/types/index.rs | 10 ++-- .../permanent/rocks/types/log_mined.rs | 43 +++++++-------- src/eth/storage/permanent/rocks/types/mod.rs | 4 +- .../rocks/types/transaction_input.rs | 4 +- .../rocks/types/transaction_mined.rs | 34 ++++++------ .../cf_versions/blocks_by_hash/V1.bincode | Bin 12 -> 8 bytes .../cf_versions/blocks_by_number/V1.bincode | Bin 7986 -> 6305 bytes tests/fixtures/cf_versions/logs/V1.bincode | Bin 12 -> 8 bytes .../cf_versions/transactions/V1.bincode | Bin 12 -> 8 bytes 16 files changed, 118 insertions(+), 105 deletions(-) diff --git a/src/bin/historic_events_processor.rs b/src/bin/historic_events_processor.rs index a9156a010..319d976d2 100644 --- a/src/bin/historic_events_processor.rs +++ b/src/bin/historic_events_processor.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::time::Duration; use anyhow::Context; @@ -6,7 +7,10 @@ use chrono::TimeZone; use chrono::Timelike; use indicatif::ProgressBar; use rocksdb::properties::ESTIMATE_NUM_KEYS; +use stratus::eth::primitives::TransactionMined; +use stratus::eth::storage::permanent::rocks::types::BlockNumberRocksdb; use stratus::eth::storage::permanent::rocks::types::BlockRocksdb; +use stratus::eth::storage::permanent::rocks::types::HashRocksdb; use stratus::eth::storage::permanent::rocks::types::TransactionMinedRocksdb; use stratus::eth::storage::permanent::rocks::types::UnixTimeRocksdb; use stratus::eth::storage::permanent::rocks::RocksStorageState; @@ -18,8 +22,14 @@ use stratus::ledger::events::Event; const TIMEOUT: Duration = Duration::from_secs(5); /// Converts a mined transaction from RocksDB to account transfer events -fn transaction_mined_rocks_db_to_events(block_timestamp: UnixTimeRocksdb, tx: TransactionMinedRocksdb) -> Vec { - transaction_to_events(block_timestamp.into(), std::borrow::Cow::Owned(tx.into())) +fn transaction_mined_rocks_db_to_events( + block_timestamp: UnixTimeRocksdb, + tx: TransactionMinedRocksdb, + block_number: BlockNumberRocksdb, + block_hash: HashRocksdb, +) -> Vec { + let tx = TransactionMined::from_rocks_primitives(tx, block_number, block_hash); + transaction_to_events(block_timestamp.into(), Cow::Owned(tx)) } /// Returns total count of blocks and transactions from RocksDB state @@ -72,7 +82,7 @@ fn process_block_events(block: BlockRocksdb) -> Vec { block .transactions .into_iter() - .flat_map(|tx| transaction_mined_rocks_db_to_events(timestamp, tx)) + .flat_map(|tx| transaction_mined_rocks_db_to_events(timestamp, tx, block.header.number, block.header.hash)) .map(|event| event.event_payload().unwrap()) .collect() } @@ -87,13 +97,13 @@ fn main() -> Result<(), anyhow::Error> { // Load last processed block number from file tracing::info!("loading last processed block"); let start_block = std::fs::read_to_string("last_processed_block") - .map(|s| s.trim().parse::().unwrap()) + .map(|s| s.trim().parse::().unwrap()) .unwrap_or(0); tracing::info!(?start_block); tracing::info!("creating rocksdb iterator"); let iter = if start_block > 0 { - b_pb.inc(start_block); + b_pb.inc(start_block.into()); state.blocks_by_number.iter_from(start_block.into(), rocksdb::Direction::Forward)? } else { state.blocks_by_number.iter_start() diff --git a/src/eth/follower/importer/importer.rs b/src/eth/follower/importer/importer.rs index 0d6c397de..4409c3780 100644 --- a/src/eth/follower/importer/importer.rs +++ b/src/eth/follower/importer/importer.rs @@ -30,7 +30,6 @@ use crate::ext::traced_sleep; use crate::ext::DisplayExt; use crate::ext::SleepReason; use crate::globals::IMPORTER_ONLINE_TASKS_SEMAPHORE; -use crate::if_else; use crate::infra::kafka::KafkaConnector; #[cfg(feature = "metrics")] use crate::infra::metrics; @@ -62,9 +61,8 @@ static EXTERNAL_RPC_CURRENT_BLOCK: AtomicU64 = AtomicU64::new(0); /// Only sets the external RPC current block number if it is equals or greater than the current one. fn set_external_rpc_current_block(new_number: BlockNumber) { - let new_number_u64 = new_number.as_u64(); let _ = EXTERNAL_RPC_CURRENT_BLOCK.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current_number| { - if_else!(new_number_u64 >= current_number, Some(new_number_u64), None) + Some(current_number.max(new_number.as_u64())) }); } diff --git a/src/eth/primitives/block_number.rs b/src/eth/primitives/block_number.rs index 5002b169c..0f722ad51 100644 --- a/src/eth/primitives/block_number.rs +++ b/src/eth/primitives/block_number.rs @@ -77,15 +77,17 @@ impl BlockNumber { } } - /// Converts itself to i64. pub fn as_i64(&self) -> i64 { self.0.as_u64() as i64 } - /// Converts itself to u64. pub fn as_u64(&self) -> u64 { self.0.as_u64() } + + pub fn as_u32(&self) -> u32 { + self.0.as_u64() as u32 + } } impl Dummy for BlockNumber { diff --git a/src/eth/storage/permanent/rocks/rocks_permanent.rs b/src/eth/storage/permanent/rocks/rocks_permanent.rs index 4ab9fbeb2..90b6e7c60 100644 --- a/src/eth/storage/permanent/rocks/rocks_permanent.rs +++ b/src/eth/storage/permanent/rocks/rocks_permanent.rs @@ -1,5 +1,5 @@ use std::path::Path; -use std::sync::atomic::AtomicU64; +use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering; use std::time::Duration; @@ -23,7 +23,7 @@ use crate::eth::storage::PermanentStorage; #[derive(Debug)] pub struct RocksPermanentStorage { pub state: RocksStorageState, - block_number: AtomicU64, + block_number: AtomicU32, } impl RocksPermanentStorage { @@ -82,7 +82,7 @@ impl PermanentStorage for RocksPermanentStorage { } fn set_mined_block_number(&self, number: BlockNumber) -> anyhow::Result<()> { - self.block_number.store(number.as_u64(), Ordering::SeqCst); + self.block_number.store(number.as_u32(), Ordering::SeqCst); Ok(()) } @@ -150,7 +150,7 @@ impl PermanentStorage for RocksPermanentStorage { #[cfg(feature = "dev")] fn reset(&self) -> anyhow::Result<()> { - self.block_number.store(0u64, Ordering::SeqCst); + self.block_number.store(0u32, Ordering::SeqCst); self.state.reset().inspect_err(|e| { tracing::error!(reason = ?e, "failed to reset in RocksPermanent"); }) diff --git a/src/eth/storage/permanent/rocks/rocks_state.rs b/src/eth/storage/permanent/rocks/rocks_state.rs index 7c3307c2c..9c5fa32f7 100644 --- a/src/eth/storage/permanent/rocks/rocks_state.rs +++ b/src/eth/storage/permanent/rocks/rocks_state.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::fmt; use std::fmt::Debug; -use std::sync::atomic::AtomicU64; +use std::sync::atomic::AtomicU32; use std::sync::Arc; use std::time::Duration; use std::time::Instant; @@ -185,10 +185,10 @@ impl RocksStorageState { self.db_path.rsplit('/').next().unwrap_or(&self.db_path) } - pub fn preload_block_number(&self) -> Result { - let block_number = self.blocks_by_number.last_key()?.unwrap_or_default(); + pub fn preload_block_number(&self) -> Result { + let block_number = self.blocks_by_number.last_key()?.unwrap_or_default().0; tracing::info!(%block_number, "preloaded block_number"); - Ok((u64::from(block_number)).into()) + Ok(AtomicU32::new(block_number)) } #[cfg(feature = "dev")] @@ -257,13 +257,14 @@ impl RocksStorageState { return log_and_err!("the block that the transaction was supposed to be in was not found") .with_context(|| format!("block_number = {:?} tx_hash = {}", block_number, tx_hash)); }; + let block = block.into_inner(); - let transaction = block.into_inner().transactions.into_iter().find(|tx| Hash::from(tx.input.hash) == tx_hash); + let transaction = block.transactions.into_iter().find(|tx| Hash::from(tx.input.hash) == tx_hash); match transaction { Some(tx) => { tracing::trace!(%tx_hash, "transaction found"); - Ok(Some(tx.into())) + Ok(Some(TransactionMined::from_rocks_primitives(tx, block_number.into_inner(), block.header.hash))) } None => log_and_err!("rocks error, transaction wasn't found in block where the index pointed at") .with_context(|| format!("block_number = {:?} tx_hash = {}", block_number, tx_hash)), @@ -289,12 +290,12 @@ impl RocksStorageState { break; } - let logs = block - .into_inner() - .transactions - .into_iter() - .flat_map(|transaction| transaction.logs) - .map(LogMined::from); + let block = block.into_inner(); + let logs = block.transactions.into_iter().enumerate().flat_map(|(tx_index, transaction)| { + transaction.logs.into_iter().enumerate().map(move |(log_index, log)| { + LogMined::from_rocks_primitives(log, block.header.number, block.header.hash, tx_index, transaction.input.hash, log_index) + }) + }); let filtered_logs = logs.filter(|log| filter.matches(log)); logs_result.extend(filtered_logs); @@ -405,7 +406,7 @@ impl RocksStorageState { self.accounts_history.prepare_batch_insertion( accounts.iter().cloned().map(|acc| { let tup = <(AddressRocksdb, AccountRocksdb)>::from(acc); - ((tup.0, 0u64.into()), tup.1.into()) + ((tup.0, 0u32.into()), tup.1.into()) }), &mut write_batch, )?; diff --git a/src/eth/storage/permanent/rocks/types/block.rs b/src/eth/storage/permanent/rocks/types/block.rs index aa1e20bac..6aea8cfa7 100644 --- a/src/eth/storage/permanent/rocks/types/block.rs +++ b/src/eth/storage/permanent/rocks/types/block.rs @@ -48,28 +48,31 @@ impl From for BlockRocksdb { impl From for Block { fn from(item: BlockRocksdb) -> Self { - Block { - header: BlockHeader { - number: BlockNumber::from(item.header.number), - hash: Hash::from(item.header.hash), - transactions_root: Hash::from(item.header.transactions_root), - gas_used: item.header.gas_used.into(), - gas_limit: item.header.gas_limit.into(), - bloom: item.header.bloom.into(), - timestamp: item.header.timestamp.into(), - parent_hash: Hash::from(item.header.parent_hash), - author: Address::from(item.header.author), - extra_data: item.header.extra_data.into(), - miner: Address::from(item.header.miner), - difficulty: item.header.difficulty.into(), - receipts_root: Hash::from(item.header.receipts_root), - uncle_hash: Hash::from(item.header.uncle_hash), - size: item.header.size.into(), - state_root: Hash::from(item.header.state_root), - total_difficulty: item.header.total_difficulty.into(), - nonce: item.header.nonce.into(), - }, - transactions: item.transactions.into_iter().map(TransactionMined::from).collect(), - } + let header = BlockHeader { + number: BlockNumber::from(item.header.number), + hash: Hash::from(item.header.hash), + transactions_root: Hash::from(item.header.transactions_root), + gas_used: item.header.gas_used.into(), + gas_limit: item.header.gas_limit.into(), + bloom: item.header.bloom.into(), + timestamp: item.header.timestamp.into(), + parent_hash: Hash::from(item.header.parent_hash), + author: Address::from(item.header.author), + extra_data: item.header.extra_data.into(), + miner: Address::from(item.header.miner), + difficulty: item.header.difficulty.into(), + receipts_root: Hash::from(item.header.receipts_root), + uncle_hash: Hash::from(item.header.uncle_hash), + size: item.header.size.into(), + state_root: Hash::from(item.header.state_root), + total_difficulty: item.header.total_difficulty.into(), + nonce: item.header.nonce.into(), + }; + let transactions = item + .transactions + .into_iter() + .map(|tx| TransactionMined::from_rocks_primitives(tx, header.number.into(), header.hash.into())) + .collect(); + Block { header, transactions } } } diff --git a/src/eth/storage/permanent/rocks/types/block_number.rs b/src/eth/storage/permanent/rocks/types/block_number.rs index 6c15ab796..2cc67c428 100644 --- a/src/eth/storage/permanent/rocks/types/block_number.rs +++ b/src/eth/storage/permanent/rocks/types/block_number.rs @@ -5,13 +5,13 @@ use crate::eth::primitives::BlockNumber; use crate::gen_newtype_from; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Ord, PartialOrd, Hash, derive_more::Display, fake::Dummy)] -pub struct BlockNumberRocksdb(pub u64); +pub struct BlockNumberRocksdb(pub u32); -gen_newtype_from!(self = BlockNumberRocksdb, other = u8, u16, u32, u64); +gen_newtype_from!(self = BlockNumberRocksdb, other = u8, u16, u32); impl From for BlockNumberRocksdb { fn from(item: BlockNumber) -> Self { - item.0.as_u64().into() + Self(item.as_u32()) } } @@ -21,7 +21,7 @@ impl From for BlockNumber { } } -impl From for u64 { +impl From for u32 { fn from(value: BlockNumberRocksdb) -> Self { value.0 } @@ -35,14 +35,14 @@ impl serde::Serialize for BlockNumberRocksdb { impl<'de> serde::Deserialize<'de> for BlockNumberRocksdb { fn deserialize>(deserializer: D) -> Result { - u64::deserialize(deserializer).map(|v| Self(u64::from_be(v))) + u32::deserialize(deserializer).map(|v| Self(u32::from_be(v))) } } -impl Add for BlockNumberRocksdb { +impl Add for BlockNumberRocksdb { type Output = Self; - fn add(self, other: u64) -> Self { + fn add(self, other: u32) -> Self { BlockNumberRocksdb(self.0 + other) } } diff --git a/src/eth/storage/permanent/rocks/types/index.rs b/src/eth/storage/permanent/rocks/types/index.rs index b1500a19b..1e515a49b 100644 --- a/src/eth/storage/permanent/rocks/types/index.rs +++ b/src/eth/storage/permanent/rocks/types/index.rs @@ -3,22 +3,22 @@ use std::fmt::Debug; use crate::eth::primitives::Index; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Add, Copy, Hash, fake::Dummy)] -pub struct IndexRocksdb(u64); +pub struct IndexRocksdb(pub(self) u32); impl IndexRocksdb { - pub fn inner_value(&self) -> u64 { - self.0 + pub fn as_usize(&self) -> usize { + self.0 as usize } } impl From for IndexRocksdb { fn from(item: Index) -> Self { - IndexRocksdb(item.0) + IndexRocksdb(item.0 as u32) } } impl From for Index { fn from(item: IndexRocksdb) -> Self { - Index::new(item.inner_value()) + Index::new(item.0 as u64) } } diff --git a/src/eth/storage/permanent/rocks/types/log_mined.rs b/src/eth/storage/permanent/rocks/types/log_mined.rs index c3e88b2b8..c5db8d235 100644 --- a/src/eth/storage/permanent/rocks/types/log_mined.rs +++ b/src/eth/storage/permanent/rocks/types/log_mined.rs @@ -2,42 +2,37 @@ use std::fmt::Debug; use super::block_number::BlockNumberRocksdb; use super::hash::HashRocksdb; -use super::index::IndexRocksdb; use super::log::LogRocksdb; +use crate::eth::primitives::Index; use crate::eth::primitives::LogMined; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, fake::Dummy)] -pub struct LogMinedRockdb { +pub struct LogMinedRocksdb { pub log: LogRocksdb, - pub transaction_hash: HashRocksdb, - pub transaction_index: IndexRocksdb, - pub log_index: IndexRocksdb, - pub block_number: BlockNumberRocksdb, - pub block_hash: HashRocksdb, } -impl From for LogMinedRockdb { +impl From for LogMinedRocksdb { fn from(item: LogMined) -> Self { - Self { - log: item.log.into(), - transaction_hash: item.transaction_hash.into(), - transaction_index: item.transaction_index.into(), - log_index: item.log_index.into(), - block_number: item.block_number.into(), - block_hash: item.block_hash.into(), - } + Self { log: item.log.into() } } } -impl From for LogMined { - fn from(item: LogMinedRockdb) -> Self { +impl LogMined { + pub fn from_rocks_primitives( + other: LogMinedRocksdb, + block_number: BlockNumberRocksdb, + block_hash: HashRocksdb, + tx_index: usize, + tx_hash: HashRocksdb, + log_index: usize, + ) -> Self { Self { - log: item.log.into(), - transaction_hash: item.transaction_hash.into(), - transaction_index: item.transaction_index.into(), - log_index: item.log_index.into(), - block_number: item.block_number.into(), - block_hash: item.block_hash.into(), + block_number: block_number.into(), + block_hash: block_hash.into(), + log: other.log.into(), + transaction_hash: tx_hash.into(), + transaction_index: Index::from(tx_index as u64), + log_index: Index::from(log_index as u64), } } } diff --git a/src/eth/storage/permanent/rocks/types/mod.rs b/src/eth/storage/permanent/rocks/types/mod.rs index b401c78fb..caaf138c7 100644 --- a/src/eth/storage/permanent/rocks/types/mod.rs +++ b/src/eth/storage/permanent/rocks/types/mod.rs @@ -44,7 +44,7 @@ mod tests { use execution::ExecutionRocksdb; use execution_result::ExecutionResultRocksdb; use gas::GasRocksdb; - use log_mined::LogMinedRockdb; + use log_mined::LogMinedRocksdb; use logs_bloom::LogsBloomRocksdb; use miner_nonce::MinerNonceRocksdb; use nonce::NonceRocksdb; @@ -71,7 +71,7 @@ mod tests { gen_test_bincode!(GasRocksdb); gen_test_bincode!(HashRocksdb); gen_test_bincode!(IndexRocksdb); - gen_test_bincode!(LogMinedRockdb); + gen_test_bincode!(LogMinedRocksdb); gen_test_bincode!(LogRocksdb); gen_test_bincode!(LogsBloomRocksdb); gen_test_bincode!(MinerNonceRocksdb); diff --git a/src/eth/storage/permanent/rocks/types/transaction_input.rs b/src/eth/storage/permanent/rocks/types/transaction_input.rs index 5339c8c87..a1368105c 100644 --- a/src/eth/storage/permanent/rocks/types/transaction_input.rs +++ b/src/eth/storage/permanent/rocks/types/transaction_input.rs @@ -14,7 +14,7 @@ use crate::ext::OptionExt; #[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, fake::Dummy)] pub struct TransactionInputRocksdb { - pub tx_type: Option, + pub tx_type: Option, pub chain_id: Option, pub hash: HashRocksdb, pub nonce: NonceRocksdb, @@ -33,7 +33,7 @@ pub struct TransactionInputRocksdb { impl From for TransactionInputRocksdb { fn from(item: TransactionInput) -> Self { Self { - tx_type: item.tx_type.map(|inner| inner.as_u64()), + tx_type: item.tx_type.map(|inner| inner.as_u64() as u8), chain_id: item.chain_id.map_into(), hash: HashRocksdb::from(item.hash), nonce: NonceRocksdb::from(item.nonce), diff --git a/src/eth/storage/permanent/rocks/types/transaction_mined.rs b/src/eth/storage/permanent/rocks/types/transaction_mined.rs index 2ef9b579c..a2f948800 100644 --- a/src/eth/storage/permanent/rocks/types/transaction_mined.rs +++ b/src/eth/storage/permanent/rocks/types/transaction_mined.rs @@ -4,7 +4,7 @@ use super::block_number::BlockNumberRocksdb; use super::execution::ExecutionRocksdb; use super::hash::HashRocksdb; use super::index::IndexRocksdb; -use super::log_mined::LogMinedRockdb; +use super::log_mined::LogMinedRocksdb; use super::transaction_input::TransactionInputRocksdb; use crate::eth::primitives::LogMined; use crate::eth::primitives::TransactionMined; @@ -13,10 +13,8 @@ use crate::eth::primitives::TransactionMined; pub struct TransactionMinedRocksdb { pub input: TransactionInputRocksdb, pub execution: ExecutionRocksdb, - pub logs: Vec, + pub logs: Vec, pub transaction_index: IndexRocksdb, - pub block_number: BlockNumberRocksdb, - pub block_hash: HashRocksdb, } impl From for TransactionMinedRocksdb { @@ -24,23 +22,29 @@ impl From for TransactionMinedRocksdb { Self { input: item.input.into(), execution: item.execution.into(), - logs: item.logs.into_iter().map(LogMinedRockdb::from).collect(), + logs: item.logs.into_iter().map(LogMinedRocksdb::from).collect(), transaction_index: IndexRocksdb::from(item.transaction_index), - block_number: BlockNumberRocksdb::from(item.block_number), - block_hash: HashRocksdb::from(item.block_hash), } } } -impl From for TransactionMined { - fn from(item: TransactionMinedRocksdb) -> Self { +impl TransactionMined { + pub fn from_rocks_primitives(other: TransactionMinedRocksdb, block_number: BlockNumberRocksdb, block_hash: HashRocksdb) -> Self { + let logs = other + .logs + .into_iter() + .enumerate() + .map(|(log_index, log)| { + LogMined::from_rocks_primitives(log, block_number, block_hash, other.transaction_index.as_usize(), other.input.hash, log_index) + }) + .collect(); Self { - input: item.input.into(), - execution: item.execution.into(), - logs: item.logs.into_iter().map(LogMined::from).collect(), - transaction_index: item.transaction_index.into(), - block_number: item.block_number.into(), - block_hash: item.block_hash.into(), + block_number: block_number.into(), + block_hash: block_hash.into(), + input: other.input.into(), + execution: other.execution.into(), + transaction_index: other.transaction_index.into(), + logs, } } } diff --git a/tests/fixtures/cf_versions/blocks_by_hash/V1.bincode b/tests/fixtures/cf_versions/blocks_by_hash/V1.bincode index b1cdd68d4845bdf2c0be599325bb288160f1a2d1..49f778b65a4386a784d293968e48cf7ab5bdd5e6 100644 GIT binary patch literal 8 PcmZQzU|=X}esU531?mE$ literal 12 TcmZQzU|=X}esVHnnnVu(6qE!- diff --git a/tests/fixtures/cf_versions/blocks_by_number/V1.bincode b/tests/fixtures/cf_versions/blocks_by_number/V1.bincode index 6bfafe4e0f703a1953ddc1073c68965003f23fb4..8f5cf4bc340e03e0470a57329282a25d1db348ba 100644 GIT binary patch delta 3525 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 E2aqY^CIA2c delta 5364 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 V^kyYn!O9EX_*=70+iaT1{{in}TUh`A diff --git a/tests/fixtures/cf_versions/logs/V1.bincode b/tests/fixtures/cf_versions/logs/V1.bincode index b1cdd68d4845bdf2c0be599325bb288160f1a2d1..49f778b65a4386a784d293968e48cf7ab5bdd5e6 100644 GIT binary patch literal 8 PcmZQzU|=X}esU531?mE$ literal 12 TcmZQzU|=X}esVHnnnVu(6qE!- diff --git a/tests/fixtures/cf_versions/transactions/V1.bincode b/tests/fixtures/cf_versions/transactions/V1.bincode index b1cdd68d4845bdf2c0be599325bb288160f1a2d1..49f778b65a4386a784d293968e48cf7ab5bdd5e6 100644 GIT binary patch literal 8 PcmZQzU|=X}esU531?mE$ literal 12 TcmZQzU|=X}esVHnnnVu(6qE!-