synchronization_chain.rs 23 KiB
Newer Older
use std::fmt;
use std::sync::Arc;
use parking_lot::RwLock;
use chain::{Block, RepresentH256};
use db;
use primitives::hash::H256;
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
use hash_queue::{HashQueueChain, HashPosition};
use miner::MemoryPool;

/// Thread-safe reference to `Chain`
pub type ChainRef = Arc<RwLock<Chain>>;

/// Index of 'verifying' queue
const VERIFYING_QUEUE: usize = 0;
/// Index of 'requested' queue
const REQUESTED_QUEUE: usize = 1;
/// Index of 'scheduled' queue
const SCHEDULED_QUEUE: usize = 2;
/// Number of hash queues
const NUMBER_OF_QUEUES: usize = 3;

/// Block synchronization state
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BlockState {
	/// Block is unknown
	Unknown,
	/// Scheduled for requesting
	Scheduled,
	/// Requested from peers
	Requested,
	/// Currently verifying
	Verifying,
	/// In storage
	Stored,
}

/// Synchronization chain information
#[derive(Debug)]
pub struct Information {
	/// Number of blocks currently scheduled for requesting
	pub scheduled: u32,
	/// Number of blocks currently requested from peers
	pub requested: u32,
	/// Number of blocks currently verifying
	pub verifying: u32,
	/// Number of blocks in the storage
}

/// Blockchain from synchroniation point of view, consisting of:
/// 1) all blocks from the `storage` [oldest blocks]
/// 2) all blocks currently verifying by `verification_queue`
/// 3) all blocks currently requested from peers
/// 4) all blocks currently scheduled for requesting [newest blocks]
pub struct Chain {
	/// Genesis block hash (stored for optimizations)
	genesis_block_hash: H256,
	/// Best storage block (stored for optimizations)
	best_storage_block: db::BestBlock,
	/// Local blocks storage
	storage: Arc<db::Store>,
	/// In-memory queue of blocks hashes
	hash_chain: HashQueueChain,
	/// Transactions memory pool
	memory_pool: MemoryPool,
}

impl BlockState {
	pub fn from_queue_index(queue_index: usize) -> BlockState {
		match queue_index {
			SCHEDULED_QUEUE => BlockState::Scheduled,
			REQUESTED_QUEUE => BlockState::Requested,
			VERIFYING_QUEUE => BlockState::Verifying,
			_ => panic!("Unsupported queue_index: {}", queue_index),
		}
	}

	pub fn to_queue_index(&self) -> usize {
		match self {
			&BlockState::Scheduled => SCHEDULED_QUEUE,
			&BlockState::Requested => REQUESTED_QUEUE,
			&BlockState::Verifying => VERIFYING_QUEUE,
			_ => panic!("Unsupported queue: {:?}", self),
		}
	}
}

impl Chain {
	/// Create new `Chain` with given storage
	pub fn new(storage: Arc<db::Store>) -> Self {
		// we only work with storages with genesis block
		let genesis_block_hash = storage.block_hash(0)
			.expect("storage with genesis block is required");
		let best_storage_block = storage.best_block()
			.expect("non-empty storage is required");

		Chain {
			genesis_block_hash: genesis_block_hash,
			best_storage_block: best_storage_block,
			storage: storage,
			hash_chain: HashQueueChain::with_number_of_queues(NUMBER_OF_QUEUES),
			memory_pool: MemoryPool::new(),
		}
	}

	/// Get information on current blockchain state
	pub fn information(&self) -> Information {
		Information {
			scheduled: self.hash_chain.len_of(SCHEDULED_QUEUE),
			requested: self.hash_chain.len_of(REQUESTED_QUEUE),
			verifying: self.hash_chain.len_of(VERIFYING_QUEUE),
			stored: self.storage.best_block().map_or(0, |block| block.number + 1),
	/// Get storage
	pub fn storage(&self) -> Arc<db::Store> {
		self.storage.clone()
	}

	/// Get memory pool reference
	pub fn memory_pool<'a>(&'a self) -> &'a MemoryPool {
		&self.memory_pool
	}

	/// Get mutable memory pool reference
	#[cfg(test)]
	pub fn memory_pool_mut<'a>(&'a mut self) -> &'a mut MemoryPool {
		&mut self.memory_pool
	}

	/// Get number of blocks in given state
	pub fn length_of_state(&self, state: BlockState) -> u32 {
		match state {
			BlockState::Stored => self.best_storage_block.number + 1,
			_ => self.hash_chain.len_of(state.to_queue_index()),
		}
	}

	/// Returns true if has blocks of given type
	pub fn has_blocks_of_state(&self, state: BlockState) -> bool {
		match state {
			BlockState::Stored => true, // storage with genesis block is required
			_ => !self.hash_chain.is_empty_at(state.to_queue_index()),
		}
	pub fn best_block(&self) -> db::BestBlock {
		let storage_best_block = self.storage.best_block().expect("storage with genesis block is required");
		match self.hash_chain.back() {
			Some(hash) => db::BestBlock {
				number: storage_best_block.number + self.hash_chain.len(),
			None => db::BestBlock {
				number: storage_best_block.number,
				hash: storage_best_block.hash,
	/// Get best block of given state
	pub fn best_block_of_state(&self, state: BlockState) -> Option<db::BestBlock> {
		match state {
			BlockState::Scheduled => self.hash_chain.back_at(SCHEDULED_QUEUE)
				.map(|hash| db::BestBlock {
					hash: hash,
					number: self.storage.best_block().expect("storage with genesis block is required").number + 1
						+ self.hash_chain.len_of(VERIFYING_QUEUE)
						+ self.hash_chain.len_of(REQUESTED_QUEUE)
						+ self.hash_chain.len_of(SCHEDULED_QUEUE)
				}),
			BlockState::Requested => self.hash_chain.back_at(REQUESTED_QUEUE)
				.map(|hash| db::BestBlock {
					hash: hash,
					number: self.storage.best_block().expect("storage with genesis block is required").number + 1
						+ self.hash_chain.len_of(VERIFYING_QUEUE)
						+ self.hash_chain.len_of(REQUESTED_QUEUE)
				}),
			BlockState::Verifying => self.hash_chain.back_at(VERIFYING_QUEUE)
				.map(|hash| db::BestBlock {
					hash: hash,
					number: self.storage.best_block().expect("storage with genesis block is required").number + 1
						+ self.hash_chain.len_of(VERIFYING_QUEUE)
				}),
			BlockState::Stored => {
					self.storage.best_block()
				},
			_ => panic!("not supported"),
		}
	}

	/// Check if block has given state
	pub fn block_has_state(&self, hash: &H256, state: BlockState) -> bool {
		match state {
			BlockState::Scheduled => self.hash_chain.is_contained_in(SCHEDULED_QUEUE, hash),
			BlockState::Requested => self.hash_chain.is_contained_in(REQUESTED_QUEUE, hash),
			BlockState::Verifying => self.hash_chain.is_contained_in(VERIFYING_QUEUE, hash),
			BlockState::Stored => self.storage.contains_block(db::BlockRef::Hash(hash.clone())),
			BlockState::Unknown => self.block_state(hash) == BlockState::Unknown,
		}
	}

	/// Get block state
	pub fn block_state(&self, hash: &H256) -> BlockState {
		match self.hash_chain.contains_in(hash) {
			Some(queue_index) => BlockState::from_queue_index(queue_index),
			None => if self.storage.contains_block(db::BlockRef::Hash(hash.clone())) {
				BlockState::Stored
			} else {
				BlockState::Unknown
			},
		}
	}

	/// Get block number from storage
	pub fn storage_block_number(&self, hash: &H256) -> Option<u32> {
		self.storage.block_number(hash)
	}

	/// Get block hash from storage
	pub fn storage_block_hash(&self, number: u32) -> Option<H256> {
		self.storage.block_hash(number)
	}

	/// Get block from the storage
	pub fn storage_block(&self, hash: &H256) -> Option<Block> {
		self.storage.block(db::BlockRef::Hash(hash.clone()))
	}

	/// Prepare best block locator hashes
	pub fn best_block_locator_hashes(&self) -> Vec<H256> {
		let mut result: Vec<H256> = Vec::with_capacity(4);
		if let Some(pre_best_block) = self.hash_chain.back_skip_n_at(SCHEDULED_QUEUE, 2) {
			result.push(pre_best_block);
		if let Some(pre_best_block) = self.hash_chain.back_skip_n_at(REQUESTED_QUEUE, 2) {
			result.push(pre_best_block);
		if let Some(pre_best_block) = self.hash_chain.back_skip_n_at(VERIFYING_QUEUE, 2) {
			result.push(pre_best_block);
		result.push(self.best_storage_block.hash.clone());
	}

	/// Prepare block locator hashes, as described in protocol documentation:
	/// https://en.bitcoin.it/wiki/Protocol_documentation#getblocks
	pub fn block_locator_hashes(&self) -> Vec<H256> {
		let mut block_locator_hashes: Vec<H256> = Vec::new();

		// calculate for hash_queue
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		let (local_index, step) = self.block_locator_hashes_for_queue(&mut block_locator_hashes);

		// calculate for storage
		let storage_best_block_number = self.storage.best_block().expect("storage with genesis block is required").number;
		let storage_index = if storage_best_block_number < local_index { 0 } else { storage_best_block_number - local_index };
		self.block_locator_hashes_for_storage(storage_index, step, &mut block_locator_hashes);
		block_locator_hashes
	}

	/// Schedule blocks for requesting
	pub fn schedule_blocks_hashes(&mut self, hashes: Vec<H256>) {
		self.hash_chain.push_back_n_at(SCHEDULED_QUEUE, hashes)
	}

Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
	/// Moves n blocks from scheduled queue to requested queue
	pub fn request_blocks_hashes(&mut self, n: u32) -> Vec<H256> {
		let scheduled = self.hash_chain.pop_front_n_at(SCHEDULED_QUEUE, n);
		self.hash_chain.push_back_n_at(REQUESTED_QUEUE, scheduled.clone());
		scheduled
	}

	/// Add block to verifying queue
	pub fn verify_block_hash(&mut self, hash: H256) {
		self.hash_chain.push_back_at(VERIFYING_QUEUE, hash);
	}

Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
	/// Moves n blocks from requested queue to verifying queue
	#[cfg(test)]
	pub fn verify_blocks_hashes(&mut self, n: u32) -> Vec<H256> {
		let requested = self.hash_chain.pop_front_n_at(REQUESTED_QUEUE, n);
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		self.hash_chain.push_back_n_at(VERIFYING_QUEUE, requested.clone());
		requested
	}

	/// Insert new best block to storage
	pub fn insert_best_block(&mut self, block: Block) -> Result<(), db::Error> {
		if block.block_header.previous_header_hash != self.best_storage_block.hash {
			return Err(db::Error::DB("Trying to insert out-of-order block".into()));
		// remember new best block hash
		self.best_storage_block.number += 1;
		self.best_storage_block.hash = block.hash();

		// insert to storage
		self.storage.insert_block(&block)
	}

	/// Remove block by hash if it is currently in given state
	pub fn remove_block_with_state(&mut self, hash: &H256, state: BlockState) -> HashPosition {
		self.hash_chain.remove_at(state.to_queue_index(), hash)
	}

	/// Remove all blocks with given state
	pub fn remove_blocks_with_state(&mut self, state: BlockState) {
		self.hash_chain.remove_all_at(state.to_queue_index());
	}

Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
	/// Calculate block locator hashes for hash queue
	fn block_locator_hashes_for_queue(&self, hashes: &mut Vec<H256>) -> (u32, u32) {
		let queue_len = self.hash_chain.len();
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
			return (0, 1);
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		let mut index = queue_len - 1;
		let mut step = 1u32;
			let block_hash = self.hash_chain[index].clone();
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
			hashes.push(block_hash);

			if hashes.len() >= 10 {
				step <<= 1;
			}
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
			if index < step {
				return (step - index - 1, step);
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
			index -= step;
		}
	}

	/// Calculate block locator hashes for storage
	fn block_locator_hashes_for_storage(&self, mut index: u32, mut step: u32, hashes: &mut Vec<H256>) {
			let block_hash = self.storage.block_hash(index)
				.expect("private function; index calculated in `block_locator_hashes`; qed");
			hashes.push(block_hash);

			if hashes.len() >= 10 {
				step <<= 1;
			}
			if index < step {
				// always include genesis hash
				if index != 0 {
					hashes.push(self.genesis_block_hash.clone())
				}

				break;
			}
			index -= step;
		}
	}
}
impl fmt::Debug for Chain {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		try!(writeln!(f, "chain: ["));
			let mut num = self.storage.best_block().expect("Storage with genesis block is required").number;
			try!(writeln!(f, "\tworse(stored): {} {:?}", 0, self.storage.block_hash(0)));
			try!(writeln!(f, "\tbest(stored): {} {:?}", num, self.storage.block_hash(num)));

			let queues = vec![
				("verifying", VERIFYING_QUEUE),
				("requested", REQUESTED_QUEUE),
				("scheduled", SCHEDULED_QUEUE),
			];
			for (state, queue) in queues {
				let queue_len = self.hash_chain.len_of(queue);
				if queue_len != 0 {
					try!(writeln!(f, "\tworse({}): {} {:?}", state, num + 1, self.hash_chain.front_at(queue)));
					num += queue_len;
					if let Some(pre_best) = self.hash_chain.pre_back_at(queue) {
						try!(writeln!(f, "\tpre-best({}): {} {:?}", state, num - 1, pre_best));
					}
					try!(writeln!(f, "\tbest({}): {} {:?}", state, num, self.hash_chain.back_at(queue)));
				}
			}
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
#[cfg(test)]
mod tests {
	use std::sync::Arc;
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
	use chain::RepresentH256;
	use hash_queue::HashPosition;
	use super::{Chain, BlockState};
	use db::{self, Store, BestBlock};
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
	use test_data;

	#[test]
	fn chain_empty() {
		let db = Arc::new(db::TestStorage::with_genesis_block());
		let db_best_block = BestBlock { number: 0, hash: db.best_block().expect("storage with genesis block is required").hash };
		let chain = Chain::new(db.clone());
		assert_eq!(chain.information().scheduled, 0);
		assert_eq!(chain.information().requested, 0);
		assert_eq!(chain.information().verifying, 0);
		assert_eq!(chain.information().stored, 1);
		assert_eq!(chain.length_of_state(BlockState::Scheduled), 0);
		assert_eq!(chain.length_of_state(BlockState::Requested), 0);
		assert_eq!(chain.length_of_state(BlockState::Verifying), 0);
		assert_eq!(chain.length_of_state(BlockState::Stored), 1);
		assert_eq!(chain.has_blocks_of_state(BlockState::Scheduled), false);
		assert_eq!(chain.has_blocks_of_state(BlockState::Requested), false);
		assert_eq!(chain.has_blocks_of_state(BlockState::Verifying), false);
		assert_eq!(chain.has_blocks_of_state(BlockState::Stored), true);
		assert_eq!(&chain.best_block(), &db_best_block);
		assert_eq!(chain.best_block_of_state(BlockState::Scheduled), None);
		assert_eq!(chain.best_block_of_state(BlockState::Requested), None);
		assert_eq!(chain.best_block_of_state(BlockState::Verifying), None);
		assert_eq!(chain.best_block_of_state(BlockState::Stored), Some(db_best_block.clone()));
		assert_eq!(chain.block_has_state(&db_best_block.hash, BlockState::Requested), false);
		assert_eq!(chain.block_has_state(&db_best_block.hash, BlockState::Stored), true);
		assert_eq!(chain.block_state(&db_best_block.hash), BlockState::Stored);
		assert_eq!(chain.block_state(&"0000000000000000000000000000000000000000000000000000000000000000".into()), BlockState::Unknown);
	}

	#[test]
	fn chain_block_path() {
		let db = Arc::new(db::TestStorage::with_genesis_block());
		let mut chain = Chain::new(db.clone());

		// add 6 blocks to scheduled queue
		chain.schedule_blocks_hashes(vec![
			"0000000000000000000000000000000000000000000000000000000000000000".into(),
			"0000000000000000000000000000000000000000000000000000000000000001".into(),
			"0000000000000000000000000000000000000000000000000000000000000002".into(),
			"0000000000000000000000000000000000000000000000000000000000000003".into(),
			"0000000000000000000000000000000000000000000000000000000000000004".into(),
			"0000000000000000000000000000000000000000000000000000000000000005".into(),
		]);
		assert!(chain.information().scheduled == 6 && chain.information().requested == 0
			&& chain.information().verifying == 0 && chain.information().stored == 1);

		// move 2 best blocks (0 && 1) to requested queue
		chain.request_blocks_hashes(2);
		assert!(chain.information().scheduled == 4 && chain.information().requested == 2
			&& chain.information().verifying == 0 && chain.information().stored == 1);
		// move 0 best blocks to requested queue
		chain.request_blocks_hashes(0);
		assert!(chain.information().scheduled == 4 && chain.information().requested == 2
			&& chain.information().verifying == 0 && chain.information().stored == 1);
		// move 1 best blocks (2) to requested queue
		chain.request_blocks_hashes(1);
		assert!(chain.information().scheduled == 3 && chain.information().requested == 3
			&& chain.information().verifying == 0 && chain.information().stored == 1);

		// try to remove block 0 from scheduled queue => missing
		assert_eq!(chain.remove_block_with_state(&"0000000000000000000000000000000000000000000000000000000000000000".into(), BlockState::Scheduled), HashPosition::Missing);
		assert!(chain.information().scheduled == 3 && chain.information().requested == 3
			&& chain.information().verifying == 0 && chain.information().stored == 1);
		// remove blocks 0 & 1 from requested queue
		assert_eq!(chain.remove_block_with_state(&"0000000000000000000000000000000000000000000000000000000000000001".into(), BlockState::Requested), HashPosition::Inside);
		assert_eq!(chain.remove_block_with_state(&"0000000000000000000000000000000000000000000000000000000000000000".into(), BlockState::Requested), HashPosition::Front);
		assert!(chain.information().scheduled == 3 && chain.information().requested == 1
			&& chain.information().verifying == 0 && chain.information().stored == 1);
		// mark 0 & 1 as verifying
		chain.verify_block_hash("0000000000000000000000000000000000000000000000000000000000000001".into());
		chain.verify_block_hash("0000000000000000000000000000000000000000000000000000000000000002".into());
		assert!(chain.information().scheduled == 3 && chain.information().requested == 1
			&& chain.information().verifying == 2 && chain.information().stored == 1);

		// mark block 0 as verified
		assert_eq!(chain.remove_block_with_state(&"0000000000000000000000000000000000000000000000000000000000000001".into(), BlockState::Verifying), HashPosition::Front);
		assert!(chain.information().scheduled == 3 && chain.information().requested == 1
			&& chain.information().verifying == 1 && chain.information().stored == 1);
		// insert new best block to the chain
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		chain.insert_best_block(test_data::block_h1()).expect("Db error");
		assert!(chain.information().scheduled == 3 && chain.information().requested == 1
			&& chain.information().verifying == 1 && chain.information().stored == 2);
		assert_eq!(db.best_block().expect("storage with genesis block is required").number, 1);
	}
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed

	#[test]
	fn chain_block_locator_hashes() {
		let mut chain = Chain::new(Arc::new(db::TestStorage::with_genesis_block()));
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		let genesis_hash = chain.best_block().hash;
		assert_eq!(chain.block_locator_hashes(), vec![genesis_hash.clone()]);

Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		let block1 = test_data::block_h1();
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		let block1_hash = block1.hash();

		chain.insert_best_block(block1).expect("Error inserting new block");
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		assert_eq!(chain.block_locator_hashes(), vec![block1_hash.clone(), genesis_hash.clone()]);

Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		let block2 = test_data::block_h2();
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		let block2_hash = block2.hash();

		chain.insert_best_block(block2).expect("Error inserting new block");
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		assert_eq!(chain.block_locator_hashes(), vec![block2_hash.clone(), block1_hash.clone(), genesis_hash.clone()]);

		chain.schedule_blocks_hashes(vec![
			"0000000000000000000000000000000000000000000000000000000000000000".into(),
			"0000000000000000000000000000000000000000000000000000000000000001".into(),
			"0000000000000000000000000000000000000000000000000000000000000002".into(),
			"0000000000000000000000000000000000000000000000000000000000000003".into(),
			"0000000000000000000000000000000000000000000000000000000000000004".into(),
			"0000000000000000000000000000000000000000000000000000000000000005".into(),
			"0000000000000000000000000000000000000000000000000000000000000006".into(),
			"0000000000000000000000000000000000000000000000000000000000000007".into(),
			"0000000000000000000000000000000000000000000000000000000000000008".into(),
			"0000000000000000000000000000000000000000000000000000000000000009".into(),
			"0000000000000000000000000000000000000000000000000000000000000010".into(),
		]);
		chain.request_blocks_hashes(10);
		chain.verify_blocks_hashes(10);

		assert_eq!(chain.best_block_locator_hashes()[0], "0000000000000000000000000000000000000000000000000000000000000010".into());
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		assert_eq!(chain.block_locator_hashes(), vec![
			"0000000000000000000000000000000000000000000000000000000000000010".into(),
			"0000000000000000000000000000000000000000000000000000000000000009".into(),
			"0000000000000000000000000000000000000000000000000000000000000008".into(),
			"0000000000000000000000000000000000000000000000000000000000000007".into(),
			"0000000000000000000000000000000000000000000000000000000000000006".into(),
			"0000000000000000000000000000000000000000000000000000000000000005".into(),
			"0000000000000000000000000000000000000000000000000000000000000004".into(),
			"0000000000000000000000000000000000000000000000000000000000000003".into(),
			"0000000000000000000000000000000000000000000000000000000000000002".into(),
			"0000000000000000000000000000000000000000000000000000000000000001".into(),
			block2_hash.clone(),
			genesis_hash.clone(),
		]);

		chain.schedule_blocks_hashes(vec![
			"0000000000000000000000000000000000000000000000000000000000000011".into(),
			"0000000000000000000000000000000000000000000000000000000000000012".into(),
			"0000000000000000000000000000000000000000000000000000000000000013".into(),
			"0000000000000000000000000000000000000000000000000000000000000014".into(),
			"0000000000000000000000000000000000000000000000000000000000000015".into(),
			"0000000000000000000000000000000000000000000000000000000000000016".into(),
		]);
		chain.request_blocks_hashes(10);

Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		assert_eq!(chain.best_block_locator_hashes()[0], "0000000000000000000000000000000000000000000000000000000000000014".into());
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		assert_eq!(chain.block_locator_hashes(), vec![
			"0000000000000000000000000000000000000000000000000000000000000016".into(),
			"0000000000000000000000000000000000000000000000000000000000000015".into(),
			"0000000000000000000000000000000000000000000000000000000000000014".into(),
			"0000000000000000000000000000000000000000000000000000000000000013".into(),
			"0000000000000000000000000000000000000000000000000000000000000012".into(),
			"0000000000000000000000000000000000000000000000000000000000000011".into(),
			"0000000000000000000000000000000000000000000000000000000000000010".into(),
			"0000000000000000000000000000000000000000000000000000000000000009".into(),
			"0000000000000000000000000000000000000000000000000000000000000008".into(),
			"0000000000000000000000000000000000000000000000000000000000000007".into(),
			"0000000000000000000000000000000000000000000000000000000000000005".into(),
			"0000000000000000000000000000000000000000000000000000000000000001".into(),
			genesis_hash.clone(),
		]);

		chain.schedule_blocks_hashes(vec![
			"0000000000000000000000000000000000000000000000000000000000000020".into(),
			"0000000000000000000000000000000000000000000000000000000000000021".into(),
			"0000000000000000000000000000000000000000000000000000000000000022".into(),
		]);

Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		assert_eq!(chain.best_block_locator_hashes()[0], "0000000000000000000000000000000000000000000000000000000000000020".into());
Svyatoslav Nikolsky's avatar
Svyatoslav Nikolsky committed
		assert_eq!(chain.block_locator_hashes(), vec![
			"0000000000000000000000000000000000000000000000000000000000000022".into(),
			"0000000000000000000000000000000000000000000000000000000000000021".into(),
			"0000000000000000000000000000000000000000000000000000000000000020".into(),
			"0000000000000000000000000000000000000000000000000000000000000016".into(),
			"0000000000000000000000000000000000000000000000000000000000000015".into(),
			"0000000000000000000000000000000000000000000000000000000000000014".into(),
			"0000000000000000000000000000000000000000000000000000000000000013".into(),
			"0000000000000000000000000000000000000000000000000000000000000012".into(),
			"0000000000000000000000000000000000000000000000000000000000000011".into(),
			"0000000000000000000000000000000000000000000000000000000000000010".into(),
			"0000000000000000000000000000000000000000000000000000000000000008".into(),
			"0000000000000000000000000000000000000000000000000000000000000004".into(),
			genesis_hash.clone(),
		]);
	}
}