Wait for supermajority of cluster to have rooted a transaction to consider it finalized (#9618)

* Add rooted stake to BlockCommitment

* Use rooted stake to include cluster check
This commit is contained in:
Tyera Eulberg
2020-04-20 23:25:49 -06:00
committed by GitHub
parent 914b022663
commit 18cba86f77
3 changed files with 43 additions and 17 deletions

View File

@ -13,9 +13,11 @@ use std::{
time::Duration, time::Duration,
}; };
pub type BlockCommitmentArray = [u64; MAX_LOCKOUT_HISTORY + 1];
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct BlockCommitment { pub struct BlockCommitment {
pub commitment: [u64; MAX_LOCKOUT_HISTORY], pub commitment: BlockCommitmentArray,
} }
impl BlockCommitment { impl BlockCommitment {
@ -28,8 +30,17 @@ impl BlockCommitment {
assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY); assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
self.commitment[confirmation_count - 1] self.commitment[confirmation_count - 1]
} }
pub fn increase_rooted_stake(&mut self, stake: u64) {
self.commitment[MAX_LOCKOUT_HISTORY] += stake;
}
pub fn get_rooted_stake(&self) -> u64 {
self.commitment[MAX_LOCKOUT_HISTORY]
}
#[cfg(test)] #[cfg(test)]
pub(crate) fn new(commitment: [u64; MAX_LOCKOUT_HISTORY]) -> Self { pub(crate) fn new(commitment: BlockCommitmentArray) -> Self {
Self { commitment } Self { commitment }
} }
} }
@ -110,6 +121,16 @@ impl BlockCommitmentCache {
0 0
}) })
} }
pub fn is_confirmed_rooted(&self, slot: Slot) -> bool {
self.get_block_commitment(slot)
.map(|block_commitment| {
(block_commitment.get_rooted_stake() as f64 / self.total_stake as f64)
> VOTE_THRESHOLD_SIZE
})
.unwrap_or(false)
}
#[cfg(test)] #[cfg(test)]
pub fn new_for_tests() -> Self { pub fn new_for_tests() -> Self {
let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new(); let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
@ -259,7 +280,7 @@ impl AggregateCommitmentService {
commitment commitment
.entry(*a) .entry(*a)
.or_insert_with(BlockCommitment::default) .or_insert_with(BlockCommitment::default)
.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports); .increase_rooted_stake(lamports);
} else { } else {
ancestors_index = i; ancestors_index = i;
break; break;
@ -351,7 +372,7 @@ mod tests {
for a in ancestors { for a in ancestors {
let mut expected = BlockCommitment::default(); let mut expected = BlockCommitment::default();
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports); expected.increase_rooted_stake(lamports);
assert_eq!(*commitment.get(&a).unwrap(), expected); assert_eq!(*commitment.get(&a).unwrap(), expected);
} }
} }
@ -376,7 +397,7 @@ mod tests {
for a in ancestors { for a in ancestors {
if a <= root { if a <= root {
let mut expected = BlockCommitment::default(); let mut expected = BlockCommitment::default();
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports); expected.increase_rooted_stake(lamports);
assert_eq!(*commitment.get(&a).unwrap(), expected); assert_eq!(*commitment.get(&a).unwrap(), expected);
} else { } else {
let mut expected = BlockCommitment::default(); let mut expected = BlockCommitment::default();
@ -408,7 +429,7 @@ mod tests {
for (i, a) in ancestors.iter().enumerate() { for (i, a) in ancestors.iter().enumerate() {
if *a <= root { if *a <= root {
let mut expected = BlockCommitment::default(); let mut expected = BlockCommitment::default();
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports); expected.increase_rooted_stake(lamports);
assert_eq!(*commitment.get(&a).unwrap(), expected); assert_eq!(*commitment.get(&a).unwrap(), expected);
} else if i <= 4 { } else if i <= 4 {
let mut expected = BlockCommitment::default(); let mut expected = BlockCommitment::default();

View File

@ -1,8 +1,11 @@
//! The `rpc` module implements the Solana RPC interface. //! The `rpc` module implements the Solana RPC interface.
use crate::{ use crate::{
cluster_info::ClusterInfo, commitment::BlockCommitmentCache, contact_info::ContactInfo, cluster_info::ClusterInfo,
storage_stage::StorageState, validator::ValidatorExit, commitment::{BlockCommitmentArray, BlockCommitmentCache},
contact_info::ContactInfo,
storage_stage::StorageState,
validator::ValidatorExit,
}; };
use bincode::serialize; use bincode::serialize;
use jsonrpc_core::{Error, Metadata, Result}; use jsonrpc_core::{Error, Metadata, Result};
@ -218,7 +221,7 @@ impl JsonRpcRequestProcessor {
} }
} }
fn get_block_commitment(&self, block: Slot) -> RpcBlockCommitment<[u64; MAX_LOCKOUT_HISTORY]> { fn get_block_commitment(&self, block: Slot) -> RpcBlockCommitment<BlockCommitmentArray> {
let r_block_commitment = self.block_commitment_cache.read().unwrap(); let r_block_commitment = self.block_commitment_cache.read().unwrap();
RpcBlockCommitment { RpcBlockCommitment {
commitment: r_block_commitment commitment: r_block_commitment
@ -486,7 +489,9 @@ impl JsonRpcRequestProcessor {
.map(|(slot, status)| { .map(|(slot, status)| {
let r_block_commitment_cache = self.block_commitment_cache.read().unwrap(); let r_block_commitment_cache = self.block_commitment_cache.read().unwrap();
let confirmations = if r_block_commitment_cache.root() >= slot { let confirmations = if r_block_commitment_cache.root() >= slot
&& r_block_commitment_cache.is_confirmed_rooted(slot)
{
None None
} else { } else {
r_block_commitment_cache r_block_commitment_cache
@ -644,7 +649,7 @@ pub trait RpcSol {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
block: Slot, block: Slot,
) -> Result<RpcBlockCommitment<[u64; MAX_LOCKOUT_HISTORY]>>; ) -> Result<RpcBlockCommitment<BlockCommitmentArray>>;
#[rpc(meta, name = "getGenesisHash")] #[rpc(meta, name = "getGenesisHash")]
fn get_genesis_hash(&self, meta: Self::Metadata) -> Result<String>; fn get_genesis_hash(&self, meta: Self::Metadata) -> Result<String>;
@ -948,7 +953,7 @@ impl RpcSol for RpcSolImpl {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
block: Slot, block: Slot,
) -> Result<RpcBlockCommitment<[u64; MAX_LOCKOUT_HISTORY]>> { ) -> Result<RpcBlockCommitment<BlockCommitmentArray>> {
Ok(meta Ok(meta
.request_processor .request_processor
.read() .read()
@ -2070,7 +2075,7 @@ pub mod tests {
.expect("actual response deserialization"); .expect("actual response deserialization");
let result = result.as_ref().unwrap(); let result = result.as_ref().unwrap();
assert_eq!(expected_res, result.status); assert_eq!(expected_res, result.status);
assert_eq!(None, result.confirmations); assert_eq!(Some(2), result.confirmations);
// Test getSignatureStatus request on unprocessed tx // Test getSignatureStatus request on unprocessed tx
let tx = system_transaction::transfer(&alice, &bob_pubkey, 10, blockhash); let tx = system_transaction::transfer(&alice, &bob_pubkey, 10, blockhash);
@ -2421,8 +2426,8 @@ pub mod tests {
let validator_exit = create_validator_exit(&exit); let validator_exit = create_validator_exit(&exit);
let bank_forks = new_bank_forks().0; let bank_forks = new_bank_forks().0;
let commitment_slot0 = BlockCommitment::new([8; MAX_LOCKOUT_HISTORY]); let commitment_slot0 = BlockCommitment::new([8; MAX_LOCKOUT_HISTORY + 1]);
let commitment_slot1 = BlockCommitment::new([9; MAX_LOCKOUT_HISTORY]); let commitment_slot1 = BlockCommitment::new([9; MAX_LOCKOUT_HISTORY + 1]);
let mut block_commitment: HashMap<u64, BlockCommitment> = HashMap::new(); let mut block_commitment: HashMap<u64, BlockCommitment> = HashMap::new();
block_commitment block_commitment
.entry(0) .entry(0)
@ -2514,7 +2519,7 @@ pub mod tests {
let res = io.handle_request_sync(&req, meta); let res = io.handle_request_sync(&req, meta);
let result: Response = serde_json::from_str(&res.expect("actual response")) let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization"); .expect("actual response deserialization");
let commitment_response: RpcBlockCommitment<[u64; MAX_LOCKOUT_HISTORY]> = let commitment_response: RpcBlockCommitment<BlockCommitmentArray> =
if let Response::Single(res) = result { if let Response::Single(res) = result {
if let Output::Success(res) = res { if let Output::Success(res) = res {
serde_json::from_value(res.result).unwrap() serde_json::from_value(res.result).unwrap()

View File

@ -733,7 +733,7 @@ An array of:
* `<null>` - Unknown transaction * `<null>` - Unknown transaction
* `<object>` * `<object>`
* `slot: <u64>` - The slot the transaction was processed * `slot: <u64>` - The slot the transaction was processed
* `confirmations: <usize | null>` - Number of blocks since signature confirmation, null if rooted * `confirmations: <usize | null>` - Number of blocks since signature confirmation, null if rooted, as well as finalized by a supermajority of the cluster
* `err: <object | null>` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14) * `err: <object | null>` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
* DEPRECATED: `status: <object>` - Transaction status * DEPRECATED: `status: <object>` - Transaction status
* `"Ok": <null>` - Transaction was successful * `"Ok": <null>` - Transaction was successful