2019-02-16 03:26:21 -07:00
|
|
|
use crate::bank::{Bank, BankError, Result};
|
|
|
|
use crate::blocktree::Blocktree;
|
|
|
|
use crate::entry::{Entry, EntrySlice};
|
2019-02-16 12:00:35 -07:00
|
|
|
use crate::leader_scheduler::LeaderScheduler;
|
2019-02-16 03:26:21 -07:00
|
|
|
use itertools::Itertools;
|
|
|
|
use solana_sdk::hash::Hash;
|
2019-02-16 12:00:35 -07:00
|
|
|
use std::sync::{Arc, RwLock};
|
2019-02-16 03:26:21 -07:00
|
|
|
|
|
|
|
pub const VERIFY_BLOCK_SIZE: usize = 16;
|
|
|
|
|
|
|
|
/// Process an ordered list of entries, populating a circular buffer "tail"
|
|
|
|
/// as we go.
|
2019-02-16 12:00:35 -07:00
|
|
|
fn process_block(
|
|
|
|
bank: &Bank,
|
|
|
|
entries: &[Entry],
|
|
|
|
leader_scheduler: &Arc<RwLock<LeaderScheduler>>,
|
|
|
|
) -> Result<()> {
|
2019-02-16 03:26:21 -07:00
|
|
|
for entry in entries {
|
|
|
|
bank.process_entry(entry)?;
|
2019-02-16 12:00:35 -07:00
|
|
|
if entry.is_tick() {
|
|
|
|
let mut leader_scheduler = leader_scheduler.write().unwrap();
|
|
|
|
leader_scheduler.update_tick_height(bank.tick_height(), bank);
|
|
|
|
}
|
2019-02-16 03:26:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Starting from the genesis block, append the provided entries to the ledger verifying them
|
|
|
|
/// along the way.
|
2019-02-16 12:00:35 -07:00
|
|
|
fn process_ledger<I>(
|
|
|
|
bank: &Bank,
|
|
|
|
entries: I,
|
|
|
|
leader_scheduler: &Arc<RwLock<LeaderScheduler>>,
|
|
|
|
) -> Result<(u64, Hash)>
|
2019-02-16 03:26:21 -07:00
|
|
|
where
|
|
|
|
I: IntoIterator<Item = Entry>,
|
|
|
|
{
|
|
|
|
let mut last_entry_id = bank.last_id();
|
|
|
|
let mut entries_iter = entries.into_iter();
|
|
|
|
|
|
|
|
trace!("genesis last_id={}", last_entry_id);
|
|
|
|
|
|
|
|
// The first entry in the ledger is a pseudo-tick used only to ensure the number of ticks
|
|
|
|
// in slot 0 is the same as the number of ticks in all subsequent slots. It is not
|
|
|
|
// registered as a tick and thus cannot be used as a last_id
|
|
|
|
let entry0 = entries_iter
|
|
|
|
.next()
|
|
|
|
.ok_or(BankError::LedgerVerificationFailed)?;
|
|
|
|
if !(entry0.is_tick() && entry0.verify(&last_entry_id)) {
|
|
|
|
warn!("Ledger proof of history failed at entry0");
|
|
|
|
return Err(BankError::LedgerVerificationFailed);
|
|
|
|
}
|
|
|
|
last_entry_id = entry0.id;
|
|
|
|
let mut entry_height = 1;
|
|
|
|
|
|
|
|
// Ledger verification needs to be parallelized, but we can't pull the whole
|
|
|
|
// thing into memory. We therefore chunk it.
|
|
|
|
for block in &entries_iter.chunks(VERIFY_BLOCK_SIZE) {
|
|
|
|
let block: Vec<_> = block.collect();
|
|
|
|
|
|
|
|
if !block.verify(&last_entry_id) {
|
|
|
|
warn!("Ledger proof of history failed at entry: {}", entry_height);
|
|
|
|
return Err(BankError::LedgerVerificationFailed);
|
|
|
|
}
|
|
|
|
|
2019-02-16 12:00:35 -07:00
|
|
|
process_block(bank, &block, leader_scheduler)?;
|
2019-02-16 03:26:21 -07:00
|
|
|
|
|
|
|
last_entry_id = block.last().unwrap().id;
|
|
|
|
entry_height += block.len() as u64;
|
|
|
|
}
|
|
|
|
Ok((entry_height, last_entry_id))
|
|
|
|
}
|
|
|
|
|
2019-02-16 12:00:35 -07:00
|
|
|
pub fn process_blocktree(
|
|
|
|
bank: &Bank,
|
|
|
|
blocktree: &Blocktree,
|
|
|
|
leader_scheduler: &Arc<RwLock<LeaderScheduler>>,
|
|
|
|
) -> Result<(u64, Hash)> {
|
2019-02-16 03:26:21 -07:00
|
|
|
let entries = blocktree.read_ledger().expect("opening ledger");
|
2019-02-16 12:00:35 -07:00
|
|
|
process_ledger(&bank, entries, leader_scheduler)
|
2019-02-16 03:26:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use crate::genesis_block::GenesisBlock;
|
|
|
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
|
|
|
use solana_sdk::system_transaction::SystemTransaction;
|
|
|
|
|
|
|
|
// create a ledger with a tick every `tick_interval` entries and a couple other transactions
|
|
|
|
fn create_sample_block_with_ticks(
|
|
|
|
genesis_block: &GenesisBlock,
|
|
|
|
mint_keypair: &Keypair,
|
|
|
|
num_one_token_transfers: usize,
|
|
|
|
tick_interval: usize,
|
|
|
|
) -> impl Iterator<Item = Entry> {
|
|
|
|
let mut entries = vec![];
|
|
|
|
|
|
|
|
let mut last_id = genesis_block.last_id();
|
|
|
|
|
|
|
|
// Start off the ledger with the psuedo-tick linked to the genesis block
|
|
|
|
// (see entry0 in `process_ledger`)
|
|
|
|
let tick = Entry::new(&genesis_block.last_id(), 0, 1, vec![]);
|
|
|
|
let mut hash = tick.id;
|
|
|
|
entries.push(tick);
|
|
|
|
|
|
|
|
for i in 0..num_one_token_transfers {
|
|
|
|
// Transfer one token from the mint to a random account
|
|
|
|
let keypair = Keypair::new();
|
|
|
|
let tx = SystemTransaction::new_account(mint_keypair, keypair.pubkey(), 1, last_id, 0);
|
|
|
|
let entry = Entry::new(&hash, 0, 1, vec![tx]);
|
|
|
|
hash = entry.id;
|
|
|
|
entries.push(entry);
|
|
|
|
|
|
|
|
// Add a second Transaction that will produce a
|
|
|
|
// ProgramError<0, ResultWithNegativeTokens> error when processed
|
|
|
|
let keypair2 = Keypair::new();
|
|
|
|
let tx = SystemTransaction::new_account(&keypair, keypair2.pubkey(), 42, last_id, 0);
|
|
|
|
let entry = Entry::new(&hash, 0, 1, vec![tx]);
|
|
|
|
hash = entry.id;
|
|
|
|
entries.push(entry);
|
|
|
|
|
|
|
|
if (i + 1) % tick_interval == 0 {
|
|
|
|
let tick = Entry::new(&hash, 0, 1, vec![]);
|
|
|
|
hash = tick.id;
|
|
|
|
last_id = hash;
|
|
|
|
entries.push(tick);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
entries.into_iter()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_sample_ledger(
|
|
|
|
tokens: u64,
|
|
|
|
num_one_token_transfers: usize,
|
|
|
|
) -> (GenesisBlock, Keypair, impl Iterator<Item = Entry>) {
|
|
|
|
let (genesis_block, mint_keypair) = GenesisBlock::new(tokens);
|
|
|
|
let block = create_sample_block_with_ticks(
|
|
|
|
&genesis_block,
|
|
|
|
&mint_keypair,
|
|
|
|
num_one_token_transfers,
|
|
|
|
num_one_token_transfers,
|
|
|
|
);
|
|
|
|
(genesis_block, mint_keypair, block)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_process_ledger_simple() {
|
|
|
|
let (genesis_block, mint_keypair, ledger) = create_sample_ledger(100, 3);
|
|
|
|
let bank = Bank::new(&genesis_block);
|
|
|
|
assert_eq!(bank.tick_height(), 0);
|
|
|
|
assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 100);
|
2019-02-16 12:00:35 -07:00
|
|
|
let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::default()));
|
|
|
|
let (ledger_height, last_id) = process_ledger(&bank, ledger, &leader_scheduler).unwrap();
|
2019-02-16 03:26:21 -07:00
|
|
|
assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 100 - 3);
|
|
|
|
assert_eq!(ledger_height, 8);
|
|
|
|
assert_eq!(bank.tick_height(), 1);
|
|
|
|
assert_eq!(bank.last_id(), last_id);
|
|
|
|
}
|
|
|
|
}
|