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:
@ -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();
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user