getConfirmedBlock: add encoding optional parameter (#7756)

automerge
This commit is contained in:
Tyera Eulberg 2020-01-12 22:34:30 -07:00 committed by Grimes
parent ad4d41e602
commit a17d5795fb
6 changed files with 244 additions and 74 deletions

View File

@ -278,16 +278,17 @@ Returns identity and transaction information about a confirmed block in the ledg
#### Parameters: #### Parameters:
* `integer` - slot, as u64 integer * `integer` - slot, as u64 integer
* `string` - (optional) encoding for each returned Transaction, either "json" or "binary". If not provided, the default encoding is JSON.
#### Results: #### Results:
The result field will be an object with the following fields: The result field will be an object with the following fields:
* `blockhash` - the blockhash of this block * `blockhash` - the blockhash of this block, as base-58 encoded string
* `previousBlockhash` - the blockhash of this block's parent * `previousBlockhash` - the blockhash of this block's parent, as base-58 encoded string
* `parentSlot` - the slot index of this block's parent * `parentSlot` - the slot index of this block's parent
* `transactions` - an array of tuples containing: * `transactions` - an array of tuples containing:
* [Transaction](transaction-api.md) object, in JSON format * [Transaction](transaction-api.md) object, either in JSON format or base-58 encoded binary data, depending on encoding parameter
* Transaction status object, containing: * Transaction status object, containing:
* `status` - Transaction status: * `status` - Transaction status:
* `"Ok": null` - Transaction was successful * `"Ok": null` - Transaction was successful

View File

@ -1,8 +1,9 @@
use bincode::serialize;
use jsonrpc_core::Result as JsonResult; use jsonrpc_core::Result as JsonResult;
use serde_json::{json, Value}; use serde_json::{json, Value};
use solana_sdk::{ use solana_sdk::{
clock::{Epoch, Slot}, clock::{Epoch, Slot},
hash::Hash, message::MessageHeader,
transaction::{Result, Transaction}, transaction::{Result, Transaction},
}; };
use std::{collections::HashMap, error, fmt, io, net::SocketAddr}; use std::{collections::HashMap, error, fmt, io, net::SocketAddr};
@ -21,13 +22,92 @@ pub struct Response<T> {
pub value: T, pub value: T,
} }
#[derive(Debug, Default, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcConfirmedBlock { pub struct RpcConfirmedBlock {
pub previous_blockhash: Hash, pub previous_blockhash: String,
pub blockhash: Hash, pub blockhash: String,
pub parent_slot: Slot, pub parent_slot: Slot,
pub transactions: Vec<(Transaction, Option<RpcTransactionStatus>)>, pub transactions: Vec<(RpcEncodedTransaction, Option<RpcTransactionStatus>)>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum RpcTransactionEncoding {
Binary,
Json,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum RpcEncodedTransaction {
Binary(String),
Json(RpcTransaction),
}
impl RpcEncodedTransaction {
pub fn encode(transaction: Transaction, encoding: RpcTransactionEncoding) -> Self {
if encoding == RpcTransactionEncoding::Json {
RpcEncodedTransaction::Json(RpcTransaction {
signatures: transaction
.signatures
.iter()
.map(|sig| sig.to_string())
.collect(),
message: RpcMessage {
header: transaction.message.header,
account_keys: transaction
.message
.account_keys
.iter()
.map(|pubkey| pubkey.to_string())
.collect(),
recent_blockhash: transaction.message.recent_blockhash.to_string(),
instructions: transaction
.message
.instructions
.iter()
.map(|instruction| RpcCompiledInstruction {
program_id_index: instruction.program_id_index,
accounts: instruction.accounts.clone(),
data: bs58::encode(instruction.data.clone()).into_string(),
})
.collect(),
},
})
} else {
RpcEncodedTransaction::Binary(
bs58::encode(serialize(&transaction).unwrap()).into_string(),
)
}
}
}
/// A duplicate representation of a Transaction for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransaction {
pub signatures: Vec<String>,
pub message: RpcMessage,
}
/// A duplicate representation of a Message for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcMessage {
pub header: MessageHeader,
pub account_keys: Vec<String>,
pub recent_blockhash: String,
pub instructions: Vec<RpcCompiledInstruction>,
}
/// A duplicate representation of a Message for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcCompiledInstruction {
pub program_id_index: u8,
pub accounts: Vec<u8>,
pub data: String,
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]

