grpc, proto: add unary is_blockhash_valid (#132)

This commit is contained in:
Kirill Fomichev 2023-05-26 14:11:58 -04:00 committed by GitHub
parent 6812298fbc
commit 604add3080
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 157 additions and 30 deletions

View File

@ -78,6 +78,10 @@ enum Action {
GetLatestBlockhash,
GetBlockHeight,
GetSlot,
IsBlockhashValid {
#[clap(long, short)]
blockhash: String,
},
GetVersion,
}
@ -316,27 +320,32 @@ async fn main() -> anyhow::Result<()> {
.ping(*count)
.await
.map_err(anyhow::Error::new)
.map(|response| info!("response: {:?}", response)),
.map(|response| info!("response: {response:?}")),
Action::GetLatestBlockhash => client
.get_latest_blockhash(commitment)
.await
.map_err(anyhow::Error::new)
.map(|response| info!("response: {:?}", response)),
.map(|response| info!("response: {response:?}")),
Action::GetBlockHeight => client
.get_block_height(commitment)
.await
.map_err(anyhow::Error::new)
.map(|response| info!("response: {:?}", response)),
.map(|response| info!("response: {response:?}")),
Action::GetSlot => client
.get_slot(commitment)
.await
.map_err(anyhow::Error::new)
.map(|response| info!("response: {:?}", response)),
.map(|response| info!("response: {response:?}")),
Action::IsBlockhashValid { blockhash } => client
.is_blockhash_valid(blockhash.clone(), commitment)
.await
.map_err(anyhow::Error::new)
.map(|response| info!("response: {response:?}")),
Action::GetVersion => client
.get_version()
.await
.map_err(anyhow::Error::new)
.map(|response| info!("response: {:?}", response)),
.map(|response| info!("response: {response:?}")),
}
.map_err(backoff::Error::transient)?;

View File

@ -17,10 +17,11 @@ use {
yellowstone_grpc_proto::prelude::{
geyser_client::GeyserClient, CommitmentLevel, GetBlockHeightRequest,
GetBlockHeightResponse, GetLatestBlockhashRequest, GetLatestBlockhashResponse,
GetSlotRequest, GetSlotResponse, GetVersionRequest, GetVersionResponse, PingRequest,
PongResponse, SubscribeRequest, SubscribeRequestFilterAccounts,
SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta,
SubscribeRequestFilterSlots, SubscribeRequestFilterTransactions, SubscribeUpdate,
GetSlotRequest, GetSlotResponse, GetVersionRequest, GetVersionResponse,
IsBlockhashValidRequest, IsBlockhashValidResponse, PingRequest, PongResponse,
SubscribeRequest, SubscribeRequestFilterAccounts, SubscribeRequestFilterBlocks,
SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterSlots,
SubscribeRequestFilterTransactions, SubscribeUpdate,
},
};
@ -163,6 +164,19 @@ impl<F: Interceptor> GeyserGrpcClient<F> {
Ok(response.into_inner())
}
pub async fn is_blockhash_valid(
&mut self,
blockhash: String,
commitment: Option<CommitmentLevel>,
) -> GeyserGrpcClientResult<IsBlockhashValidResponse> {
let request = tonic::Request::new(IsBlockhashValidRequest {
blockhash,
commitment: commitment.map(|value| value as i32),
});
let response = self.client.is_blockhash_valid(request).await?;
Ok(response.into_inner())
}
pub async fn get_version(&mut self) -> GeyserGrpcClientResult<GetVersionResponse> {
let request = tonic::Request::new(GetVersionRequest {});
let response = self.client.get_version(request).await?;

View File

@ -9,10 +9,11 @@ use {
subscribe_update::UpdateOneof,
CommitmentLevel, GetBlockHeightRequest, GetBlockHeightResponse,
GetLatestBlockhashRequest, GetLatestBlockhashResponse, GetSlotRequest, GetSlotResponse,
GetVersionRequest, GetVersionResponse, PingRequest, PongResponse, SubscribeRequest,
SubscribeUpdate, SubscribeUpdateAccount, SubscribeUpdateAccountInfo,
SubscribeUpdateBlock, SubscribeUpdateBlockMeta, SubscribeUpdatePing,
SubscribeUpdateSlot, SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo,
GetVersionRequest, GetVersionResponse, IsBlockhashValidRequest,
IsBlockhashValidResponse, PingRequest, PongResponse, SubscribeRequest, SubscribeUpdate,
SubscribeUpdateAccount, SubscribeUpdateAccountInfo, SubscribeUpdateBlock,
SubscribeUpdateBlockMeta, SubscribeUpdatePing, SubscribeUpdateSlot,
SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo,
},
version::VERSION,
},
@ -21,7 +22,9 @@ use {
ReplicaAccountInfoV2, ReplicaBlockInfoV2, ReplicaTransactionInfoV2, SlotStatus,
},
solana_sdk::{
clock::UnixTimestamp, pubkey::Pubkey, signature::Signature,
clock::{UnixTimestamp, MAX_RECENT_BLOCKHASHES},
pubkey::Pubkey,
signature::Signature,
transaction::SanitizedTransaction,
},
solana_transaction_status::{Reward, TransactionStatusMeta},
@ -297,9 +300,29 @@ struct ClientConnection {
stream_tx: mpsc::Sender<TonicResult<SubscribeUpdate>>,
}
#[derive(Debug)]
struct BlockhashStatus {
slot: u64,
processed: bool,
confirmed: bool,
finalized: bool,
}
impl BlockhashStatus {
fn new(slot: u64) -> Self {
Self {
slot,
processed: false,
confirmed: false,
finalized: false,
}
}
}
#[derive(Debug, Default)]
struct BlockMetaStorageInner {
blocks: BTreeMap<u64, MessageBlockMeta>,
blockhashes: HashMap<String, BlockhashStatus>,
processed: Option<u64>,
confirmed: Option<u64>,
finalized: Option<u64>,
@ -330,8 +353,37 @@ impl BlockMetaStorage {
}
.replace(msg.slot);
if let Some(blockhash) = storage
.blocks
.get(&msg.slot)
.map(|block| block.blockhash.clone())
{
let entry = storage
.blockhashes
.entry(blockhash)
.or_insert_with(|| BlockhashStatus::new(msg.slot));
let status = match msg.status {
CommitmentLevel::Processed => &mut entry.processed,
CommitmentLevel::Confirmed => &mut entry.confirmed,
CommitmentLevel::Finalized => &mut entry.finalized,
};
*status = true;
}
if msg.status == CommitmentLevel::Finalized {
clean_btree_slots(&mut storage.blocks, msg.slot - KEEP_SLOTS);
let keep_slot = msg.slot - KEEP_SLOTS;
loop {
match storage.blocks.keys().next().cloned() {
Some(slot) if slot < keep_slot => storage.blocks.remove(&slot),
_ => break,
};
}
let keep_slot = msg.slot - MAX_RECENT_BLOCKHASHES as u64 - 32;
storage
.blockhashes
.retain(|_blockhash, status| status.slot >= keep_slot);
}
}
Message::BlockMeta(msg) => {
@ -347,6 +399,14 @@ impl BlockMetaStorage {
(Self { inner }, tx)
}
fn parse_commitment(commitment: Option<i32>) -> Result<CommitmentLevel, Status> {
let commitment = commitment.unwrap_or(CommitmentLevel::Finalized as i32);
CommitmentLevel::from_i32(commitment).ok_or_else(|| {
let msg = format!("failed to create CommitmentLevel from {commitment:?}");
Status::unknown(msg)
})
}
async fn get_block<F, T>(
&self,
handler: F,
@ -355,12 +415,7 @@ impl BlockMetaStorage {
where
F: FnOnce(&MessageBlockMeta) -> Option<T>,
{
let commitment = commitment.unwrap_or(CommitmentLevel::Finalized as i32);
let commitment = CommitmentLevel::from_i32(commitment).ok_or_else(|| {
let msg = format!("failed to create CommitmentLevel from {commitment:?}");
Status::unknown(msg)
})?;
let commitment = Self::parse_commitment(commitment)?;
let storage = self.inner.read().await;
let slot = match commitment {
@ -368,6 +423,7 @@ impl BlockMetaStorage {
CommitmentLevel::Confirmed => storage.confirmed,
CommitmentLevel::Finalized => storage.finalized,
};
match slot.and_then(|slot| storage.blocks.get(&slot)) {
Some(block) => match handler(block) {
Some(resp) => Ok(Response::new(resp)),
@ -376,15 +432,37 @@ impl BlockMetaStorage {
None => Err(Status::internal("block is not available yet")),
}
}
}
fn clean_btree_slots<T>(storage: &mut BTreeMap<u64, T>, keep_slot: u64) {
while let Some(slot) = storage.keys().next().cloned() {
if slot < keep_slot {
storage.remove(&slot);
} else {
break;
async fn is_blockhash_valid(
&self,
blockhash: &str,
commitment: Option<i32>,
) -> Result<Response<IsBlockhashValidResponse>, Status> {
let commitment = Self::parse_commitment(commitment)?;
let storage = self.inner.read().await;
if storage.blockhashes.len() < MAX_RECENT_BLOCKHASHES + 32 {
return Err(Status::internal("startup"));
}
let slot = match commitment {
CommitmentLevel::Processed => storage.processed,
CommitmentLevel::Confirmed => storage.confirmed,
CommitmentLevel::Finalized => storage.finalized,
}
.ok_or_else(|| Status::internal("startup"))?;
let valid = storage
.blockhashes
.get(blockhash)
.map(|status| match commitment {
CommitmentLevel::Processed => status.processed,
CommitmentLevel::Confirmed => status.confirmed,
CommitmentLevel::Finalized => status.finalized,
})
.unwrap_or(false);
Ok(Response::new(IsBlockhashValidResponse { valid, slot }))
}
}
@ -507,7 +585,13 @@ impl GrpcService {
}
if slot.status == CommitmentLevel::Finalized {
clean_btree_slots(&mut messages, slot.slot - KEEP_SLOTS);
let keep_slot = slot.slot - KEEP_SLOTS;
loop {
match messages.keys().next().cloned() {
Some(slot) if slot < keep_slot => messages.remove(&slot),
_ => break,
};
}
}
},
Some(msg) = new_clients_rx.recv() => {
@ -672,6 +756,16 @@ impl Geyser for GrpcService {
.await
}
async fn is_blockhash_valid(
&self,
request: Request<IsBlockhashValidRequest>,
) -> Result<Response<IsBlockhashValidResponse>, Status> {
let req = request.get_ref();
self.blocks_meta
.is_blockhash_valid(&req.blockhash, req.commitment)
.await
}
async fn get_version(
&self,
_request: Request<GetVersionRequest>,

View File

@ -12,6 +12,7 @@ service Geyser {
rpc GetLatestBlockhash(GetLatestBlockhashRequest) returns (GetLatestBlockhashResponse) {}
rpc GetBlockHeight(GetBlockHeightRequest) returns (GetBlockHeightResponse) {}
rpc GetSlot(GetSlotRequest) returns (GetSlotResponse) {}
rpc isBlockhashValid(IsBlockhashValidRequest) returns (IsBlockhashValidResponse) {}
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
}
@ -156,7 +157,6 @@ message GetLatestBlockhashRequest {
}
message GetLatestBlockhashResponse {
// The latest blockhash
uint64 slot = 1;
string blockhash = 2;
uint64 last_valid_block_height = 3;
@ -183,3 +183,13 @@ message GetVersionRequest {}
message GetVersionResponse {
string version = 1;
}
message IsBlockhashValidRequest {
string blockhash = 1;
optional CommitmentLevel commitment = 2;
}
message IsBlockhashValidResponse {
uint64 slot = 1;
bool valid = 2;
}