Add mechanism to get blockhash's last valid slot (#10239)

automerge
This commit is contained in:
Tyera Eulberg
2020-05-26 13:06:21 -06:00
committed by GitHub
parent 1bfc4c1489
commit 4e431bc818
8 changed files with 160 additions and 17 deletions

View File

@ -614,26 +614,46 @@ impl RpcClient {
} }
pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> { pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> {
Ok(self let (blockhash, fee_calculator, _last_valid_slot) = self
.get_recent_blockhash_with_commitment(CommitmentConfig::default())? .get_recent_blockhash_with_commitment(CommitmentConfig::default())?
.value) .value;
Ok((blockhash, fee_calculator))
} }
pub fn get_recent_blockhash_with_commitment( pub fn get_recent_blockhash_with_commitment(
&self, &self,
commitment_config: CommitmentConfig, commitment_config: CommitmentConfig,
) -> RpcResult<(Hash, FeeCalculator)> { ) -> RpcResult<(Hash, FeeCalculator, Slot)> {
let Response { let (context, blockhash, fee_calculator, last_valid_slot) = if let Ok(Response {
context,
value:
RpcFees {
blockhash,
fee_calculator,
last_valid_slot,
},
}) =
self.send::<Response<RpcFees>>(RpcRequest::GetFees, json!([commitment_config]))
{
(context, blockhash, fee_calculator, last_valid_slot)
} else if let Ok(Response {
context, context,
value: value:
RpcBlockhashFeeCalculator { RpcBlockhashFeeCalculator {
blockhash, blockhash,
fee_calculator, fee_calculator,
}, },
} = self.send::<Response<RpcBlockhashFeeCalculator>>( }) = self.send::<Response<RpcBlockhashFeeCalculator>>(
RpcRequest::GetRecentBlockhash, RpcRequest::GetRecentBlockhash,
json!([commitment_config]), json!([commitment_config]),
)?; ) {
(context, blockhash, fee_calculator, 0)
} else {
return Err(ClientError::new_with_request(
RpcError::ParseError("RpcBlockhashFeeCalculator or RpcFees".to_string()).into(),
RpcRequest::GetRecentBlockhash,
));
};
let blockhash = blockhash.parse().map_err(|_| { let blockhash = blockhash.parse().map_err(|_| {
ClientError::new_with_request( ClientError::new_with_request(
@ -643,7 +663,7 @@ impl RpcClient {
})?; })?;
Ok(Response { Ok(Response {
context, context,
value: (blockhash, fee_calculator), value: (blockhash, fee_calculator, last_valid_slot),
}) })
} }

View File

@ -16,15 +16,17 @@ pub enum RpcRequest {
GetConfirmedTransaction, GetConfirmedTransaction,
GetEpochInfo, GetEpochInfo,
GetEpochSchedule, GetEpochSchedule,
GetFeeCalculatorForBlockhash,
GetFeeRateGovernor,
GetFees,
GetGenesisHash, GetGenesisHash,
GetIdentity, GetIdentity,
GetInflation, GetInflation,
GetLargestAccounts, GetLargestAccounts,
GetLeaderSchedule, GetLeaderSchedule,
GetMinimumBalanceForRentExemption,
GetProgramAccounts, GetProgramAccounts,
GetRecentBlockhash, GetRecentBlockhash,
GetFeeCalculatorForBlockhash,
GetFeeRateGovernor,
GetSignatureStatuses, GetSignatureStatuses,
GetSlot, GetSlot,
GetSlotLeader, GetSlotLeader,
@ -37,13 +39,12 @@ pub enum RpcRequest {
GetTransactionCount, GetTransactionCount,
GetVersion, GetVersion,
GetVoteAccounts, GetVoteAccounts,
MinimumLedgerSlot,
RegisterNode, RegisterNode,
RequestAirdrop, RequestAirdrop,
SendTransaction, SendTransaction,
SimulateTransaction, SimulateTransaction,
SignVote, SignVote,
GetMinimumBalanceForRentExemption,
MinimumLedgerSlot,
} }
impl fmt::Display for RpcRequest { impl fmt::Display for RpcRequest {
@ -61,15 +62,17 @@ impl fmt::Display for RpcRequest {
RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction", RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction",
RpcRequest::GetEpochInfo => "getEpochInfo", RpcRequest::GetEpochInfo => "getEpochInfo",
RpcRequest::GetEpochSchedule => "getEpochSchedule", RpcRequest::GetEpochSchedule => "getEpochSchedule",
RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor",
RpcRequest::GetFees => "getFees",
RpcRequest::GetGenesisHash => "getGenesisHash", RpcRequest::GetGenesisHash => "getGenesisHash",
RpcRequest::GetIdentity => "getIdentity", RpcRequest::GetIdentity => "getIdentity",
RpcRequest::GetInflation => "getInflation", RpcRequest::GetInflation => "getInflation",
RpcRequest::GetLargestAccounts => "getLargestAccounts", RpcRequest::GetLargestAccounts => "getLargestAccounts",
RpcRequest::GetLeaderSchedule => "getLeaderSchedule", RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
RpcRequest::GetProgramAccounts => "getProgramAccounts", RpcRequest::GetProgramAccounts => "getProgramAccounts",
RpcRequest::GetRecentBlockhash => "getRecentBlockhash", RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor",
RpcRequest::GetSignatureStatuses => "getSignatureStatuses", RpcRequest::GetSignatureStatuses => "getSignatureStatuses",
RpcRequest::GetSlot => "getSlot", RpcRequest::GetSlot => "getSlot",
RpcRequest::GetSlotLeader => "getSlotLeader", RpcRequest::GetSlotLeader => "getSlotLeader",
@ -82,13 +85,12 @@ impl fmt::Display for RpcRequest {
RpcRequest::GetTransactionCount => "getTransactionCount", RpcRequest::GetTransactionCount => "getTransactionCount",
RpcRequest::GetVersion => "getVersion", RpcRequest::GetVersion => "getVersion",
RpcRequest::GetVoteAccounts => "getVoteAccounts", RpcRequest::GetVoteAccounts => "getVoteAccounts",
RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
RpcRequest::RegisterNode => "registerNode", RpcRequest::RegisterNode => "registerNode",
RpcRequest::RequestAirdrop => "requestAirdrop", RpcRequest::RequestAirdrop => "requestAirdrop",
RpcRequest::SendTransaction => "sendTransaction", RpcRequest::SendTransaction => "sendTransaction",
RpcRequest::SimulateTransaction => "simulateTransaction", RpcRequest::SimulateTransaction => "simulateTransaction",
RpcRequest::SignVote => "signVote", RpcRequest::SignVote => "signVote",
RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
}; };
write!(f, "{}", method) write!(f, "{}", method)

View File

@ -35,6 +35,14 @@ pub struct RpcBlockhashFeeCalculator {
pub fee_calculator: FeeCalculator, pub fee_calculator: FeeCalculator,
} }
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcFees {
pub blockhash: String,
pub fee_calculator: FeeCalculator,
pub last_valid_slot: Slot,
}
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcFeeCalculator { pub struct RpcFeeCalculator {

View File

@ -441,7 +441,7 @@ impl SyncClient for ThinClient {
match recent_blockhash { match recent_blockhash {
Ok(Response { value, .. }) => { Ok(Response { value, .. }) => {
self.optimizer.report(index, duration_as_ms(&now.elapsed())); self.optimizer.report(index, duration_as_ms(&now.elapsed()));
Ok(value) Ok((value.0, value.1))
} }
Err(e) => { Err(e) => {
self.optimizer.report(index, std::u64::MAX); self.optimizer.report(index, std::u64::MAX);

View File

@ -207,6 +207,22 @@ impl JsonRpcRequestProcessor {
) )
} }
fn get_fees(&self, commitment: Option<CommitmentConfig>) -> RpcResponse<RpcFees> {
let bank = &*self.bank(commitment)?;
let (blockhash, fee_calculator) = bank.confirmed_last_blockhash();
let last_valid_slot = bank
.get_blockhash_last_valid_slot(&blockhash)
.expect("bank blockhash queue should contain blockhash");
new_response(
bank,
RpcFees {
blockhash: blockhash.to_string(),
fee_calculator,
last_valid_slot,
},
)
}
fn get_fee_calculator_for_blockhash( fn get_fee_calculator_for_blockhash(
&self, &self,
blockhash: &Hash, blockhash: &Hash,
@ -793,6 +809,13 @@ pub trait RpcSol {
commitment: Option<CommitmentConfig>, commitment: Option<CommitmentConfig>,
) -> RpcResponse<RpcBlockhashFeeCalculator>; ) -> RpcResponse<RpcBlockhashFeeCalculator>;
#[rpc(meta, name = "getFees")]
fn get_fees(
&self,
meta: Self::Metadata,
commitment: Option<CommitmentConfig>,
) -> RpcResponse<RpcFees>;
#[rpc(meta, name = "getFeeCalculatorForBlockhash")] #[rpc(meta, name = "getFeeCalculatorForBlockhash")]
fn get_fee_calculator_for_blockhash( fn get_fee_calculator_for_blockhash(
&self, &self,
@ -1126,6 +1149,15 @@ impl RpcSol for RpcSolImpl {
.get_recent_blockhash(commitment) .get_recent_blockhash(commitment)
} }
fn get_fees(
&self,
meta: Self::Metadata,
commitment: Option<CommitmentConfig>,
) -> RpcResponse<RpcFees> {
debug!("get_fees rpc request received");
meta.request_processor.read().unwrap().get_fees(commitment)
}
fn get_fee_calculator_for_blockhash( fn get_fee_calculator_for_blockhash(
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
@ -1571,6 +1603,7 @@ pub mod tests {
get_tmp_ledger_path, get_tmp_ledger_path,
}; };
use solana_sdk::{ use solana_sdk::{
clock::MAX_RECENT_BLOCKHASHES,
fee_calculator::DEFAULT_BURN_PERCENT, fee_calculator::DEFAULT_BURN_PERCENT,
hash::{hash, Hash}, hash::{hash, Hash},
instruction::InstructionError, instruction::InstructionError,
@ -2517,6 +2550,38 @@ pub mod tests {
assert_eq!(expected, result); assert_eq!(expected, result);
} }
#[test]
fn test_rpc_get_fees() {
let bob_pubkey = Pubkey::new_rand();
let RpcHandler {
io,
meta,
blockhash,
..
} = start_rpc_handler_with_tx(&bob_pubkey);
let req = r#"{"jsonrpc":"2.0","id":1,"method":"getFees"}"#;
let res = io.handle_request_sync(&req, meta);
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"value":{
"blockhash": blockhash.to_string(),
"feeCalculator": {
"lamportsPerSignature": 0,
},
"lastValidSlot": MAX_RECENT_BLOCKHASHES,
}},
"id": 1
});
let expected: Response =
serde_json::from_value(expected).expect("expected response deserialization");
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
assert_eq!(expected, result);
}
#[test] #[test]
fn test_rpc_get_fee_calculator_for_blockhash() { fn test_rpc_get_fee_calculator_for_blockhash() {
let bob_pubkey = Pubkey::new_rand(); let bob_pubkey = Pubkey::new_rand();

View File

@ -27,6 +27,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
* [getEpochSchedule](jsonrpc-api.md#getepochschedule) * [getEpochSchedule](jsonrpc-api.md#getepochschedule)
* [getFeeCalculatorForBlockhash](jsonrpc-api.md#getfeecalculatorforblockhash) * [getFeeCalculatorForBlockhash](jsonrpc-api.md#getfeecalculatorforblockhash)
* [getFeeRateGovernor](jsonrpc-api.md#getfeerategovernor) * [getFeeRateGovernor](jsonrpc-api.md#getfeerategovernor)
* [getFees](jsonrpc-api.md#getfees)
* [getFirstAvailableBlock](jsonrpc-api.md#getfirstavailableblock) * [getFirstAvailableBlock](jsonrpc-api.md#getfirstavailableblock)
* [getGenesisHash](jsonrpc-api.md#getgenesishash) * [getGenesisHash](jsonrpc-api.md#getgenesishash)
* [getIdentity](jsonrpc-api.md#getidentity) * [getIdentity](jsonrpc-api.md#getidentity)
@ -538,6 +539,34 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
{"jsonrpc":"2.0","result":{"context":{"slot":54},"value":{"feeRateGovernor":{"burnPercent":50,"maxLamportsPerSignature":100000,"minLamportsPerSignature":5000,"targetLamportsPerSignature":10000,"targetSignaturesPerSlot":20000}}},"id":1} {"jsonrpc":"2.0","result":{"context":{"slot":54},"value":{"feeRateGovernor":{"burnPercent":50,"maxLamportsPerSignature":100000,"minLamportsPerSignature":5000,"targetLamportsPerSignature":10000,"targetSignaturesPerSlot":20000}}},"id":1}
``` ```
### getFees
Returns a recent block hash from the ledger, a fee schedule that can be used to
compute the cost of submitting a transaction using it, and the last slot in
which the blockhash will be valid.
#### Parameters:
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result will be an RpcResponse JSON object with `value` set to a JSON object with the following fields:
* `blockhash: <string>` - a Hash as base-58 encoded string
* `feeCalculator: <object>` - FeeCalculator object, the fee schedule for this block hash
* `lastValidSlot: <u64>` - last slot in which a blockhash will be valid
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getFees"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"blockhash":"CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR","feeCalculator":{lamportsPerSignature":5000},"lastValidSlot":297}},"id":1}
```
### getFirstAvailableBlock ### getFirstAvailableBlock
Returns the slot of the lowest confirmed block that has not been purged from the ledger Returns the slot of the lowest confirmed block that has not been purged from the ledger
@ -765,7 +794,7 @@ An RpcResponse containing a JSON object consisting of a string blockhash and Fee
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899 curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result // Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"blockhash":"CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR","feeCalculator":{"burnPercent":50,"lamportsPerSignature":5000,"maxLamportsPerSignature":100000,"minLamportsPerSignature":5000,"targetLamportsPerSignature":10000,"targetSignaturesPerSlot":20000}}},"id":1} {"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"blockhash":"CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR","feeCalculator":{"lamportsPerSignature":5000}}},"id":1}
``` ```
### getSignatureStatuses ### getSignatureStatuses

View File

@ -936,6 +936,15 @@ impl Bank {
&self.fee_rate_governor &self.fee_rate_governor
} }
pub fn get_blockhash_last_valid_slot(&self, blockhash: &Hash) -> Option<Slot> {
let blockhash_queue = self.blockhash_queue.read().unwrap();
// This calculation will need to be updated to consider epoch boundaries if BlockhashQueue
// length is made variable by epoch
blockhash_queue
.get_hash_age(blockhash)
.map(|age| self.slot + blockhash_queue.len() as u64 - age)
}
pub fn confirmed_last_blockhash(&self) -> (Hash, FeeCalculator) { pub fn confirmed_last_blockhash(&self) -> (Hash, FeeCalculator) {
const NUM_BLOCKHASH_CONFIRMATIONS: usize = 3; const NUM_BLOCKHASH_CONFIRMATIONS: usize = 3;

View File

@ -58,6 +58,12 @@ impl BlockhashQueue {
.map(|age| self.hash_height - age.hash_height <= max_age as u64) .map(|age| self.hash_height - age.hash_height <= max_age as u64)
} }
pub fn get_hash_age(&self, hash: &Hash) -> Option<u64> {
self.ages
.get(hash)
.map(|age| self.hash_height - age.hash_height)
}
/// check if hash is valid /// check if hash is valid
#[cfg(test)] #[cfg(test)]
pub fn check_hash(&self, hash: Hash) -> bool { pub fn check_hash(&self, hash: Hash) -> bool {
@ -119,6 +125,10 @@ impl BlockhashQueue {
.iter() .iter()
.map(|(k, v)| recent_blockhashes::IterItem(v.hash_height, k, &v.fee_calculator)) .map(|(k, v)| recent_blockhashes::IterItem(v.hash_height, k, &v.fee_calculator))
} }
pub fn len(&self) -> usize {
self.max_age
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {