diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 296d7747a45997..f7e1d02a07c833 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -33,6 +33,7 @@ use solana_sdk::{ }; use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; use std::{ + collections::HashMap, net::{SocketAddr, UdpSocket}, sync::{Arc, RwLock}, thread::sleep, @@ -311,21 +312,19 @@ impl JsonRpcRequestProcessor { Ok(self.blocktree.get_confirmed_block(slot).ok()) } - // The `get_block_time` method is not fully implemented. It currently returns `slot` * - // DEFAULT_MS_PER_SLOT offset from 0 for all requests, and null for any values that would - // overflow. pub fn get_block_time(&self, slot: Slot) -> Result> { - // This calculation currently assumes that bank.ticks_per_slot and bank.slots_per_year will - // remain unchanged after genesis. If these values will be variable in the future, those - // timing parameters will need to be stored persistently, and this calculation will likely - // need to be moved upstream into blocktree. Also, an explicit commitment level will need - // to be set. + // This calculation currently assumes that bank.slots_per_year will remain unchanged after + // genesis (ie. that this bank's slot_per_year will be applicable to any rooted slot being + // queried). If these values will be variable in the future, those timing parameters will + // need to be stored persistently, and the slot_duration calculation will likely need to be + // moved upstream into blocktree. Also, an explicit commitment level will need to be set. let bank = self.bank(None); let slot_duration = slot_duration_from_slots_per_year(bank.slots_per_year()); + let epoch = bank.epoch_schedule().get_epoch(slot); + let stakes = HashMap::new(); + let stakes = bank.epoch_vote_accounts(epoch).unwrap_or(&stakes); - Ok(self - .blocktree - .get_block_time(slot, slot_duration, &bank.clone())) + Ok(self.blocktree.get_block_time(slot, slot_duration, stakes)) } } @@ -1008,7 +1007,9 @@ pub mod tests { replay_stage::tests::create_test_transactions_and_populate_blocktree, }; use jsonrpc_core::{MetaIoHandler, Output, Response, Value}; - use solana_ledger::get_tmp_ledger_path; + use solana_ledger::{ + blocktree::entries_to_test_shreds, entry::next_entry_mut, get_tmp_ledger_path, + }; use solana_sdk::{ fee_calculator::DEFAULT_BURN_PERCENT, hash::{hash, Hash}, @@ -1044,6 +1045,14 @@ pub mod tests { } fn start_rpc_handler_with_tx(pubkey: &Pubkey) -> RpcHandler { + start_rpc_handler_with_tx_and_blocktree(pubkey, vec![], 0) + } + + fn start_rpc_handler_with_tx_and_blocktree( + pubkey: &Pubkey, + blocktree_roots: Vec, + default_timestamp: i64, + ) -> RpcHandler { let (bank_forks, alice, leader_vote_keypair) = new_bank_forks(); let bank = bank_forks.read().unwrap().working_bank(); @@ -1073,6 +1082,32 @@ pub mod tests { blocktree.clone(), ); + // Add timestamp vote to blocktree + let vote = Vote { + slots: vec![1], + hash: Hash::default(), + timestamp: Some(default_timestamp), + }; + let vote_ix = vote_instruction::vote( + &leader_vote_keypair.pubkey(), + &leader_vote_keypair.pubkey(), + vote, + ); + let vote_tx = Transaction::new_signed_instructions( + &[&leader_vote_keypair], + vec![vote_ix], + Hash::default(), + ); + let shreds = entries_to_test_shreds( + vec![next_entry_mut(&mut Hash::default(), 0, vec![vote_tx])], + 1, + 0, + true, + 0, + ); + blocktree.insert_shreds(shreds, None, false).unwrap(); + blocktree.set_roots(&blocktree_roots).unwrap(); + let leader_pubkey = *bank.collector_id(); let exit = Arc::new(AtomicBool::new(false)); let validator_exit = create_validator_exit(&exit); @@ -1864,27 +1899,29 @@ pub mod tests { #[test] fn test_get_block_time() { let bob_pubkey = Pubkey::new_rand(); - let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&bob_pubkey); + let base_timestamp = 1576183541; + let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx_and_blocktree( + &bob_pubkey, + vec![1, 2, 3, 4, 5, 6, 7], + base_timestamp, + ); let slot_duration = slot_duration_from_slots_per_year(bank.slots_per_year()); - let slot = 100; + let slot = 2; let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#, slot ); let res = io.handle_request_sync(&req, meta.clone()); - let expected = format!( - r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#, - (slot * slot_duration).as_secs() - ); + let expected = format!(r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#, base_timestamp); let expected: Response = serde_json::from_str(&expected).expect("expected response deserialization"); let result: Response = serde_json::from_str(&res.expect("actual response")) .expect("actual response deserialization"); assert_eq!(expected, result); - let slot = 12345; + let slot = 7; let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#, slot @@ -1892,7 +1929,7 @@ pub mod tests { let res = io.handle_request_sync(&req, meta.clone()); let expected = format!( r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#, - (slot * slot_duration).as_secs() + base_timestamp + (5 * slot_duration).as_secs() as i64 ); let expected: Response = serde_json::from_str(&expected).expect("expected response deserialization"); @@ -1900,7 +1937,7 @@ pub mod tests { .expect("actual response deserialization"); assert_eq!(expected, result); - let slot = 123450000000000000u64; + let slot = 12345; let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#, slot diff --git a/ledger/src/blocktree.rs b/ledger/src/blocktree.rs index cb14d55945e324..45ac7fbada71d7 100644 --- a/ledger/src/blocktree.rs +++ b/ledger/src/blocktree.rs @@ -25,8 +25,8 @@ use solana_client::rpc_request::{RpcConfirmedBlock, RpcTransactionStatus}; use solana_measure::measure::Measure; use solana_metrics::{datapoint_debug, datapoint_error}; use solana_rayon_threadlimit::get_thread_count; -use solana_runtime::bank::Bank; use solana_sdk::{ + account::Account, clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, MS_PER_TICK}, genesis_config::GenesisConfig, hash::Hash, @@ -1186,18 +1186,12 @@ impl Blocktree { } } - // The `get_block_time` method is not fully implemented (depends on validator timestamp - // transactions). It currently returns Some(`slot` * DEFAULT_MS_PER_SLOT) offset from 0 for all - // transactions, and None for any values that would overflow any step. pub fn get_block_time( &self, slot: Slot, slot_duration: Duration, - bank: &Bank, + stakes: &HashMap, ) -> Option { - let epoch = bank.epoch_schedule().get_epoch(slot); - let stakes = HashMap::new(); - let stakes = bank.epoch_vote_accounts(epoch).unwrap_or(&stakes); let mut total_stake = 0; let stake_weighted_timestamps_sum: u64 = self .get_timestamp_slots(slot, TIMESTAMP_SLOT_INTERVAL) @@ -4564,6 +4558,35 @@ pub mod tests { expected_timestamps ); assert_eq!(blocktree.get_block_timestamps(2).unwrap(), vec![]); + + // Build epoch vote_accounts HashMap to test stake-weighted block time + blocktree.set_roots(&[3, 8]).unwrap(); + let mut stakes = HashMap::new(); + for (i, keypair) in vote_keypairs.iter().enumerate() { + stakes.insert(keypair.pubkey(), (1 + i as u64, Account::default())); + } + let slot_duration = Duration::from_millis(400); + let block_time_slot_3 = blocktree.get_block_time(3, slot_duration.clone(), &stakes); + + let mut total_stake = 0; + let mut expected_time: u64 = (0..6) + .map(|x| { + if x % 2 == 0 { + total_stake += 1 + x; + (base_timestamp as u64 + x) * (1 + x) + } else { + 0 + } + }) + .sum(); + expected_time /= total_stake; + assert_eq!(block_time_slot_3.unwrap() as u64, expected_time); + assert_eq!( + blocktree + .get_block_time(8, slot_duration.clone(), &stakes) + .unwrap() as u64, + expected_time + 2 // At 400ms block duration, 5 slots == 2sec + ); } #[test]