View File

@ -1020,6 +1020,7 @@ mod tests {
}; };
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use itertools::Itertools; use itertools::Itertools;
use solana_client::rpc_request::RpcEncodedTransaction;
use solana_ledger::{ use solana_ledger::{
blocktree::entries_to_test_shreds, blocktree::entries_to_test_shreds,
entry::{next_entry, Entry, EntrySlice}, entry::{next_entry, Entry, EntrySlice},
@ -1971,22 +1972,24 @@ mod tests {
transaction_status_service.join().unwrap(); transaction_status_service.join().unwrap();
let confirmed_block = blocktree.get_confirmed_block(bank.slot()).unwrap(); let confirmed_block = blocktree.get_confirmed_block(bank.slot(), None).unwrap();
assert_eq!(confirmed_block.transactions.len(), 3); assert_eq!(confirmed_block.transactions.len(), 3);
for (transaction, result) in confirmed_block.transactions.into_iter() { for (transaction, result) in confirmed_block.transactions.into_iter() {
if transaction.signatures[0] == success_signature { if let RpcEncodedTransaction::Json(transaction) = transaction {
assert_eq!(result.unwrap().status, Ok(())); if transaction.signatures[0] == success_signature.to_string() {
} else if transaction.signatures[0] == ix_error_signature { assert_eq!(result.unwrap().status, Ok(()));
assert_eq!( } else if transaction.signatures[0] == ix_error_signature.to_string() {
result.unwrap().status, assert_eq!(
Err(TransactionError::InstructionError( result.unwrap().status,
0, Err(TransactionError::InstructionError(
InstructionError::CustomError(1) 0,
)) InstructionError::CustomError(1)
); ))
} else { );
assert_eq!(result, None); } else {
assert_eq!(result, None);
}
} }
} }
} }

View File

@ -1186,6 +1186,7 @@ pub(crate) mod tests {
transaction_status_service::TransactionStatusService, transaction_status_service::TransactionStatusService,
}; };
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use solana_client::rpc_request::RpcEncodedTransaction;
use solana_ledger::{ use solana_ledger::{
blocktree::make_slot_entries, blocktree::make_slot_entries,
blocktree::{entries_to_test_shreds, BlocktreeError}, blocktree::{entries_to_test_shreds, BlocktreeError},
@ -1988,22 +1989,24 @@ pub(crate) mod tests {
blocktree.clone(), blocktree.clone(),
); );
let confirmed_block = blocktree.get_confirmed_block(slot).unwrap(); let confirmed_block = blocktree.get_confirmed_block(slot, None).unwrap();
assert_eq!(confirmed_block.transactions.len(), 3); assert_eq!(confirmed_block.transactions.len(), 3);
for (transaction, result) in confirmed_block.transactions.into_iter() { for (transaction, result) in confirmed_block.transactions.into_iter() {
if transaction.signatures[0] == signatures[0] { if let RpcEncodedTransaction::Json(transaction) = transaction {
assert_eq!(result.unwrap().status, Ok(())); if transaction.signatures[0] == signatures[0].to_string() {
} else if transaction.signatures[0] == signatures[1] { assert_eq!(result.unwrap().status, Ok(()));
assert_eq!( } else if transaction.signatures[0] == signatures[1].to_string() {
result.unwrap().status, assert_eq!(
Err(TransactionError::InstructionError( result.unwrap().status,
0, Err(TransactionError::InstructionError(
InstructionError::CustomError(1) 0,
)) InstructionError::CustomError(1)
); ))
} else { );
assert_eq!(result, None); } else {
assert_eq!(result, None);
}
} }
} }
} }

View File

