From 18cba86f7706b9222c1abf08af3f0c196ed43402 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 20 Apr 2020 23:25:49 -0600 Subject: [PATCH] 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 --- core/src/commitment.rs | 33 +++++++++++++++++++++++++++------ core/src/rpc.rs | 25 +++++++++++++++---------- docs/src/apps/jsonrpc-api.md | 2 +- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/core/src/commitment.rs b/core/src/commitment.rs index 945cd878e0..8f10bdfd6e 100644 --- a/core/src/commitment.rs +++ b/core/src/commitment.rs @@ -13,9 +13,11 @@ use std::{ time::Duration, }; +pub type BlockCommitmentArray = [u64; MAX_LOCKOUT_HISTORY + 1]; + #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct BlockCommitment { - pub commitment: [u64; MAX_LOCKOUT_HISTORY], + pub commitment: BlockCommitmentArray, } impl BlockCommitment { @@ -28,8 +30,17 @@ impl BlockCommitment { assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY); 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)] - pub(crate) fn new(commitment: [u64; MAX_LOCKOUT_HISTORY]) -> Self { + pub(crate) fn new(commitment: BlockCommitmentArray) -> Self { Self { commitment } } } @@ -110,6 +121,16 @@ impl BlockCommitmentCache { 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)] pub fn new_for_tests() -> Self { let mut block_commitment: HashMap = HashMap::new(); @@ -259,7 +280,7 @@ impl AggregateCommitmentService { commitment .entry(*a) .or_insert_with(BlockCommitment::default) - .increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports); + .increase_rooted_stake(lamports); } else { ancestors_index = i; break; @@ -351,7 +372,7 @@ mod tests { for a in ancestors { 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); } } @@ -376,7 +397,7 @@ mod tests { for a in ancestors { if a <= root { 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); } else { let mut expected = BlockCommitment::default(); @@ -408,7 +429,7 @@ mod tests { for (i, a) in ancestors.iter().enumerate() { if *a <= root { 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); } else if i <= 4 { let mut expected = BlockCommitment::default(); diff --git a/core/src/rpc.rs b/core/src/rpc.rs index a64f74beca..f9f4618a76 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -1,8 +1,11 @@ //! The `rpc` module implements the Solana RPC interface. use crate::{ - cluster_info::ClusterInfo, commitment::BlockCommitmentCache, contact_info::ContactInfo, - storage_stage::StorageState, validator::ValidatorExit, + cluster_info::ClusterInfo, + commitment::{BlockCommitmentArray, BlockCommitmentCache}, + contact_info::ContactInfo, + storage_stage::StorageState, + validator::ValidatorExit, }; use bincode::serialize; 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 { let r_block_commitment = self.block_commitment_cache.read().unwrap(); RpcBlockCommitment { commitment: r_block_commitment @@ -486,7 +489,9 @@ impl JsonRpcRequestProcessor { .map(|(slot, status)| { 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 } else { r_block_commitment_cache @@ -644,7 +649,7 @@ pub trait RpcSol { &self, meta: Self::Metadata, block: Slot, - ) -> Result>; + ) -> Result>; #[rpc(meta, name = "getGenesisHash")] fn get_genesis_hash(&self, meta: Self::Metadata) -> Result; @@ -948,7 +953,7 @@ impl RpcSol for RpcSolImpl { &self, meta: Self::Metadata, block: Slot, - ) -> Result> { + ) -> Result> { Ok(meta .request_processor .read() @@ -2070,7 +2075,7 @@ pub mod tests { .expect("actual response deserialization"); let result = result.as_ref().unwrap(); assert_eq!(expected_res, result.status); - assert_eq!(None, result.confirmations); + assert_eq!(Some(2), result.confirmations); // Test getSignatureStatus request on unprocessed tx 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 bank_forks = new_bank_forks().0; - let commitment_slot0 = BlockCommitment::new([8; MAX_LOCKOUT_HISTORY]); - let commitment_slot1 = BlockCommitment::new([9; MAX_LOCKOUT_HISTORY]); + let commitment_slot0 = BlockCommitment::new([8; MAX_LOCKOUT_HISTORY + 1]); + let commitment_slot1 = BlockCommitment::new([9; MAX_LOCKOUT_HISTORY + 1]); let mut block_commitment: HashMap = HashMap::new(); block_commitment .entry(0) @@ -2514,7 +2519,7 @@ pub mod tests { let res = io.handle_request_sync(&req, meta); let result: Response = serde_json::from_str(&res.expect("actual response")) .expect("actual response deserialization"); - let commitment_response: RpcBlockCommitment<[u64; MAX_LOCKOUT_HISTORY]> = + let commitment_response: RpcBlockCommitment = if let Response::Single(res) = result { if let Output::Success(res) = res { serde_json::from_value(res.result).unwrap() diff --git a/docs/src/apps/jsonrpc-api.md b/docs/src/apps/jsonrpc-api.md index e5b7b7a9b1..21d88327ea 100644 --- a/docs/src/apps/jsonrpc-api.md +++ b/docs/src/apps/jsonrpc-api.md @@ -733,7 +733,7 @@ An array of: * `` - Unknown transaction * `` * `slot: ` - The slot the transaction was processed - * `confirmations: ` - Number of blocks since signature confirmation, null if rooted + * `confirmations: ` - Number of blocks since signature confirmation, null if rooted, as well as finalized by a supermajority of the cluster * `err: ` - 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: ` - Transaction status * `"Ok": ` - Transaction was successful