From 3bc8d7880105d6912a96e2084758ac06323f7a31 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Sun, 17 Nov 2019 20:17:15 -0700 Subject: [PATCH] =?UTF-8?q?Add=20ConfirmedBlock=20struct,=20and=20rework?= =?UTF-8?q?=20Blocktree=20apis=20to=20include=20block=E2=80=A6=20(#7004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add RpcConfirmedBlock struct, and rework Blocktree apis to include blockhash info and dummy tx statuses * Remove unused lifetime --- Cargo.lock | 1 + client/src/rpc_request.rs | 9 ++++ core/src/rpc.rs | 51 ++++++----------------- ledger/Cargo.toml | 1 + ledger/src/blocktree.rs | 85 +++++++++++++++++++++++++++++++------- ledger/src/blocktree_db.rs | 2 +- 6 files changed, 95 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54c8a3564..a8b3f1958 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3573,6 +3573,7 @@ dependencies = [ "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "solana-budget-api 0.21.0", + "solana-client 0.21.0", "solana-genesis-programs 0.21.0", "solana-logger 0.21.0", "solana-measure 0.21.0", diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index 6c8b3495e..313350858 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -3,6 +3,8 @@ use serde_json::{json, Value}; use solana_sdk::{ clock::{Epoch, Slot}, commitment_config::CommitmentConfig, + hash::Hash, + transaction::{Result, Transaction}, }; use std::{error, fmt, io, net::SocketAddr}; @@ -20,6 +22,13 @@ pub struct Response { pub value: T, } +#[derive(Debug, Default, PartialEq, Serialize)] +pub struct RpcConfirmedBlock { + pub previous_blockhash: Hash, + pub blockhash: Hash, + pub transactions: Vec<(Transaction, Result<()>)>, +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct RpcContactInfo { /// Pubkey of the node as a base-58 string diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 24a3c67ae..9c59a8805 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -12,8 +12,8 @@ use bincode::serialize; use jsonrpc_core::{Error, Metadata, Result}; use jsonrpc_derive::rpc; use solana_client::rpc_request::{ - Response, RpcContactInfo, RpcEpochInfo, RpcResponseContext, RpcVersionInfo, RpcVoteAccountInfo, - RpcVoteAccountStatus, + Response, RpcConfirmedBlock, RpcContactInfo, RpcEpochInfo, RpcResponseContext, RpcVersionInfo, + RpcVoteAccountInfo, RpcVoteAccountStatus, }; use solana_drone::drone::request_airdrop_transaction; use solana_ledger::{bank_forks::BankForks, blocktree::Blocktree}; @@ -26,10 +26,9 @@ use solana_sdk::{ fee_calculator::FeeCalculator, hash::Hash, inflation::Inflation, - instruction::InstructionError, pubkey::Pubkey, signature::Signature, - transaction::{self, Transaction, TransactionError}, + transaction::{self, Transaction}, }; use solana_vote_api::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; use std::{ @@ -302,37 +301,13 @@ impl JsonRpcRequestProcessor { } } - // The `get_confirmed_block` method is not fully implemented. It currenlty returns a batch of - // transaction tuples (Transaction, transaction::Result), where the Transaction is a legitimate - // transaction, but the Result is mocked to demonstrate Ok results and TransactionErrors. - pub fn get_confirmed_block( - &self, - slot: Slot, - ) -> Result)>> { - let transactions = self - .blocktree - .get_confirmed_block_transactions(slot) - .unwrap_or_else(|_| vec![]); - Ok(transactions - .iter() - .enumerate() - .map(|(i, transaction)| { - let status = if i % 3 == 0 { - Ok(()) - } else if i % 3 == 1 { - Err(TransactionError::InstructionError( - 0, - InstructionError::InsufficientFunds, - )) - } else { - Err(TransactionError::InstructionError( - 0, - InstructionError::CustomError(3), - )) - }; - (transaction.clone(), status) - }) - .collect()) + // The `get_confirmed_block` method is not fully implemented. It currenlty returns a partially + // complete RpcConfirmedBlock. The `blockhash` and `previous_blockhash` fields are legitimate + // data, while the `transactions` field contains transaction tuples (Transaction, + // transaction::Result), where the Transaction is a legitimate transaction, but the Result is + // always `Ok()`. + pub fn get_confirmed_block(&self, slot: Slot) -> Result> { + Ok(self.blocktree.get_confirmed_block(slot).ok()) } } @@ -537,12 +512,12 @@ pub trait RpcSol { #[rpc(meta, name = "setLogFilter")] fn set_log_filter(&self, _meta: Self::Metadata, filter: String) -> Result<()>; - #[rpc(meta, name = "getConfirmedBlock")] + #[rpc(meta, name = "getRpcConfirmedBlock")] fn get_confirmed_block( &self, meta: Self::Metadata, slot: Slot, - ) -> Result)>>; + ) -> Result>; } pub struct RpcSolImpl; @@ -991,7 +966,7 @@ impl RpcSol for RpcSolImpl { &self, meta: Self::Metadata, slot: Slot, - ) -> Result)>> { + ) -> Result> { meta.request_processor .read() .unwrap() diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 820f8c63a..2a381e793 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -28,6 +28,7 @@ rayon = "1.2.0" reed-solomon-erasure = { package = "solana-reed-solomon-erasure", version = "4.0.1-3", features = ["simd-accel"] } serde = "1.0.102" serde_derive = "1.0.102" +solana-client = { path = "../client", version = "0.21.0" } solana-genesis-programs = { path = "../genesis-programs", version = "0.21.0" } solana-logger = { path = "../logger", version = "0.21.0" } solana-measure = { path = "../measure", version = "0.21.0" } diff --git a/ledger/src/blocktree.rs b/ledger/src/blocktree.rs index d34eb5473..43996d924 100644 --- a/ledger/src/blocktree.rs +++ b/ledger/src/blocktree.rs @@ -22,6 +22,7 @@ use rayon::{ ThreadPool, }; use rocksdb::DBRawIterator; +use solana_client::rpc_request::RpcConfirmedBlock; use solana_measure::measure::Measure; use solana_metrics::{datapoint_debug, datapoint_error}; use solana_rayon_threadlimit::get_thread_count; @@ -31,7 +32,7 @@ use solana_sdk::{ hash::Hash, signature::{Keypair, KeypairUtil}, timing::timestamp, - transaction::Transaction, + transaction::{self, Transaction}, }; use std::{ cell::RefCell, @@ -1123,19 +1124,42 @@ impl Blocktree { } } - pub fn get_confirmed_block_transactions(&self, slot: Slot) -> Result> { + pub fn get_confirmed_block(&self, slot: Slot) -> Result { if self.is_root(slot) { - Ok(self - .get_slot_entries(slot, 0, None)? + let slot_meta_cf = self.db.column::(); + let slot_meta = slot_meta_cf + .get(slot)? + .expect("Rooted slot must exist in SlotMeta"); + + let slot_entries = self.get_slot_entries(slot, 0, None)?; + let slot_transaction_iterator = slot_entries .iter() .cloned() - .flat_map(|entry| entry.transactions) - .collect()) + .flat_map(|entry| entry.transactions); + let parent_slot_entries = self.get_slot_entries(slot_meta.parent_slot, 0, None)?; + + let block = RpcConfirmedBlock { + previous_blockhash: get_last_hash(parent_slot_entries.iter()) + .expect("Rooted parent slot must have blockhash"), + blockhash: get_last_hash(slot_entries.iter()) + .expect("Rooted slot must have blockhash"), + transactions: self.map_transactions_to_statuses(slot_transaction_iterator), + }; + Ok(block) } else { Err(BlocktreeError::SlotNotRooted) } } + // The `map_transactions_to_statuses` method is not fully implemented (depends on persistent + // status store). It currently returns status Ok(()) for all transactions. + fn map_transactions_to_statuses<'a>( + &self, + iterator: impl Iterator + 'a, + ) -> Vec<(Transaction, transaction::Result<()>)> { + iterator.map(|transaction| (transaction, Ok(()))).collect() + } + /// Returns the entry vector for the slot starting with `shred_start_index` pub fn get_slot_entries( &self, @@ -1485,6 +1509,10 @@ fn get_slot_meta_entry<'a>( }) } +fn get_last_hash<'a>(iterator: impl Iterator + 'a) -> Option { + iterator.last().map(|entry| entry.hash) +} + fn is_valid_write_to_slot_0(slot_to_write: u64, parent_slot: Slot, last_root: u64) -> bool { slot_to_write == 0 && last_root == 0 && parent_slot == 0 } @@ -1984,15 +2012,19 @@ fn adjust_ulimit_nofile() { pub mod tests { use super::*; use crate::{ - entry::next_entry_mut, + entry::{next_entry, next_entry_mut}, genesis_utils::{create_genesis_config, GenesisConfigInfo}, shred::{max_ticks_per_n_shreds, DataShredHeader}, }; use itertools::Itertools; use rand::{seq::SliceRandom, thread_rng}; use solana_sdk::{ - hash::Hash, instruction::CompiledInstruction, packet::PACKET_DATA_SIZE, pubkey::Pubkey, - signature::Signature, transaction::TransactionError, + hash::{self, Hash}, + instruction::CompiledInstruction, + packet::PACKET_DATA_SIZE, + pubkey::Pubkey, + signature::Signature, + transaction::TransactionError, }; use std::{iter::FromIterator, time::Duration}; @@ -4054,7 +4086,7 @@ pub mod tests { } #[test] - fn test_get_confirmed_block_transactions() { + fn test_get_confirmed_block() { let slot = 0; let (shreds, entries) = make_slot_entries_with_transactions(slot, 0, 100); @@ -4063,17 +4095,24 @@ pub mod tests { ledger.insert_shreds(shreds, None, false).unwrap(); ledger.set_roots(&[0]).unwrap(); - let transactions = ledger.get_confirmed_block_transactions(0).unwrap(); - assert_eq!(transactions.len(), 100); - let expected_transactions: Vec = entries + let confirmed_block = ledger.get_confirmed_block(0).unwrap(); + assert_eq!(confirmed_block.transactions.len(), 100); + + let expected_transactions: Vec<(Transaction, transaction::Result<()>)> = entries .iter() .cloned() .filter(|entry| !entry.is_tick()) .flat_map(|entry| entry.transactions) + .map(|transaction| (transaction, Ok(()))) .collect(); - assert_eq!(transactions, expected_transactions); - let not_root = ledger.get_confirmed_block_transactions(1); + let mut expected_block = RpcConfirmedBlock::default(); + expected_block.transactions = expected_transactions; + // The blockhash and previous_blockhash of `expected_block` are default only because + // `make_slot_entries_with_transactions` sets all entry hashes to default + assert_eq!(confirmed_block, expected_block); + + let not_root = ledger.get_confirmed_block(1); assert!(not_root.is_err()); drop(ledger); @@ -4134,4 +4173,20 @@ pub mod tests { } Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction"); } + + #[test] + fn test_get_last_hash() { + let mut entries: Vec = vec![]; + let empty_entries_iterator = entries.iter(); + assert!(get_last_hash(empty_entries_iterator).is_none()); + + let mut prev_hash = hash::hash(&[42u8]); + for _ in 0..10 { + let entry = next_entry(&prev_hash, 1, vec![]); + prev_hash = entry.hash; + entries.push(entry); + } + let entries_iterator = entries.iter(); + assert_eq!(get_last_hash(entries_iterator).unwrap(), entries[9].hash); + } } diff --git a/ledger/src/blocktree_db.rs b/ledger/src/blocktree_db.rs index a48d0e5cf..653148a8e 100644 --- a/ledger/src/blocktree_db.rs +++ b/ledger/src/blocktree_db.rs @@ -275,7 +275,7 @@ impl Column for columns::TransactionStatus { key } - fn index<'a>(key: &[u8]) -> (Slot, Signature) { + fn index(key: &[u8]) -> (Slot, Signature) { let slot = BigEndian::read_u64(&key[..8]); let index = Signature::new(&key[8..72]); (slot, index)