Add missing websocket methods to rust RPC PubSub client (#21065)
- Added accountSubscribe, programSubscribe, slotSubscribe and rootSubscribe to rust RpcClient - Removed duplication on cleanup threads - Moved RPCVote from rpc/ to client/rpc_response
This commit is contained in:
parent
78d99b89c0
commit
a0f9e0e8ee
File diff suppressed because it is too large
Load Diff
|
@ -10,6 +10,8 @@ documentation = "https://docs.rs/solana-client-test"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
serde_json = "1.0.68"
|
||||||
|
serial_test = "0.5.1"
|
||||||
solana-client = { path = "../client", version = "=1.9.0" }
|
solana-client = { path = "../client", version = "=1.9.0" }
|
||||||
solana-measure = { path = "../measure", version = "=1.9.0" }
|
solana-measure = { path = "../measure", version = "=1.9.0" }
|
||||||
solana-merkle-tree = { path = "../merkle-tree", version = "=1.9.0" }
|
solana-merkle-tree = { path = "../merkle-tree", version = "=1.9.0" }
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
use solana_client::{pubsub_client::PubsubClient, rpc_client::RpcClient, rpc_response::SlotInfo};
|
use serde_json::{json, Value};
|
||||||
|
use serial_test::serial;
|
||||||
|
use solana_client::{
|
||||||
|
pubsub_client::PubsubClient,
|
||||||
|
rpc_client::RpcClient,
|
||||||
|
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
|
||||||
|
rpc_response::SlotInfo,
|
||||||
|
};
|
||||||
use solana_rpc::{
|
use solana_rpc::{
|
||||||
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
|
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
|
||||||
rpc_pubsub_service::{PubSubConfig, PubSubService},
|
rpc_pubsub_service::{PubSubConfig, PubSubService},
|
||||||
|
@ -7,19 +14,22 @@ use solana_rpc::{
|
||||||
use solana_runtime::{
|
use solana_runtime::{
|
||||||
bank::Bank,
|
bank::Bank,
|
||||||
bank_forks::BankForks,
|
bank_forks::BankForks,
|
||||||
commitment::BlockCommitmentCache,
|
commitment::{BlockCommitmentCache, CommitmentSlots},
|
||||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||||
};
|
};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
|
clock::Slot,
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
native_token::sol_to_lamports,
|
native_token::sol_to_lamports,
|
||||||
|
pubkey::Pubkey,
|
||||||
rpc_port,
|
rpc_port,
|
||||||
signature::{Keypair, Signer},
|
signature::{Keypair, Signer},
|
||||||
system_transaction,
|
system_program, system_transaction,
|
||||||
};
|
};
|
||||||
use solana_streamer::socket::SocketAddrSpace;
|
use solana_streamer::socket::SocketAddrSpace;
|
||||||
use solana_test_validator::TestValidator;
|
use solana_test_validator::TestValidator;
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
|
@ -87,6 +97,249 @@ fn test_rpc_client() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_account_subscription() {
|
||||||
|
let pubsub_addr = SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
|
rpc_port::DEFAULT_RPC_PUBSUB_PORT,
|
||||||
|
);
|
||||||
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
let GenesisConfigInfo {
|
||||||
|
genesis_config,
|
||||||
|
mint_keypair: alice,
|
||||||
|
..
|
||||||
|
} = create_genesis_config(10_000);
|
||||||
|
let bank = Bank::new_for_tests(&genesis_config);
|
||||||
|
let blockhash = bank.last_blockhash();
|
||||||
|
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||||
|
let bank0 = bank_forks.read().unwrap().get(0).unwrap().clone();
|
||||||
|
let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), 1);
|
||||||
|
bank_forks.write().unwrap().insert(bank1);
|
||||||
|
let bob = Keypair::new();
|
||||||
|
|
||||||
|
let subscriptions = Arc::new(RpcSubscriptions::new_for_tests(
|
||||||
|
&exit,
|
||||||
|
bank_forks.clone(),
|
||||||
|
Arc::new(RwLock::new(BlockCommitmentCache::default())),
|
||||||
|
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
|
||||||
|
));
|
||||||
|
let (trigger, pubsub_service) =
|
||||||
|
PubSubService::new(PubSubConfig::default(), &subscriptions, pubsub_addr);
|
||||||
|
std::thread::sleep(Duration::from_millis(400));
|
||||||
|
let config = Some(RpcAccountInfoConfig {
|
||||||
|
commitment: Some(CommitmentConfig::finalized()),
|
||||||
|
encoding: None,
|
||||||
|
data_slice: None,
|
||||||
|
});
|
||||||
|
let (mut client, receiver) = PubsubClient::account_subscribe(
|
||||||
|
&format!("ws://0.0.0.0:{}/", pubsub_addr.port()),
|
||||||
|
&bob.pubkey(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Transfer 100 lamports from alice to bob
|
||||||
|
let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash);
|
||||||
|
bank_forks
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.get(1)
|
||||||
|
.unwrap()
|
||||||
|
.process_transaction(&tx)
|
||||||
|
.unwrap();
|
||||||
|
let commitment_slots = CommitmentSlots {
|
||||||
|
slot: 1,
|
||||||
|
..CommitmentSlots::default()
|
||||||
|
};
|
||||||
|
subscriptions.notify_subscribers(commitment_slots);
|
||||||
|
let commitment_slots = CommitmentSlots {
|
||||||
|
slot: 2,
|
||||||
|
root: 1,
|
||||||
|
highest_confirmed_slot: 1,
|
||||||
|
highest_confirmed_root: 1,
|
||||||
|
};
|
||||||
|
subscriptions.notify_subscribers(commitment_slots);
|
||||||
|
|
||||||
|
let expected = json!({
|
||||||
|
"context": { "slot": 1 },
|
||||||
|
"value": {
|
||||||
|
"owner": system_program::id().to_string(),
|
||||||
|
"lamports": 100,
|
||||||
|
"data": "",
|
||||||
|
"executable": false,
|
||||||
|
"rentEpoch": 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Read notification
|
||||||
|
let mut errors: Vec<(Value, Value)> = Vec::new();
|
||||||
|
let response = receiver.recv();
|
||||||
|
match response {
|
||||||
|
Ok(response) => {
|
||||||
|
let actual = serde_json::to_value(response).unwrap();
|
||||||
|
if expected != actual {
|
||||||
|
errors.push((expected, actual));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => eprintln!("unexpected websocket receive timeout"),
|
||||||
|
}
|
||||||
|
|
||||||
|
exit.store(true, Ordering::Relaxed);
|
||||||
|
trigger.cancel();
|
||||||
|
client.shutdown().unwrap();
|
||||||
|
pubsub_service.close().unwrap();
|
||||||
|
assert_eq!(errors, [].to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_program_subscription() {
|
||||||
|
let pubsub_addr = SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
|
rpc_port::DEFAULT_RPC_PUBSUB_PORT,
|
||||||
|
);
|
||||||
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
let GenesisConfigInfo {
|
||||||
|
genesis_config,
|
||||||
|
mint_keypair: alice,
|
||||||
|
..
|
||||||
|
} = create_genesis_config(10_000);
|
||||||
|
let bank = Bank::new_for_tests(&genesis_config);
|
||||||
|
let blockhash = bank.last_blockhash();
|
||||||
|
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||||
|
let bank0 = bank_forks.read().unwrap().get(0).unwrap().clone();
|
||||||
|
let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), 1);
|
||||||
|
bank_forks.write().unwrap().insert(bank1);
|
||||||
|
let bob = Keypair::new();
|
||||||
|
|
||||||
|
let subscriptions = Arc::new(RpcSubscriptions::new_for_tests(
|
||||||
|
&exit,
|
||||||
|
bank_forks.clone(),
|
||||||
|
Arc::new(RwLock::new(BlockCommitmentCache::default())),
|
||||||
|
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
|
||||||
|
));
|
||||||
|
let (trigger, pubsub_service) =
|
||||||
|
PubSubService::new(PubSubConfig::default(), &subscriptions, pubsub_addr);
|
||||||
|
std::thread::sleep(Duration::from_millis(400));
|
||||||
|
let config = Some(RpcProgramAccountsConfig {
|
||||||
|
..RpcProgramAccountsConfig::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let (mut client, receiver) = PubsubClient::program_subscribe(
|
||||||
|
&format!("ws://0.0.0.0:{}/", pubsub_addr.port()),
|
||||||
|
&system_program::id(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Transfer 100 lamports from alice to bob
|
||||||
|
let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash);
|
||||||
|
bank_forks
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.get(1)
|
||||||
|
.unwrap()
|
||||||
|
.process_transaction(&tx)
|
||||||
|
.unwrap();
|
||||||
|
let commitment_slots = CommitmentSlots {
|
||||||
|
slot: 1,
|
||||||
|
..CommitmentSlots::default()
|
||||||
|
};
|
||||||
|
subscriptions.notify_subscribers(commitment_slots);
|
||||||
|
let commitment_slots = CommitmentSlots {
|
||||||
|
slot: 2,
|
||||||
|
root: 1,
|
||||||
|
highest_confirmed_slot: 1,
|
||||||
|
highest_confirmed_root: 1,
|
||||||
|
};
|
||||||
|
subscriptions.notify_subscribers(commitment_slots);
|
||||||
|
|
||||||
|
// Poll notifications generated by the transfer
|
||||||
|
let mut notifications = Vec::new();
|
||||||
|
let mut pubkeys = HashSet::new();
|
||||||
|
loop {
|
||||||
|
let response = receiver.recv_timeout(Duration::from_millis(100));
|
||||||
|
match response {
|
||||||
|
Ok(response) => {
|
||||||
|
notifications.push(response.clone());
|
||||||
|
pubkeys.insert(response.value.pubkey);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown
|
||||||
|
exit.store(true, Ordering::Relaxed);
|
||||||
|
trigger.cancel();
|
||||||
|
client.shutdown().unwrap();
|
||||||
|
pubsub_service.close().unwrap();
|
||||||
|
|
||||||
|
// system_transaction::transfer() will generate 7 program account notifications for system_program
|
||||||
|
// since accounts need to be created
|
||||||
|
assert_eq!(notifications.len(), 7);
|
||||||
|
|
||||||
|
assert!(pubkeys.contains(&alice.pubkey().to_string()));
|
||||||
|
assert!(pubkeys.contains(&bob.pubkey().to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_root_subscription() {
|
||||||
|
let pubsub_addr = SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
|
rpc_port::DEFAULT_RPC_PUBSUB_PORT,
|
||||||
|
);
|
||||||
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
|
||||||
|
let bank = Bank::new_for_tests(&genesis_config);
|
||||||
|
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||||
|
let bank0 = bank_forks.read().unwrap().get(0).unwrap().clone();
|
||||||
|
let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), 1);
|
||||||
|
bank_forks.write().unwrap().insert(bank1);
|
||||||
|
|
||||||
|
let subscriptions = Arc::new(RpcSubscriptions::new_for_tests(
|
||||||
|
&exit,
|
||||||
|
bank_forks.clone(),
|
||||||
|
Arc::new(RwLock::new(BlockCommitmentCache::default())),
|
||||||
|
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
|
||||||
|
));
|
||||||
|
let (trigger, pubsub_service) =
|
||||||
|
PubSubService::new(PubSubConfig::default(), &subscriptions, pubsub_addr);
|
||||||
|
std::thread::sleep(Duration::from_millis(400));
|
||||||
|
let (mut client, receiver) =
|
||||||
|
PubsubClient::root_subscribe(&format!("ws://0.0.0.0:{}/", pubsub_addr.port())).unwrap();
|
||||||
|
|
||||||
|
let roots = vec![1, 2, 3];
|
||||||
|
subscriptions.notify_roots(roots.clone());
|
||||||
|
|
||||||
|
// Read notifications
|
||||||
|
let mut errors: Vec<(Slot, Slot)> = Vec::new();
|
||||||
|
for expected in roots {
|
||||||
|
let response = receiver.recv();
|
||||||
|
match response {
|
||||||
|
Ok(response) => {
|
||||||
|
if expected != response {
|
||||||
|
errors.push((expected, response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => eprintln!("unexpected websocket receive timeout"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit.store(true, Ordering::Relaxed);
|
||||||
|
trigger.cancel();
|
||||||
|
client.shutdown().unwrap();
|
||||||
|
pubsub_service.close().unwrap();
|
||||||
|
assert_eq!(errors, [].to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn test_slot_subscription() {
|
fn test_slot_subscription() {
|
||||||
let pubsub_addr = SocketAddr::new(
|
let pubsub_addr = SocketAddr::new(
|
||||||
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
rpc_config::{
|
rpc_config::{
|
||||||
RpcSignatureSubscribeConfig, RpcTransactionLogsConfig, RpcTransactionLogsFilter,
|
RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSignatureSubscribeConfig,
|
||||||
|
RpcTransactionLogsConfig, RpcTransactionLogsFilter,
|
||||||
},
|
},
|
||||||
rpc_response::{
|
rpc_response::{
|
||||||
Response as RpcResponse, RpcLogsResponse, RpcSignatureResult, SlotInfo, SlotUpdate,
|
Response as RpcResponse, RpcKeyedAccount, RpcLogsResponse, RpcSignatureResult,
|
||||||
|
SlotInfo, SlotUpdate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
log::*,
|
log::*,
|
||||||
|
@ -14,13 +16,14 @@ use {
|
||||||
value::Value::{Number, Object},
|
value::Value::{Number, Object},
|
||||||
Map, Value,
|
Map, Value,
|
||||||
},
|
},
|
||||||
solana_sdk::signature::Signature,
|
solana_account_decoder::UiAccount,
|
||||||
|
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature},
|
||||||
std::{
|
std::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
mpsc::{channel, Receiver},
|
mpsc::{channel, Receiver, Sender},
|
||||||
Arc, RwLock,
|
Arc, RwLock,
|
||||||
},
|
},
|
||||||
thread::{sleep, JoinHandle},
|
thread::{sleep, JoinHandle},
|
||||||
|
@ -154,16 +157,37 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type PubsubLogsClientSubscription = PubsubClientSubscription<RpcResponse<RpcLogsResponse>>;
|
||||||
pub type LogsSubscription = (
|
pub type LogsSubscription = (
|
||||||
PubsubClientSubscription<RpcResponse<RpcLogsResponse>>,
|
PubsubLogsClientSubscription,
|
||||||
Receiver<RpcResponse<RpcLogsResponse>>,
|
Receiver<RpcResponse<RpcLogsResponse>>,
|
||||||
);
|
);
|
||||||
pub type SlotsSubscription = (PubsubClientSubscription<SlotInfo>, Receiver<SlotInfo>);
|
|
||||||
|
pub type PubsubSlotClientSubscription = PubsubClientSubscription<SlotInfo>;
|
||||||
|
pub type SlotsSubscription = (PubsubSlotClientSubscription, Receiver<SlotInfo>);
|
||||||
|
|
||||||
|
pub type PubsubSignatureClientSubscription =
|
||||||
|
PubsubClientSubscription<RpcResponse<RpcSignatureResult>>;
|
||||||
pub type SignatureSubscription = (
|
pub type SignatureSubscription = (
|
||||||
PubsubClientSubscription<RpcResponse<RpcSignatureResult>>,
|
PubsubSignatureClientSubscription,
|
||||||
Receiver<RpcResponse<RpcSignatureResult>>,
|
Receiver<RpcResponse<RpcSignatureResult>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pub type PubsubProgramClientSubscription = PubsubClientSubscription<RpcResponse<RpcKeyedAccount>>;
|
||||||
|
pub type ProgramSubscription = (
|
||||||
|
PubsubProgramClientSubscription,
|
||||||
|
Receiver<RpcResponse<RpcKeyedAccount>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub type PubsubAccountClientSubscription = PubsubClientSubscription<RpcResponse<UiAccount>>;
|
||||||
|
pub type AccountSubscription = (
|
||||||
|
PubsubAccountClientSubscription,
|
||||||
|
Receiver<RpcResponse<UiAccount>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub type PubsubRootClientSubscription = PubsubClientSubscription<Slot>;
|
||||||
|
pub type RootSubscription = (PubsubRootClientSubscription, Receiver<Slot>);
|
||||||
|
|
||||||
pub struct PubsubClient {}
|
pub struct PubsubClient {}
|
||||||
|
|
||||||
fn connect_with_retry(
|
fn connect_with_retry(
|
||||||
|
@ -201,6 +225,47 @@ fn connect_with_retry(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PubsubClient {
|
impl PubsubClient {
|
||||||
|
pub fn account_subscribe(
|
||||||
|
url: &str,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
config: Option<RpcAccountInfoConfig>,
|
||||||
|
) -> Result<AccountSubscription, PubsubClientError> {
|
||||||
|
let url = Url::parse(url)?;
|
||||||
|
let socket = connect_with_retry(url)?;
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
|
||||||
|
let socket = Arc::new(RwLock::new(socket));
|
||||||
|
let socket_clone = socket.clone();
|
||||||
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
|
let exit_clone = exit.clone();
|
||||||
|
let body = json!({
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"accountSubscribe",
|
||||||
|
"params":[
|
||||||
|
pubkey.to_string(),
|
||||||
|
config
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
let subscription_id = PubsubAccountClientSubscription::send_subscribe(&socket_clone, body)?;
|
||||||
|
|
||||||
|
let t_cleanup = std::thread::spawn(move || {
|
||||||
|
Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = PubsubClientSubscription {
|
||||||
|
message_type: PhantomData,
|
||||||
|
operation: "account",
|
||||||
|
socket,
|
||||||
|
subscription_id,
|
||||||
|
t_cleanup: Some(t_cleanup),
|
||||||
|
exit,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((result, receiver))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn logs_subscribe(
|
pub fn logs_subscribe(
|
||||||
url: &str,
|
url: &str,
|
||||||
filter: RpcTransactionLogsFilter,
|
filter: RpcTransactionLogsFilter,
|
||||||
|
@ -214,38 +279,18 @@ impl PubsubClient {
|
||||||
let socket_clone = socket.clone();
|
let socket_clone = socket.clone();
|
||||||
let exit = Arc::new(AtomicBool::new(false));
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
let exit_clone = exit.clone();
|
let exit_clone = exit.clone();
|
||||||
|
let body = json!({
|
||||||
let subscription_id =
|
"jsonrpc":"2.0",
|
||||||
PubsubClientSubscription::<RpcResponse<RpcLogsResponse>>::send_subscribe(
|
"id":1,
|
||||||
&socket_clone,
|
"method":"logsSubscribe",
|
||||||
json!({
|
"params":[filter, config]
|
||||||
"jsonrpc":"2.0","id":1,"method":"logsSubscribe","params":[filter, config]
|
|
||||||
})
|
})
|
||||||
.to_string(),
|
.to_string();
|
||||||
)?;
|
|
||||||
|
let subscription_id = PubsubLogsClientSubscription::send_subscribe(&socket_clone, body)?;
|
||||||
|
|
||||||
let t_cleanup = std::thread::spawn(move || {
|
let t_cleanup = std::thread::spawn(move || {
|
||||||
loop {
|
Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
|
||||||
if exit_clone.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
match PubsubClientSubscription::read_message(&socket_clone) {
|
|
||||||
Ok(message) => match sender.send(message) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => {
|
|
||||||
info!("receive error: {:?}", err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
info!("receive error: {:?}", err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("websocket - exited receive loop");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = PubsubClientSubscription {
|
let result = PubsubClientSubscription {
|
||||||
|
@ -260,49 +305,71 @@ impl PubsubClient {
|
||||||
Ok((result, receiver))
|
Ok((result, receiver))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn slot_subscribe(url: &str) -> Result<SlotsSubscription, PubsubClientError> {
|
pub fn program_subscribe(
|
||||||
|
url: &str,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
config: Option<RpcProgramAccountsConfig>,
|
||||||
|
) -> Result<ProgramSubscription, PubsubClientError> {
|
||||||
let url = Url::parse(url)?;
|
let url = Url::parse(url)?;
|
||||||
let socket = connect_with_retry(url)?;
|
let socket = connect_with_retry(url)?;
|
||||||
let (sender, receiver) = channel::<SlotInfo>();
|
let (sender, receiver) = channel();
|
||||||
|
|
||||||
let socket = Arc::new(RwLock::new(socket));
|
let socket = Arc::new(RwLock::new(socket));
|
||||||
let socket_clone = socket.clone();
|
let socket_clone = socket.clone();
|
||||||
let exit = Arc::new(AtomicBool::new(false));
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
let exit_clone = exit.clone();
|
let exit_clone = exit.clone();
|
||||||
let subscription_id = PubsubClientSubscription::<SlotInfo>::send_subscribe(
|
let body = json!({
|
||||||
&socket_clone,
|
"jsonrpc":"2.0",
|
||||||
json!({
|
"id":1,
|
||||||
"jsonrpc":"2.0","id":1,"method":"slotSubscribe","params":[]
|
"method":"programSubscribe",
|
||||||
|
"params":[
|
||||||
|
pubkey.to_string(),
|
||||||
|
config
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.to_string(),
|
.to_string();
|
||||||
)?;
|
let subscription_id = PubsubProgramClientSubscription::send_subscribe(&socket_clone, body)?;
|
||||||
|
|
||||||
let t_cleanup = std::thread::spawn(move || {
|
let t_cleanup = std::thread::spawn(move || {
|
||||||
loop {
|
Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
|
||||||
if exit_clone.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
match PubsubClientSubscription::read_message(&socket_clone) {
|
|
||||||
Ok(message) => match sender.send(message) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => {
|
|
||||||
info!("receive error: {:?}", err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
info!("receive error: {:?}", err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("websocket - exited receive loop");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = PubsubClientSubscription {
|
let result = PubsubClientSubscription {
|
||||||
message_type: PhantomData,
|
message_type: PhantomData,
|
||||||
operation: "slot",
|
operation: "program",
|
||||||
|
socket,
|
||||||
|
subscription_id,
|
||||||
|
t_cleanup: Some(t_cleanup),
|
||||||
|
exit,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((result, receiver))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_subscribe(url: &str) -> Result<RootSubscription, PubsubClientError> {
|
||||||
|
let url = Url::parse(url)?;
|
||||||
|
let socket = connect_with_retry(url)?;
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
|
||||||
|
let socket = Arc::new(RwLock::new(socket));
|
||||||
|
let socket_clone = socket.clone();
|
||||||
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
|
let exit_clone = exit.clone();
|
||||||
|
let body = json!({
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"rootSubscribe",
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
let subscription_id = PubsubRootClientSubscription::send_subscribe(&socket_clone, body)?;
|
||||||
|
|
||||||
|
let t_cleanup = std::thread::spawn(move || {
|
||||||
|
Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = PubsubClientSubscription {
|
||||||
|
message_type: PhantomData,
|
||||||
|
operation: "root",
|
||||||
socket,
|
socket,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
t_cleanup: Some(t_cleanup),
|
t_cleanup: Some(t_cleanup),
|
||||||
|
@ -336,35 +403,10 @@ impl PubsubClient {
|
||||||
})
|
})
|
||||||
.to_string();
|
.to_string();
|
||||||
let subscription_id =
|
let subscription_id =
|
||||||
PubsubClientSubscription::<RpcResponse<RpcSignatureResult>>::send_subscribe(
|
PubsubSignatureClientSubscription::send_subscribe(&socket_clone, body)?;
|
||||||
&socket_clone,
|
|
||||||
body,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let t_cleanup = std::thread::spawn(move || {
|
let t_cleanup = std::thread::spawn(move || {
|
||||||
loop {
|
Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
|
||||||
if exit_clone.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let message: Result<RpcResponse<RpcSignatureResult>, PubsubClientError> =
|
|
||||||
PubsubClientSubscription::read_message(&socket_clone);
|
|
||||||
|
|
||||||
if let Ok(msg) = message {
|
|
||||||
match sender.send(msg.clone()) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => {
|
|
||||||
info!("receive error: {:?}", err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info!("receive error: {:?}", message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("websocket - exited receive loop");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = PubsubClientSubscription {
|
let result = PubsubClientSubscription {
|
||||||
|
@ -379,6 +421,40 @@ impl PubsubClient {
|
||||||
Ok((result, receiver))
|
Ok((result, receiver))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn slot_subscribe(url: &str) -> Result<SlotsSubscription, PubsubClientError> {
|
||||||
|
let url = Url::parse(url)?;
|
||||||
|
let socket = connect_with_retry(url)?;
|
||||||
|
let (sender, receiver) = channel::<SlotInfo>();
|
||||||
|
|
||||||
|
let socket = Arc::new(RwLock::new(socket));
|
||||||
|
let socket_clone = socket.clone();
|
||||||
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
|
let exit_clone = exit.clone();
|
||||||
|
let body = json!({
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"slotSubscribe",
|
||||||
|
"params":[]
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
let subscription_id = PubsubSlotClientSubscription::send_subscribe(&socket_clone, body)?;
|
||||||
|
|
||||||
|
let t_cleanup = std::thread::spawn(move || {
|
||||||
|
Self::cleanup_with_sender(exit_clone, &socket_clone, sender)
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = PubsubClientSubscription {
|
||||||
|
message_type: PhantomData,
|
||||||
|
operation: "slot",
|
||||||
|
socket,
|
||||||
|
subscription_id,
|
||||||
|
t_cleanup: Some(t_cleanup),
|
||||||
|
exit,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((result, receiver))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn slot_updates_subscribe(
|
pub fn slot_updates_subscribe(
|
||||||
url: &str,
|
url: &str,
|
||||||
handler: impl Fn(SlotUpdate) + Send + 'static,
|
handler: impl Fn(SlotUpdate) + Send + 'static,
|
||||||
|
@ -387,35 +463,21 @@ impl PubsubClient {
|
||||||
let socket = connect_with_retry(url)?;
|
let socket = connect_with_retry(url)?;
|
||||||
|
|
||||||
let socket = Arc::new(RwLock::new(socket));
|
let socket = Arc::new(RwLock::new(socket));
|
||||||
|
let socket_clone = socket.clone();
|
||||||
let exit = Arc::new(AtomicBool::new(false));
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
let exit_clone = exit.clone();
|
let exit_clone = exit.clone();
|
||||||
let subscription_id = PubsubClientSubscription::<SlotUpdate>::send_subscribe(
|
let body = json!({
|
||||||
&socket,
|
"jsonrpc":"2.0",
|
||||||
json!({
|
"id":1,
|
||||||
"jsonrpc":"2.0","id":1,"method":"slotsUpdatesSubscribe","params":[]
|
"method":"slotsUpdatesSubscribe",
|
||||||
|
"params":[]
|
||||||
})
|
})
|
||||||
.to_string(),
|
.to_string();
|
||||||
)?;
|
let subscription_id = PubsubSlotClientSubscription::send_subscribe(&socket, body)?;
|
||||||
|
|
||||||
let t_cleanup = {
|
let t_cleanup = std::thread::spawn(move || {
|
||||||
let socket = socket.clone();
|
Self::cleanup_with_handler(exit_clone, &socket_clone, handler)
|
||||||
std::thread::spawn(move || {
|
});
|
||||||
loop {
|
|
||||||
if exit_clone.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
match PubsubClientSubscription::read_message(&socket) {
|
|
||||||
Ok(message) => handler(message),
|
|
||||||
Err(err) => {
|
|
||||||
info!("receive error: {:?}", err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("websocket - exited receive loop");
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PubsubClientSubscription {
|
Ok(PubsubClientSubscription {
|
||||||
message_type: PhantomData,
|
message_type: PhantomData,
|
||||||
|
@ -426,6 +488,47 @@ impl PubsubClient {
|
||||||
exit,
|
exit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cleanup_with_sender<T>(
|
||||||
|
exit: Arc<AtomicBool>,
|
||||||
|
socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
|
||||||
|
sender: Sender<T>,
|
||||||
|
) where
|
||||||
|
T: DeserializeOwned + Send + 'static,
|
||||||
|
{
|
||||||
|
let handler = move |message| match sender.send(message) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => {
|
||||||
|
info!("receive error: {:?}", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Self::cleanup_with_handler(exit, socket, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_with_handler<T, F>(
|
||||||
|
exit: Arc<AtomicBool>,
|
||||||
|
socket: &Arc<RwLock<WebSocket<MaybeTlsStream<TcpStream>>>>,
|
||||||
|
handler: F,
|
||||||
|
) where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
F: Fn(T) + Send + 'static,
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
if exit.load(Ordering::Relaxed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
match PubsubClientSubscription::read_message(socket) {
|
||||||
|
Ok(message) => handler(message),
|
||||||
|
Err(err) => {
|
||||||
|
info!("receive error: {:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("websocket - exited receive loop");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -286,6 +286,14 @@ pub struct RpcIdentity {
|
||||||
pub identity: String,
|
pub identity: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RpcVote {
|
||||||
|
pub slots: Vec<Slot>,
|
||||||
|
pub hash: String,
|
||||||
|
pub timestamp: Option<UnixTimestamp>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RpcVoteAccountStatus {
|
pub struct RpcVoteAccountStatus {
|
||||||
|
|
|
@ -8,7 +8,6 @@ use {
|
||||||
ProgramSubscriptionParams, SignatureSubscriptionParams, SubscriptionControl,
|
ProgramSubscriptionParams, SignatureSubscriptionParams, SubscriptionControl,
|
||||||
SubscriptionId, SubscriptionParams, SubscriptionToken,
|
SubscriptionId, SubscriptionParams, SubscriptionToken,
|
||||||
},
|
},
|
||||||
rpc_subscriptions::RpcVote,
|
|
||||||
},
|
},
|
||||||
dashmap::DashMap,
|
dashmap::DashMap,
|
||||||
jsonrpc_core::{Error, ErrorCode, Result},
|
jsonrpc_core::{Error, ErrorCode, Result},
|
||||||
|
@ -21,7 +20,7 @@ use {
|
||||||
RpcTransactionLogsConfig, RpcTransactionLogsFilter,
|
RpcTransactionLogsConfig, RpcTransactionLogsFilter,
|
||||||
},
|
},
|
||||||
rpc_response::{
|
rpc_response::{
|
||||||
Response as RpcResponse, RpcKeyedAccount, RpcLogsResponse, RpcSignatureResult,
|
Response as RpcResponse, RpcKeyedAccount, RpcLogsResponse, RpcSignatureResult, RpcVote,
|
||||||
SlotInfo, SlotUpdate,
|
SlotInfo, SlotUpdate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,7 +18,7 @@ use {
|
||||||
rpc_filter::RpcFilterType,
|
rpc_filter::RpcFilterType,
|
||||||
rpc_response::{
|
rpc_response::{
|
||||||
ProcessedSignatureResult, ReceivedSignatureResult, Response, RpcKeyedAccount,
|
ProcessedSignatureResult, ReceivedSignatureResult, Response, RpcKeyedAccount,
|
||||||
RpcLogsResponse, RpcResponseContext, RpcSignatureResult, SlotInfo, SlotUpdate,
|
RpcLogsResponse, RpcResponseContext, RpcSignatureResult, RpcVote, SlotInfo, SlotUpdate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
solana_measure::measure::Measure,
|
solana_measure::measure::Measure,
|
||||||
|
@ -29,7 +29,7 @@ use {
|
||||||
},
|
},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::{AccountSharedData, ReadableAccount},
|
account::{AccountSharedData, ReadableAccount},
|
||||||
clock::{Slot, UnixTimestamp},
|
clock::Slot,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
timing::timestamp,
|
timing::timestamp,
|
||||||
|
@ -69,15 +69,6 @@ fn get_transaction_logs(
|
||||||
}
|
}
|
||||||
logs
|
logs
|
||||||
}
|
}
|
||||||
|
|
||||||
// A more human-friendly version of Vote, with the bank state signature base58 encoded.
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct RpcVote {
|
|
||||||
pub slots: Vec<Slot>,
|
|
||||||
pub hash: String,
|
|
||||||
pub timestamp: Option<UnixTimestamp>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum NotificationEntry {
|
pub enum NotificationEntry {
|
||||||
Slot(SlotInfo),
|
Slot(SlotInfo),
|
||||||
SlotUpdate(SlotUpdate),
|
SlotUpdate(SlotUpdate),
|
||||||
|
|
Loading…
Reference in New Issue