Legitimately map transactions to statuses in blocktree (#7011)
* Refactor rocksdb TransactionStatus to store/return struct; hook up map_transactions_to_statuses * Cleanup use statements
This commit is contained in:
parent
3bc8d78801
commit
e0a2bb9d86
|
@ -26,7 +26,13 @@ pub struct Response<T> {
|
||||||
pub struct RpcConfirmedBlock {
|
pub struct RpcConfirmedBlock {
|
||||||
pub previous_blockhash: Hash,
|
pub previous_blockhash: Hash,
|
||||||
pub blockhash: Hash,
|
pub blockhash: Hash,
|
||||||
pub transactions: Vec<(Transaction, Result<()>)>,
|
pub transactions: Vec<(Transaction, Option<RpcTransactionStatus>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct RpcTransactionStatus {
|
||||||
|
pub status: Result<()>,
|
||||||
|
pub fee: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
|
|
@ -304,8 +304,8 @@ impl JsonRpcRequestProcessor {
|
||||||
// The `get_confirmed_block` method is not fully implemented. It currenlty returns a partially
|
// The `get_confirmed_block` method is not fully implemented. It currenlty returns a partially
|
||||||
// complete RpcConfirmedBlock. The `blockhash` and `previous_blockhash` fields are legitimate
|
// complete RpcConfirmedBlock. The `blockhash` and `previous_blockhash` fields are legitimate
|
||||||
// data, while the `transactions` field contains transaction tuples (Transaction,
|
// data, while the `transactions` field contains transaction tuples (Transaction,
|
||||||
// transaction::Result), where the Transaction is a legitimate transaction, but the Result is
|
// transaction::Result), where the Transaction is a legitimate transaction, but the
|
||||||
// always `Ok()`.
|
// Option<RpcTransactionStatus> is always None.
|
||||||
pub fn get_confirmed_block(&self, slot: Slot) -> Result<Option<RpcConfirmedBlock>> {
|
pub fn get_confirmed_block(&self, slot: Slot) -> Result<Option<RpcConfirmedBlock>> {
|
||||||
Ok(self.blocktree.get_confirmed_block(slot).ok())
|
Ok(self.blocktree.get_confirmed_block(slot).ok())
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use rayon::{
|
||||||
ThreadPool,
|
ThreadPool,
|
||||||
};
|
};
|
||||||
use rocksdb::DBRawIterator;
|
use rocksdb::DBRawIterator;
|
||||||
use solana_client::rpc_request::RpcConfirmedBlock;
|
use solana_client::rpc_request::{RpcConfirmedBlock, 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;
|
||||||
|
@ -32,7 +32,7 @@ use solana_sdk::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
signature::{Keypair, KeypairUtil},
|
signature::{Keypair, KeypairUtil},
|
||||||
timing::timestamp,
|
timing::timestamp,
|
||||||
transaction::{self, Transaction},
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
@ -1143,7 +1143,7 @@ impl Blocktree {
|
||||||
.expect("Rooted parent slot must have blockhash"),
|
.expect("Rooted parent slot must have blockhash"),
|
||||||
blockhash: get_last_hash(slot_entries.iter())
|
blockhash: get_last_hash(slot_entries.iter())
|
||||||
.expect("Rooted slot must have blockhash"),
|
.expect("Rooted slot must have blockhash"),
|
||||||
transactions: self.map_transactions_to_statuses(slot_transaction_iterator),
|
transactions: self.map_transactions_to_statuses(slot, slot_transaction_iterator),
|
||||||
};
|
};
|
||||||
Ok(block)
|
Ok(block)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1151,13 +1151,22 @@ impl Blocktree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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>(
|
fn map_transactions_to_statuses<'a>(
|
||||||
&self,
|
&self,
|
||||||
|
slot: Slot,
|
||||||
iterator: impl Iterator<Item = Transaction> + 'a,
|
iterator: impl Iterator<Item = Transaction> + 'a,
|
||||||
) -> Vec<(Transaction, transaction::Result<()>)> {
|
) -> Vec<(Transaction, Option<RpcTransactionStatus>)> {
|
||||||
iterator.map(|transaction| (transaction, Ok(()))).collect()
|
iterator
|
||||||
|
.map(|transaction| {
|
||||||
|
let signature = transaction.signatures[0];
|
||||||
|
(
|
||||||
|
transaction,
|
||||||
|
self.transaction_status_cf
|
||||||
|
.get((slot, signature))
|
||||||
|
.expect("Expect database get to succeed"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the entry vector for the slot starting with `shred_start_index`
|
/// Returns the entry vector for the slot starting with `shred_start_index`
|
||||||
|
@ -4095,17 +4104,36 @@ pub mod tests {
|
||||||
ledger.insert_shreds(shreds, None, false).unwrap();
|
ledger.insert_shreds(shreds, None, false).unwrap();
|
||||||
ledger.set_roots(&[0]).unwrap();
|
ledger.set_roots(&[0]).unwrap();
|
||||||
|
|
||||||
let confirmed_block = ledger.get_confirmed_block(0).unwrap();
|
let expected_transactions: Vec<(Transaction, Option<RpcTransactionStatus>)> = entries
|
||||||
assert_eq!(confirmed_block.transactions.len(), 100);
|
|
||||||
|
|
||||||
let expected_transactions: Vec<(Transaction, transaction::Result<()>)> = entries
|
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|entry| !entry.is_tick())
|
.filter(|entry| !entry.is_tick())
|
||||||
.flat_map(|entry| entry.transactions)
|
.flat_map(|entry| entry.transactions)
|
||||||
.map(|transaction| (transaction, Ok(())))
|
.map(|transaction| {
|
||||||
|
let signature = transaction.signatures[0];
|
||||||
|
ledger
|
||||||
|
.transaction_status_cf
|
||||||
|
.put(
|
||||||
|
(slot, signature),
|
||||||
|
&RpcTransactionStatus {
|
||||||
|
status: Ok(()),
|
||||||
|
fee: 42,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
(
|
||||||
|
transaction,
|
||||||
|
Some(RpcTransactionStatus {
|
||||||
|
status: Ok(()),
|
||||||
|
fee: 42,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let confirmed_block = ledger.get_confirmed_block(0).unwrap();
|
||||||
|
assert_eq!(confirmed_block.transactions.len(), 100);
|
||||||
|
|
||||||
let mut expected_block = RpcConfirmedBlock::default();
|
let mut expected_block = RpcConfirmedBlock::default();
|
||||||
expected_block.transactions = expected_transactions;
|
expected_block.transactions = expected_transactions;
|
||||||
// The blockhash and previous_blockhash of `expected_block` are default only because
|
// The blockhash and previous_blockhash of `expected_block` are default only because
|
||||||
|
@ -4136,17 +4164,17 @@ pub mod tests {
|
||||||
assert!(transaction_status_cf
|
assert!(transaction_status_cf
|
||||||
.put(
|
.put(
|
||||||
(0, Signature::default()),
|
(0, Signature::default()),
|
||||||
&(
|
&RpcTransactionStatus {
|
||||||
solana_sdk::transaction::Result::<()>::Err(
|
status: solana_sdk::transaction::Result::<()>::Err(
|
||||||
TransactionError::AccountNotFound
|
TransactionError::AccountNotFound
|
||||||
),
|
),
|
||||||
5u64
|
fee: 5u64
|
||||||
)
|
},
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// result found
|
// result found
|
||||||
let (status, fee) = transaction_status_cf
|
let RpcTransactionStatus { status, fee } = transaction_status_cf
|
||||||
.get((0, Signature::default()))
|
.get((0, Signature::default()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -4157,12 +4185,15 @@ pub mod tests {
|
||||||
assert!(transaction_status_cf
|
assert!(transaction_status_cf
|
||||||
.put(
|
.put(
|
||||||
(9, Signature::default()),
|
(9, Signature::default()),
|
||||||
&(solana_sdk::transaction::Result::<()>::Ok(()), 9u64)
|
&RpcTransactionStatus {
|
||||||
|
status: solana_sdk::transaction::Result::<()>::Ok(()),
|
||||||
|
fee: 9u64
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// result found
|
// result found
|
||||||
let (status, fee) = transaction_status_cf
|
let RpcTransactionStatus { status, fee } = transaction_status_cf
|
||||||
.get((9, Signature::default()))
|
.get((9, Signature::default()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -4189,4 +4220,53 @@ pub mod tests {
|
||||||
let entries_iterator = entries.iter();
|
let entries_iterator = entries.iter();
|
||||||
assert_eq!(get_last_hash(entries_iterator).unwrap(), entries[9].hash);
|
assert_eq!(get_last_hash(entries_iterator).unwrap(), entries[9].hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_transactions_to_statuses() {
|
||||||
|
let blocktree_path = get_tmp_ledger_path!();
|
||||||
|
{
|
||||||
|
let blocktree = Blocktree::open(&blocktree_path).unwrap();
|
||||||
|
let transaction_status_cf = blocktree.db.column::<cf::TransactionStatus>();
|
||||||
|
|
||||||
|
let slot = 0;
|
||||||
|
let mut transactions: Vec<Transaction> = vec![];
|
||||||
|
for x in 0..4 {
|
||||||
|
let transaction = Transaction::new_with_compiled_instructions(
|
||||||
|
&[&Keypair::new()],
|
||||||
|
&[Pubkey::new_rand()],
|
||||||
|
Hash::default(),
|
||||||
|
vec![Pubkey::new_rand()],
|
||||||
|
vec![CompiledInstruction::new(1, &(), vec![0])],
|
||||||
|
);
|
||||||
|
transaction_status_cf
|
||||||
|
.put(
|
||||||
|
(slot, transaction.signatures[0]),
|
||||||
|
&RpcTransactionStatus {
|
||||||
|
status: solana_sdk::transaction::Result::<()>::Err(
|
||||||
|
TransactionError::AccountNotFound,
|
||||||
|
),
|
||||||
|
fee: x,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
transactions.push(transaction);
|
||||||
|
}
|
||||||
|
// Push transaction that will not have matching status, as a test case
|
||||||
|
transactions.push(Transaction::new_with_compiled_instructions(
|
||||||
|
&[&Keypair::new()],
|
||||||
|
&[Pubkey::new_rand()],
|
||||||
|
Hash::default(),
|
||||||
|
vec![Pubkey::new_rand()],
|
||||||
|
vec![CompiledInstruction::new(1, &(), vec![0])],
|
||||||
|
));
|
||||||
|
|
||||||
|
let map = blocktree.map_transactions_to_statuses(slot, transactions.into_iter());
|
||||||
|
assert_eq!(map.len(), 5);
|
||||||
|
for x in 0..4 {
|
||||||
|
assert_eq!(map[x].1.as_ref().unwrap().fee, x as u64);
|
||||||
|
}
|
||||||
|
assert_eq!(map[4].1.as_ref(), None);
|
||||||
|
}
|
||||||
|
Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,9 @@ use rocksdb::{
|
||||||
};
|
};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use solana_client::rpc_request::RpcTransactionStatus;
|
||||||
use solana_sdk::{clock::Slot, signature::Signature};
|
use solana_sdk::{clock::Slot, signature::Signature};
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fs, marker::PhantomData, path::Path, sync::Arc};
|
||||||
use std::fs;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
// A good value for this is the number of cores on the machine
|
// A good value for this is the number of cores on the machine
|
||||||
const TOTAL_THREADS: i32 = 8;
|
const TOTAL_THREADS: i32 = 8;
|
||||||
|
@ -261,7 +258,7 @@ pub trait TypedColumn: Column {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedColumn for columns::TransactionStatus {
|
impl TypedColumn for columns::TransactionStatus {
|
||||||
type Type = (solana_sdk::transaction::Result<()>, u64);
|
type Type = RpcTransactionStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Column for columns::TransactionStatus {
|
impl Column for columns::TransactionStatus {
|
||||||
|
|
Loading…
Reference in New Issue