@ -13,7 +13,8 @@ use jsonrpc_core::{Error, Metadata, Result};
use jsonrpc_derive::rpc; use jsonrpc_derive::rpc;
use solana_client::rpc_request::{ use solana_client::rpc_request::{
Response, RpcConfirmedBlock, RpcContactInfo, RpcEpochInfo, RpcLeaderSchedule, Response, RpcConfirmedBlock, RpcContactInfo, RpcEpochInfo, RpcLeaderSchedule,
RpcResponseContext, RpcVersionInfo, RpcVoteAccountInfo, RpcVoteAccountStatus, RpcResponseContext, RpcTransactionEncoding, RpcVersionInfo, RpcVoteAccountInfo,
RpcVoteAccountStatus,
}; };
use solana_faucet::faucet::request_airdrop_transaction; use solana_faucet::faucet::request_airdrop_transaction;
use solana_ledger::{ use solana_ledger::{
@ -312,8 +313,12 @@ impl JsonRpcRequestProcessor {
} }
} }
pub fn get_confirmed_block(&self, slot: Slot) -> Result<Option<RpcConfirmedBlock>> { pub fn get_confirmed_block(
Ok(self.blocktree.get_confirmed_block(slot).ok()) &self,
slot: Slot,
encoding: Option<RpcTransactionEncoding>,
) -> Result<Option<RpcConfirmedBlock>> {
Ok(self.blocktree.get_confirmed_block(slot, encoding).ok())
} }
pub fn get_confirmed_blocks( pub fn get_confirmed_blocks(
@ -562,6 +567,7 @@ pub trait RpcSol {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
slot: Slot, slot: Slot,
encoding: Option<RpcTransactionEncoding>,
) -> Result<Option<RpcConfirmedBlock>>; ) -> Result<Option<RpcConfirmedBlock>>;
#[rpc(meta, name = "getBlockTime")] #[rpc(meta, name = "getBlockTime")]
@ -1031,11 +1037,12 @@ impl RpcSol for RpcSolImpl {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
slot: Slot, slot: Slot,
encoding: Option<RpcTransactionEncoding>,
) -> Result<Option<RpcConfirmedBlock>> { ) -> Result<Option<RpcConfirmedBlock>> {
meta.request_processor meta.request_processor
.read() .read()
.unwrap() .unwrap()
.get_confirmed_block(slot) .get_confirmed_block(slot, encoding)
} }
fn get_confirmed_blocks( fn get_confirmed_blocks(
@ -1063,7 +1070,9 @@ pub mod tests {
genesis_utils::{create_genesis_config, GenesisConfigInfo}, genesis_utils::{create_genesis_config, GenesisConfigInfo},
replay_stage::tests::create_test_transactions_and_populate_blocktree, replay_stage::tests::create_test_transactions_and_populate_blocktree,
}; };
use bincode::deserialize;
use jsonrpc_core::{MetaIoHandler, Output, Response, Value}; use jsonrpc_core::{MetaIoHandler, Output, Response, Value};
use solana_client::rpc_request::RpcEncodedTransaction;
use solana_ledger::{ use solana_ledger::{
blocktree::entries_to_test_shreds, blocktree_processor::fill_blocktree_slot_with_ticks, blocktree::entries_to_test_shreds, blocktree_processor::fill_blocktree_slot_with_ticks,
entry::next_entry_mut, get_tmp_ledger_path, entry::next_entry_mut, get_tmp_ledger_path,
@ -2008,6 +2017,36 @@ pub mod tests {
let req = let req =
format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getConfirmedBlock","params":[0]}}"#); format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getConfirmedBlock","params":[0]}}"#);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let confirmed_block: Option<RpcConfirmedBlock> =
serde_json::from_value(result["result"].clone()).unwrap();
let confirmed_block = confirmed_block.unwrap();
assert_eq!(confirmed_block.transactions.len(), 3);
for (transaction, result) in confirmed_block.transactions.into_iter() {
if let RpcEncodedTransaction::Json(transaction) = transaction {
if transaction.signatures[0] == confirmed_block_signatures[0].to_string() {
assert_eq!(transaction.message.recent_blockhash, blockhash.to_string());
assert_eq!(result.unwrap().status, Ok(()));
} else if transaction.signatures[0] == confirmed_block_signatures[1].to_string() {
assert_eq!(
result.unwrap().status,
Err(TransactionError::InstructionError(
0,
InstructionError::CustomError(1)
))
);
} else {
assert_eq!(result, None);
}
}
}
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getConfirmedBlock","params":[0, "binary"]}}"#
);
let res = io.handle_request_sync(&req, meta); let res = io.handle_request_sync(&req, meta);
let result: Value = serde_json::from_str(&res.expect("actual response")) let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization"); .expect("actual response deserialization");
@ -2017,19 +2056,23 @@ pub mod tests {
assert_eq!(confirmed_block.transactions.len(), 3); assert_eq!(confirmed_block.transactions.len(), 3);
for (transaction, result) in confirmed_block.transactions.into_iter() { for (transaction, result) in confirmed_block.transactions.into_iter() {
if transaction.signatures[0] == confirmed_block_signatures[0] { if let RpcEncodedTransaction::Binary(transaction) = transaction {
assert_eq!(transaction.message.recent_blockhash, blockhash); let decoded_transaction: Transaction =
assert_eq!(result.unwrap().status, Ok(())); deserialize(&bs58::decode(&transaction).into_vec().unwrap()).unwrap();
} else if transaction.signatures[0] == confirmed_block_signatures[1] { if decoded_transaction.signatures[0] == confirmed_block_signatures[0] {
assert_eq!( assert_eq!(decoded_transaction.message.recent_blockhash, blockhash);
result.unwrap().status, assert_eq!(result.unwrap().status, Ok(()));
Err(TransactionError::InstructionError( } else if decoded_transaction.signatures[0] == confirmed_block_signatures[1] {
0, assert_eq!(
InstructionError::CustomError(1) result.unwrap().status,
)) Err(TransactionError::InstructionError(
); 0,
} else { InstructionError::CustomError(1)
assert_eq!(result, None); ))
);
} else {
assert_eq!(result, None);
}
} }
} }
} }

View File

@ -21,7 +21,9 @@ use rayon::{
ThreadPool, ThreadPool,
}; };
use rocksdb::DBRawIterator; use rocksdb::DBRawIterator;
use solana_client::rpc_request::{RpcConfirmedBlock, RpcTransactionStatus}; use solana_client::rpc_request::{
RpcConfirmedBlock, RpcEncodedTransaction, RpcTransactionEncoding, RpcTransactionStatus,
};
use solana_measure::measure::Measure; use solana_measure::measure::Measure;
use solana_metrics::{datapoint_debug, datapoint_error}; use solana_metrics::{datapoint_debug, datapoint_error};
use solana_rayon_threadlimit::get_thread_count; use solana_rayon_threadlimit::get_thread_count;
@ -1324,7 +1326,12 @@ impl Blocktree {
.collect() .collect()
} }
pub fn get_confirmed_block(&self, slot: Slot) -> Result<RpcConfirmedBlock> { pub fn get_confirmed_block(
&self,
slot: Slot,
encoding: Option<RpcTransactionEncoding>,
) -> Result<RpcConfirmedBlock> {
let encoding = encoding.unwrap_or(RpcTransactionEncoding::Json);
if self.is_root(slot) { if self.is_root(slot) {
let slot_meta_cf = self.db.column::<cf::SlotMeta>(); let slot_meta_cf = self.db.column::<cf::SlotMeta>();
let slot_meta = slot_meta_cf let slot_meta = slot_meta_cf
@ -1344,13 +1351,18 @@ impl Blocktree {
Hash::default() Hash::default()
}; };
let blockhash = get_last_hash(slot_entries.iter())
.unwrap_or_else(|| panic!("Rooted slot {:?} must have blockhash", slot));
let block = RpcConfirmedBlock { let block = RpcConfirmedBlock {
previous_blockhash, previous_blockhash: previous_blockhash.to_string(),
blockhash: get_last_hash(slot_entries.iter()) blockhash: blockhash.to_string(),
.unwrap_or_else(|| panic!("Rooted slot {:?} must have blockhash", slot)),
parent_slot: slot_meta.parent_slot, parent_slot: slot_meta.parent_slot,
transactions: self transactions: self.map_transactions_to_statuses(
.map_transactions_to_statuses(slot, slot_transaction_iterator), slot,
encoding,
slot_transaction_iterator,
),
}; };
return Ok(block); return Ok(block);
} }
@ -1361,13 +1373,16 @@ impl Blocktree {
fn map_transactions_to_statuses<'a>( fn map_transactions_to_statuses<'a>(
&self, &self,
slot: Slot, slot: Slot,
encoding: RpcTransactionEncoding,
iterator: impl Iterator<Item = Transaction> + 'a, iterator: impl Iterator<Item = Transaction> + 'a,
) -> Vec<(Transaction, Option<RpcTransactionStatus>)> { ) -> Vec<(RpcEncodedTransaction, Option<RpcTransactionStatus>)> {
iterator iterator
.map(|transaction| { .map(|transaction| {
let signature = transaction.signatures[0]; let signature = transaction.signatures[0];
let encoded_transaction =
RpcEncodedTransaction::encode(transaction, encoding.clone());
( (
transaction, encoded_transaction,
self.transaction_status_cf self.transaction_status_cf
.get((slot, signature)) .get((slot, signature))
.expect("Expect database get to succeed"), .expect("Expect database get to succeed"),
@ -4634,31 +4649,52 @@ pub mod tests {
.collect(); .collect();
// Even if marked as root, a slot that is empty of entries should return an error // Even if marked as root, a slot that is empty of entries should return an error
let confirmed_block_err = ledger.get_confirmed_block(slot - 1).unwrap_err(); let confirmed_block_err = ledger.get_confirmed_block(slot - 1, None).unwrap_err();
assert_matches!(confirmed_block_err, BlocktreeError::SlotNotRooted); assert_matches!(confirmed_block_err, BlocktreeError::SlotNotRooted);
let confirmed_block = ledger.get_confirmed_block(slot).unwrap(); let confirmed_block = ledger.get_confirmed_block(slot, None).unwrap();
assert_eq!(confirmed_block.transactions.len(), 100); assert_eq!(confirmed_block.transactions.len(), 100);
let mut expected_block = RpcConfirmedBlock::default(); let expected_block = RpcConfirmedBlock {
expected_block.transactions = expected_transactions.clone(); transactions: expected_transactions
expected_block.parent_slot = slot - 1; .iter()
expected_block.blockhash = blockhash; .cloned()
.map(|(tx, status)| {
(
RpcEncodedTransaction::encode(tx, RpcTransactionEncoding::Json),
status,
)
})
.collect(),
parent_slot: slot - 1,
blockhash: blockhash.to_string(),
previous_blockhash: Hash::default().to_string(),
};
// The previous_blockhash of `expected_block` is default because its parent slot is a // The previous_blockhash of `expected_block` is default because its parent slot is a
// root, but empty of entries. This is special handling for snapshot root slots. // root, but empty of entries. This is special handling for snapshot root slots.
assert_eq!(confirmed_block, expected_block); assert_eq!(confirmed_block, expected_block);
let confirmed_block = ledger.get_confirmed_block(slot + 1).unwrap(); let confirmed_block = ledger.get_confirmed_block(slot + 1, None).unwrap();
assert_eq!(confirmed_block.transactions.len(), 100); assert_eq!(confirmed_block.transactions.len(), 100);
let mut expected_block = RpcConfirmedBlock::default(); let expected_block = RpcConfirmedBlock {
expected_block.transactions = expected_transactions; transactions: expected_transactions
expected_block.parent_slot = slot; .iter()
expected_block.previous_blockhash = blockhash; .cloned()
expected_block.blockhash = blockhash; .map(|(tx, status)| {
(
RpcEncodedTransaction::encode(tx, RpcTransactionEncoding::Json),
status,
)
})
.collect(),
parent_slot: slot,
blockhash: blockhash.to_string(),
previous_blockhash: blockhash.to_string(),
};
assert_eq!(confirmed_block, expected_block); assert_eq!(confirmed_block, expected_block);
let not_root = ledger.get_confirmed_block(slot + 2).unwrap_err(); let not_root = ledger.get_confirmed_block(slot + 2, None).unwrap_err();
assert_matches!(not_root, BlocktreeError::SlotNotRooted); assert_matches!(not_root, BlocktreeError::SlotNotRooted);
drop(ledger); drop(ledger);
@ -4875,7 +4911,11 @@ pub mod tests {
vec![CompiledInstruction::new(1, &(), vec![0])], vec![CompiledInstruction::new(1, &(), vec![0])],
)); ));
let map = blocktree.map_transactions_to_statuses(slot, transactions.into_iter()); let map = blocktree.map_transactions_to_statuses(
slot,
RpcTransactionEncoding::Json,
transactions.into_iter(),
);
assert_eq!(map.len(), 5); assert_eq!(map.len(), 5);
for x in 0..4 { for x in 0..4 {
assert_eq!(map[x].1.as_ref().unwrap().fee, x as u64); assert_eq!(map[x].1.as_ref().unwrap().fee, x as u64);