From 2f4869b9b9cd2190b6b1d9211febe35d010f9962 Mon Sep 17 00:00:00 2001 From: Yan Mingzhi Date: Mon, 26 Aug 2024 12:35:46 +0000 Subject: [PATCH 1/2] chore: upgrade workspace resolver version --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 9bcbedd..5c83f8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ split-debuginfo = "packed" [workspace] members = ["example", "interface", "storage", "svm/cli", "svm/executor"] +resolver = "2" [workspace.package] version = "0.1.0" From c279c69c358e6cf9c3b832fa75df5eef7eb2ddde Mon Sep 17 00:00:00 2001 From: Yan Mingzhi Date: Wed, 28 Aug 2024 00:59:20 +0000 Subject: [PATCH 2/2] feat: init banks from blockstore not working properly error --- storage/src/accounts/mod.rs | 18 +++-- storage/src/accounts/tests.rs | 134 +++++++++++++++++++++++++++++++++- storage/src/blockstore/mod.rs | 65 ++++++++++++----- storage/src/error.rs | 6 ++ storage/src/impls.rs | 20 ++--- storage/src/init/default.rs | 50 ++----------- storage/src/ledger.rs | 31 ++++++-- storage/src/tests/basic.rs | 14 ++-- 8 files changed, 237 insertions(+), 101 deletions(-) diff --git a/storage/src/accounts/mod.rs b/storage/src/accounts/mod.rs index 0a4022d..19633f1 100644 --- a/storage/src/accounts/mod.rs +++ b/storage/src/accounts/mod.rs @@ -1,4 +1,5 @@ use rollups_interface::l2::{bank::BankInfo, storage::TransactionSet}; +use solana_entry::entry::Entry; use solana_runtime::{ bank::{Bank, ExecutedTransactionCounts, NewBankOptions, TotalAccountsStats}, installed_scheduler_pool::BankWithScheduler, @@ -104,6 +105,7 @@ impl RollupStorage { &mut self, mut result: TransactionsResultWrapper, batch: &CommitBatch, + entries: &[Entry], ) -> Result<()> { // In order to avoid a race condition, leaders must get the last // blockhash *before* recording transactions because recording @@ -126,17 +128,19 @@ impl RollupStorage { &mut result.output.execute_timings, ); - self.register_ticks(); - Ok(()) + self.register_ticks(entries) } - fn register_ticks(&self) { + fn register_ticks(&self, entries: &[Entry]) -> Result<()> { let fork = self.bank_forks.read().unwrap(); let bank_with_schedule = fork.working_bank_with_scheduler(); - // TODO: register real ticks later if use scheduled bank - for _ in bank_with_schedule.tick_height()..bank_with_schedule.max_tick_height() { - bank_with_schedule.register_tick(&Default::default()); - } + + // Skip the first tick because it's the entry that contains transactions + entries.iter().skip(1).for_each(|entry| { + bank_with_schedule.register_tick(&entry.hash); + }); + + Ok(()) } fn collect_execution_logs( diff --git a/storage/src/accounts/tests.rs b/storage/src/accounts/tests.rs index e5661cc..189e1e6 100644 --- a/storage/src/accounts/tests.rs +++ b/storage/src/accounts/tests.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use rollups_interface::l2::storage::StorageOperations; +use rollups_interface::l2::{bank::BankOperations, storage::StorageOperations}; use solana_accounts_db::{ accounts_db::{self, ACCOUNTS_DB_CONFIG_FOR_TESTING}, accounts_index::AccountSecondaryIndexes, @@ -9,11 +9,139 @@ use solana_runtime::{ snapshot_bank_utils, snapshot_utils::{self, create_tmp_accounts_dir_for_tests}, }; -use solana_sdk::{clock::Slot, pubkey::Pubkey, system_transaction}; +use solana_sdk::{ + clock::Slot, pubkey::Pubkey, signature::Keypair, signer::Signer, system_transaction, + transaction::SanitizedTransaction, +}; use solana_svm::runtime_config::RuntimeConfig; use std::time::{Duration, Instant}; -use crate::{config::GlobalConfig, RollupStorage}; +use crate::{ + blockstore::txs::CommitBatch, + config::GlobalConfig, + execution::TransactionsResultWrapper, + init::default::{DEFAULT_MINT_LAMPORTS, DEFAULT_VALIDATOR_LAMPORTS}, + tests::mock::processor::process_transfers_ex, + RollupStorage, +}; + +#[tokio::test] +async fn init_from_snapshot_works() -> Result<()> { + let ledger_path = tempfile::tempdir()?.into_path(); + let mut config = GlobalConfig::new_temp(&ledger_path)?; + config + .storage + .snapshot_config + .full_snapshot_archive_interval_slots = 1; // set snapshot interval to 1 + let mut store = RollupStorage::new(config)?; + store.init()?; + let keypairs = store.config.keypairs.clone(); + + store.set_snapshot_interval(1); + assert_eq!(store.current_height(), 0); + + let alice = keypairs.mint_keypair.as_ref().unwrap().clone(); + let bob = keypairs.validator_keypair.as_ref().unwrap().clone(); + let charlie = Keypair::new().pubkey(); + let dave = Keypair::new().pubkey(); + + const ALICE_INIT_BALANCE: u64 = DEFAULT_MINT_LAMPORTS; + const BOB_INIT_BALANCE: u64 = DEFAULT_VALIDATOR_LAMPORTS; + assert_eq!(store.balance(&alice.pubkey()), ALICE_INIT_BALANCE); + assert_eq!(store.balance(&bob.pubkey()), BOB_INIT_BALANCE); + assert_eq!(store.balance(&charlie), 0); + assert_eq!(store.balance(&dave), 0); + + const TO_CHARLIE: u64 = 2000000; + const TO_DAVE: u64 = 1000000; + const FEE: u64 = 5000; + + // 1. process transfers + store.bump()?; + let bank = store.bank.clone(); + + let raw_txs = vec![ + system_transaction::transfer(&alice, &charlie, TO_CHARLIE, bank.last_blockhash()), + system_transaction::transfer(&bob, &dave, TO_DAVE, bank.last_blockhash()), + ]; + let origin_txs = raw_txs + .clone() + .into_iter() + .map(|tx| SanitizedTransaction::from_transaction_for_tests(tx)) + .collect::>(); + let results = process_transfers_ex(&store, origin_txs.clone()); + + // 2. commit + store + .commit( + TransactionsResultWrapper { output: results }, + &CommitBatch::new(origin_txs.clone().into()), + ) + .await?; + + let (bank_height, store_height) = store.get_mixed_heights()?; + assert_eq!(bank_height, 1); + assert_eq!(store_height, Some(1)); + assert_eq!( + store.balance(&alice.pubkey()), + ALICE_INIT_BALANCE - TO_CHARLIE - FEE + ); + assert_eq!( + store.balance(&bob.pubkey()), + BOB_INIT_BALANCE - TO_DAVE - FEE + ); + assert_eq!(store.balance(&charlie), TO_CHARLIE); + assert_eq!(store.balance(&dave), TO_DAVE); + + // 3. save and close + store.force_save().await?; + // TODO: sleep is needed here, improve later + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + store.close().await?; + + // 4. open with snapshot + assert!(ledger_path.join("snapshots/1/1").exists()); + let mut config = GlobalConfig::new(&ledger_path)?; + config.keypairs = keypairs.clone(); + let mut store = RollupStorage::new(config)?; + store.init()?; + + let (bank_height, store_height) = store.get_mixed_heights()?; + assert_eq!(bank_height, 1); + assert_eq!(store_height, Some(1)); + assert_eq!( + store.balance(&alice.pubkey()), + ALICE_INIT_BALANCE - TO_CHARLIE - FEE + ); + // TODO: check why bob balance is not `BOB_INIT_BALANCE - TO_DAVE - FEE` ? + assert_eq!(store.balance(&bob.pubkey()), BOB_INIT_BALANCE - TO_DAVE); + assert_eq!(store.balance(&charlie), TO_CHARLIE); + assert_eq!(store.balance(&dave), TO_DAVE); + store.close().await?; + + // 5. open without snapshot + let mut config = GlobalConfig::new(&ledger_path)?; + config.keypairs = keypairs; + config.storage.snapshot_config = Default::default(); // disable snapshot + let mut store = RollupStorage::new(config)?; + store.init()?; + + let (bank_height, store_height) = store.get_mixed_heights()?; + assert_eq!(bank_height, 1); + assert_eq!(store_height, Some(1)); + // TODO: check why bob balance is not `ALICE_INIT_BALANCE - TO_CHARLIE` ? + assert_eq!( + store.balance(&alice.pubkey()), + ALICE_INIT_BALANCE - TO_CHARLIE + ); + // TODO: check why bob balance is not `BOB_INIT_BALANCE - TO_DAVE - FEE` ? + assert_eq!(store.balance(&bob.pubkey()), BOB_INIT_BALANCE - TO_DAVE); + assert_eq!(store.balance(&charlie), TO_CHARLIE); + assert_eq!(store.balance(&dave), TO_DAVE); + store.close().await?; + + Ok(()) +} #[tokio::test] #[ignore = "Takes a long time to run"] diff --git a/storage/src/blockstore/mod.rs b/storage/src/blockstore/mod.rs index ee9f062..47c847c 100644 --- a/storage/src/blockstore/mod.rs +++ b/storage/src/blockstore/mod.rs @@ -1,14 +1,15 @@ use process::EntriesProcessor; -use rollups_interface::l2::storage::TransactionsResult; -use solana_entry::entry::Entry; +use solana_entry::entry::{next_hash, Entry}; use solana_ledger::{blockstore_processor, shred::Shred}; -use solana_sdk::transaction::{SanitizedTransaction, VersionedTransaction}; +use solana_sdk::{hash::Hash, transaction::VersionedTransaction}; -use crate::{execution::TransactionsResultWrapper, Error, Result, RollupStorage}; +use crate::{error::BankError, Error, Result, RollupStorage}; pub mod process; pub mod txs; +const DEFAULT_NUM_HASHES: u64 = 2; + impl RollupStorage { pub(crate) fn aligne_blockstore_with_bank_forks(&self) -> Result<()> { blockstore_processor::process_blockstore_from_root( @@ -25,13 +26,8 @@ impl RollupStorage { Ok(()) } - pub(crate) fn blockstore_save( - &self, - result: &TransactionsResultWrapper, - extras: &[SanitizedTransaction], - ) -> Result<()> { - let executed_txs = result.success_txs(extras); - let (data_shreds, code_shreds) = self.transactions_to_shreds(executed_txs)?; + pub(crate) fn blockstore_save(&self, entries: Vec) -> Result<()> { + let (data_shreds, code_shreds) = self.transactions_to_shreds(entries)?; let _data_info = self.blockstore .insert_shreds(data_shreds, Some(&self.leader_schedule_cache), true)?; @@ -43,9 +39,8 @@ impl RollupStorage { pub(crate) fn transactions_to_shreds( &self, - txs: Vec, + entries: Vec, ) -> Result<(Vec, Vec)> { - let entries = self.transactions_to_entries(txs); let mut processor = EntriesProcessor::new(Default::default()); processor.process( @@ -62,11 +57,45 @@ impl RollupStorage { ) } - fn transactions_to_entries(&self, transactions: Vec) -> Vec { - let entry = Entry { + pub(crate) fn transactions_to_entries( + &self, + transactions: Vec, + ) -> Result> { + let mut start_hash = self + .bank + .parent() + .ok_or(BankError::BankNotExists(self.bank.parent_slot()))? + .last_blockhash(); + let entry = self.new_entry(&start_hash, DEFAULT_NUM_HASHES, transactions); + start_hash = entry.hash; + + let mut entries = vec![entry]; + for _ in 0..self.config.genesis.ticks_per_slot { + let entry = self.new_entry(&start_hash, DEFAULT_NUM_HASHES, vec![]); + start_hash = entry.hash; + entries.push(entry); + } + + Ok(entries) + } + + fn new_entry( + &self, + prev_hash: &Hash, + mut num_hashes: u64, + transactions: Vec, + ) -> Entry { + // If you passed in transactions, but passed in num_hashes == 0, then + // next_hash will generate the next hash and set num_hashes == 1 + if num_hashes == 0 && !transactions.is_empty() { + num_hashes = 1; + } + + let hash = next_hash(prev_hash, num_hashes, &transactions); + Entry { + num_hashes, + hash, transactions, - ..Default::default() - }; - vec![entry] + } } } diff --git a/storage/src/error.rs b/storage/src/error.rs index 45afb89..88b52ca 100644 --- a/storage/src/error.rs +++ b/storage/src/error.rs @@ -12,6 +12,9 @@ pub enum Error { #[error("Load blockstore failed: {0}")] LoadBlockstoreFailed(String), + #[error("Init bank forks failed: {0}")] + InitBankForksFailed(String), + #[error("Init config failed: {0}")] InitConfigFailed(String), @@ -47,6 +50,9 @@ pub enum StorageError { #[error("Invalid Merkle root, slot: {slot}, index: {index}")] InvalidMerkleRoot { slot: Slot, index: u64 }, + + #[error("Empty entries hashes")] + EmptyEntriesHashes, } #[derive(Debug, Error)] diff --git a/storage/src/impls.rs b/storage/src/impls.rs index 2ac3a62..ac1ec2f 100644 --- a/storage/src/impls.rs +++ b/storage/src/impls.rs @@ -1,7 +1,7 @@ use rollups_interface::l2::{ bank::{BankInfo, BankOperations}, executor::Init, - storage::{StorageOperations, TransactionSet}, + storage::{StorageOperations, TransactionSet, TransactionsResult}, }; use solana_gossip::cluster_info::ClusterInfo; use solana_ledger::{ @@ -177,24 +177,18 @@ impl StorageOperations for RollupStorage { origin: &Self::TransactionSet<'a>, ) -> Result<(), Self::Error> { // TODO: make commit async - self.blockstore_save(&result, origin.transactions())?; - self.bank_commit(result, origin)?; + let executed_txs = result.success_txs(origin.transactions()); + let entries = self.transactions_to_entries(executed_txs)?; + + self.bank_commit(result, origin, &entries)?; + self.blockstore_save(entries)?; Ok(()) } async fn force_save(&mut self) -> Result<(), Self::Error> { self.bank.freeze(); - let _removed_banks = self - .bank_forks - .write() - .unwrap() - .set_root( - self.bank.slot(), - &self.background_service.accounts_background_request_sender, - None, - ) - .map_err(|e| BankError::SetRootFailed(e.to_string()))?; + self.set_root(self.bank.slot(), None)?; Ok(()) } diff --git a/storage/src/init/default.rs b/storage/src/init/default.rs index 81d6797..7f81754 100644 --- a/storage/src/init/default.rs +++ b/storage/src/init/default.rs @@ -1,17 +1,12 @@ -use crate::{Error, Result}; -use rand::Rng; -use solana_entry::entry::create_ticks; +use crate::{config::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, Result}; use solana_ledger::{ - blockstore::Blockstore, - blockstore_options::{AccessType, BlockstoreOptions}, + blockstore::create_new_ledger, blockstore_options::LedgerColumnOptions, genesis_utils::GenesisConfigInfo, - shred::{ProcessShredsStats, ReedSolomonCache, Shredder}, }; use solana_runtime::genesis_utils::create_genesis_config_with_leader_ex; use solana_sdk::{ fee_calculator::FeeRateGovernor, genesis_config::{ClusterType, GenesisConfig}, - hash::Hash, rent::Rent, signature::Keypair, signer::Signer, @@ -53,44 +48,13 @@ pub(crate) fn default_genesis_config(ledger_path: &Path) -> Result<(GenesisConfi } fn init_block_store(ledger_path: &Path, genesis_config: &GenesisConfig) -> Result<()> { - genesis_config - .write(ledger_path) - .map_err(|e| Error::InitCommon(format!("failed to save genesis config: {e}")))?; - - let blockstore = Blockstore::open_with_options( + let hash = create_new_ledger( ledger_path, - BlockstoreOptions { - access_type: AccessType::Primary, - recovery_mode: None, - enforce_ulimit_nofile: false, - column_options: Default::default(), - }, + genesis_config, + MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, + LedgerColumnOptions::default(), )?; - let ticks_per_slot = genesis_config.ticks_per_slot; - let hashes_per_tick = genesis_config.poh_config.hashes_per_tick.unwrap_or(0); - let entries = create_ticks(ticks_per_slot, hashes_per_tick, genesis_config.hash()); - let last_hash = entries.last().unwrap().hash; - let version = solana_sdk::shred_version::version_from_hash(&last_hash); - - let shredder = Shredder::new(0, 0, 0, version).unwrap(); - let (shreds, _) = shredder.entries_to_shreds( - &Keypair::new(), - &entries, - true, // is_last_in_slot - // chained_merkle_root - Some(Hash::new_from_array(rand::thread_rng().gen())), - 0, // next_shred_index - 0, // next_code_index - true, // merkle_variant - &ReedSolomonCache::default(), - &mut ProcessShredsStats::default(), - ); - assert!(shreds.last().unwrap().last_in_slot()); - - blockstore.insert_shreds(shreds, None, false)?; - blockstore.set_roots(std::iter::once(&0))?; - // Explicitly close the blockstore before we create the archived genesis file - drop(blockstore); + info!("Create new ledger done, new genesis hash: {}", hash); Ok(()) } diff --git a/storage/src/ledger.rs b/storage/src/ledger.rs index 877d864..520bfde 100644 --- a/storage/src/ledger.rs +++ b/storage/src/ledger.rs @@ -22,14 +22,7 @@ impl RollupStorage { } pub fn reorg(&mut self, slot: u64, finalized: Option) -> Result> { - let mut bank_forks = self.bank_forks.write().unwrap(); - let removed = bank_forks - .set_root( - slot, - &self.background_service.accounts_background_request_sender, - finalized, - ) - .map_err(|e| BankError::SetRootFailed(e.to_string()))?; + let removed = self.set_root(slot, finalized)?; // TODO: this should not purge slots that on the best chain removed.iter().for_each(|bank| { @@ -37,8 +30,30 @@ impl RollupStorage { .purge_and_compact_slots(bank.slot(), bank.slot()); }); + let bank_forks = self.bank_forks.read().unwrap(); self.bank = bank_forks.working_bank(); Ok(removed) } + + pub fn set_root( + &mut self, + slot: u64, + finalized: Option, + ) -> Result> { + self.blockstore + .set_roots(std::iter::once(&slot)) + .map_err(|e| BankError::SetRootFailed(e.to_string()))?; + let removed_banks = self + .bank_forks + .write() + .unwrap() + .set_root( + slot, + &self.background_service.accounts_background_request_sender, + finalized, + ) + .map_err(|e| BankError::SetRootFailed(e.to_string()))?; + Ok(removed_banks) + } } diff --git a/storage/src/tests/basic.rs b/storage/src/tests/basic.rs index 3b21aae..57af1ba 100644 --- a/storage/src/tests/basic.rs +++ b/storage/src/tests/basic.rs @@ -109,11 +109,7 @@ fn init_with_given_config_works() -> Result<()> { #[tokio::test] async fn storage_basic_process_works() -> Result<()> { let ledger_path = tempfile::tempdir()?.into_path(); - let mut config = GlobalConfig::new_temp(&ledger_path)?; - config - .storage - .snapshot_config - .full_snapshot_archive_interval_slots = 1; // set snapshot interval to 1 + let config = GlobalConfig::new_temp(&ledger_path)?; let mut store = RollupStorage::new(config)?; store.init()?; let keypairs = store.config.keypairs.clone(); @@ -222,26 +218,26 @@ async fn storage_basic_process_works() -> Result<()> { // 4. open again let mut config = GlobalConfig::new(&ledger_path)?; config.keypairs = keypairs; + let ticks_per_slot = config.genesis.ticks_per_slot; let mut store = RollupStorage::new(config)?; store.init()?; let (bank_height, store_height) = store.get_mixed_heights()?; assert_eq!(bank_height, 1); assert_eq!(store_height, Some(1)); + // TODO: check why bob balance is not `ALICE_INIT_BALANCE - TO_CHARLIE` ? assert_eq!( store.balance(&alice.pubkey()), - ALICE_INIT_BALANCE - TO_CHARLIE - FEE + ALICE_INIT_BALANCE - TO_CHARLIE ); // TODO: check why bob balance is not `BOB_INIT_BALANCE - TO_DAVE - FEE` ? assert_eq!(store.balance(&bob.pubkey()), BOB_INIT_BALANCE - TO_DAVE); assert_eq!(store.balance(&charlie), TO_CHARLIE); assert_eq!(store.balance(&dave), TO_DAVE); - // load snapshot, compare with accounts data - // load entries from blockstore, compare with original transactions let entries = store.blockstore.get_slot_entries(1, 0)?; - assert_eq!(entries.len(), 1); + assert_eq!(entries.len() as u64, ticks_per_slot + 1); assert_eq!(entries[0].transactions.len(), 2); assert_eq!( entries[0].transactions[0],