Split out quic- and udp-client definitions (#28762)
* Move ConnectionCache back to solana-client, and duplicate ThinClient, TpuClient there * Dedupe thin_client modules * Dedupe tpu_client modules * Move TpuClient to TpuConnectionCache * Move ThinClient to TpuConnectionCache * Move TpuConnection and quic/udp trait implementations back to solana-client * Remove enum_dispatch from solana-tpu-client * Move udp-client to its own crate * Move quic-client to its own crate
This commit is contained in:
parent
dcfb73f664
commit
c32377b5af
|
@ -4637,6 +4637,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"solana-client",
|
||||||
"solana-core",
|
"solana-core",
|
||||||
"solana-gossip",
|
"solana-gossip",
|
||||||
"solana-ledger",
|
"solana-ledger",
|
||||||
|
@ -4689,7 +4690,6 @@ dependencies = [
|
||||||
"solana-runtime",
|
"solana-runtime",
|
||||||
"solana-sdk 1.15.0",
|
"solana-sdk 1.15.0",
|
||||||
"solana-send-transaction-service",
|
"solana-send-transaction-service",
|
||||||
"solana-tpu-client",
|
|
||||||
"tarpc",
|
"tarpc",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-serde",
|
"tokio-serde",
|
||||||
|
@ -4915,6 +4915,7 @@ dependencies = [
|
||||||
"solana-clap-utils",
|
"solana-clap-utils",
|
||||||
"solana-cli-config",
|
"solana-cli-config",
|
||||||
"solana-cli-output",
|
"solana-cli-output",
|
||||||
|
"solana-client",
|
||||||
"solana-config-program",
|
"solana-config-program",
|
||||||
"solana-faucet",
|
"solana-faucet",
|
||||||
"solana-logger 1.15.0",
|
"solana-logger 1.15.0",
|
||||||
|
@ -4983,15 +4984,33 @@ dependencies = [
|
||||||
name = "solana-client"
|
name = "solana-client"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"bincode",
|
||||||
|
"enum_dispatch",
|
||||||
|
"futures 0.3.24",
|
||||||
|
"futures-util",
|
||||||
|
"indexmap",
|
||||||
|
"indicatif",
|
||||||
"log",
|
"log",
|
||||||
|
"rand 0.7.3",
|
||||||
|
"rand_chacha 0.2.2",
|
||||||
|
"rayon",
|
||||||
|
"solana-logger 1.15.0",
|
||||||
"solana-measure",
|
"solana-measure",
|
||||||
|
"solana-metrics",
|
||||||
|
"solana-net-utils",
|
||||||
"solana-pubsub-client",
|
"solana-pubsub-client",
|
||||||
|
"solana-quic-client",
|
||||||
"solana-rpc-client",
|
"solana-rpc-client",
|
||||||
"solana-rpc-client-api",
|
"solana-rpc-client-api",
|
||||||
"solana-rpc-client-nonce-utils",
|
"solana-rpc-client-nonce-utils",
|
||||||
"solana-sdk 1.15.0",
|
"solana-sdk 1.15.0",
|
||||||
|
"solana-streamer",
|
||||||
"solana-thin-client",
|
"solana-thin-client",
|
||||||
"solana-tpu-client",
|
"solana-tpu-client",
|
||||||
|
"solana-udp-client",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5076,6 +5095,7 @@ dependencies = [
|
||||||
"serial_test",
|
"serial_test",
|
||||||
"solana-address-lookup-table-program",
|
"solana-address-lookup-table-program",
|
||||||
"solana-bloom",
|
"solana-bloom",
|
||||||
|
"solana-client",
|
||||||
"solana-entry",
|
"solana-entry",
|
||||||
"solana-frozen-abi 1.15.0",
|
"solana-frozen-abi 1.15.0",
|
||||||
"solana-frozen-abi-macro 1.15.0",
|
"solana-frozen-abi-macro 1.15.0",
|
||||||
|
@ -5125,6 +5145,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
"solana-bench-tps",
|
"solana-bench-tps",
|
||||||
|
"solana-client",
|
||||||
"solana-core",
|
"solana-core",
|
||||||
"solana-faucet",
|
"solana-faucet",
|
||||||
"solana-gossip",
|
"solana-gossip",
|
||||||
|
@ -5387,6 +5408,7 @@ dependencies = [
|
||||||
"serial_test",
|
"serial_test",
|
||||||
"solana-bloom",
|
"solana-bloom",
|
||||||
"solana-clap-utils",
|
"solana-clap-utils",
|
||||||
|
"solana-client",
|
||||||
"solana-entry",
|
"solana-entry",
|
||||||
"solana-frozen-abi 1.15.0",
|
"solana-frozen-abi 1.15.0",
|
||||||
"solana-frozen-abi-macro 1.15.0",
|
"solana-frozen-abi-macro 1.15.0",
|
||||||
|
@ -5571,6 +5593,7 @@ dependencies = [
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rayon",
|
"rayon",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
|
"solana-client",
|
||||||
"solana-config-program",
|
"solana-config-program",
|
||||||
"solana-core",
|
"solana-core",
|
||||||
"solana-download-utils",
|
"solana-download-utils",
|
||||||
|
@ -5959,12 +5982,28 @@ dependencies = [
|
||||||
name = "solana-quic-client"
|
name = "solana-quic-client"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-mutex",
|
||||||
|
"async-trait",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"futures 0.3.24",
|
||||||
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"quinn",
|
||||||
|
"quinn-proto",
|
||||||
|
"quinn-udp",
|
||||||
|
"rustls 0.20.6",
|
||||||
"solana-logger 1.15.0",
|
"solana-logger 1.15.0",
|
||||||
|
"solana-measure",
|
||||||
"solana-metrics",
|
"solana-metrics",
|
||||||
|
"solana-net-utils",
|
||||||
|
"solana-perf",
|
||||||
|
"solana-rpc-client-api",
|
||||||
"solana-sdk 1.15.0",
|
"solana-sdk 1.15.0",
|
||||||
"solana-streamer",
|
"solana-streamer",
|
||||||
"solana-tpu-client",
|
"solana-tpu-client",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -6019,6 +6058,7 @@ dependencies = [
|
||||||
"soketto",
|
"soketto",
|
||||||
"solana-account-decoder",
|
"solana-account-decoder",
|
||||||
"solana-address-lookup-table-program",
|
"solana-address-lookup-table-program",
|
||||||
|
"solana-client",
|
||||||
"solana-entry",
|
"solana-entry",
|
||||||
"solana-faucet",
|
"solana-faucet",
|
||||||
"solana-gossip",
|
"solana-gossip",
|
||||||
|
@ -6128,6 +6168,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"solana-account-decoder",
|
"solana-account-decoder",
|
||||||
|
"solana-client",
|
||||||
"solana-logger 1.15.0",
|
"solana-logger 1.15.0",
|
||||||
"solana-pubsub-client",
|
"solana-pubsub-client",
|
||||||
"solana-rpc",
|
"solana-rpc",
|
||||||
|
@ -6345,6 +6386,7 @@ version = "1.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"log",
|
"log",
|
||||||
|
"solana-client",
|
||||||
"solana-logger 1.15.0",
|
"solana-logger 1.15.0",
|
||||||
"solana-measure",
|
"solana-measure",
|
||||||
"solana-metrics",
|
"solana-metrics",
|
||||||
|
@ -6566,35 +6608,23 @@ dependencies = [
|
||||||
name = "solana-tpu-client"
|
name = "solana-tpu-client"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-mutex",
|
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bincode",
|
"bincode",
|
||||||
"crossbeam-channel",
|
|
||||||
"enum_dispatch",
|
|
||||||
"futures 0.3.24",
|
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"itertools",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"quinn",
|
|
||||||
"quinn-proto",
|
|
||||||
"quinn-udp",
|
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rand_chacha 0.2.2",
|
"rand_chacha 0.2.2",
|
||||||
"rayon",
|
"rayon",
|
||||||
"rustls 0.20.6",
|
|
||||||
"solana-logger 1.15.0",
|
"solana-logger 1.15.0",
|
||||||
"solana-measure",
|
"solana-measure",
|
||||||
"solana-metrics",
|
"solana-metrics",
|
||||||
"solana-net-utils",
|
"solana-net-utils",
|
||||||
"solana-perf",
|
|
||||||
"solana-pubsub-client",
|
"solana-pubsub-client",
|
||||||
"solana-rpc-client",
|
"solana-rpc-client",
|
||||||
"solana-rpc-client-api",
|
"solana-rpc-client-api",
|
||||||
"solana-sdk 1.15.0",
|
"solana-sdk 1.15.0",
|
||||||
"solana-streamer",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
@ -6654,9 +6684,13 @@ dependencies = [
|
||||||
name = "solana-udp-client"
|
name = "solana-udp-client"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"solana-net-utils",
|
"solana-net-utils",
|
||||||
|
"solana-sdk 1.15.0",
|
||||||
|
"solana-streamer",
|
||||||
"solana-tpu-client",
|
"solana-tpu-client",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -14,6 +14,7 @@ crossbeam-channel = "0.5"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
rand = "0.7.0"
|
rand = "0.7.0"
|
||||||
rayon = "1.5.3"
|
rayon = "1.5.3"
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-core = { path = "../core", version = "=1.15.0" }
|
solana-core = { path = "../core", version = "=1.15.0" }
|
||||||
solana-gossip = { path = "../gossip", version = "=1.15.0" }
|
solana-gossip = { path = "../gossip", version = "=1.15.0" }
|
||||||
solana-ledger = { path = "../ledger", version = "=1.15.0" }
|
solana-ledger = { path = "../ledger", version = "=1.15.0" }
|
||||||
|
|
|
@ -5,6 +5,7 @@ use {
|
||||||
log::*,
|
log::*,
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
rayon::prelude::*,
|
rayon::prelude::*,
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_core::banking_stage::BankingStage,
|
solana_core::banking_stage::BankingStage,
|
||||||
solana_gossip::cluster_info::{ClusterInfo, Node},
|
solana_gossip::cluster_info::{ClusterInfo, Node},
|
||||||
solana_ledger::{
|
solana_ledger::{
|
||||||
|
@ -28,7 +29,7 @@ use {
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_tpu_client::connection_cache::{ConnectionCache, DEFAULT_TPU_CONNECTION_POOL_SIZE},
|
solana_tpu_client::tpu_connection_cache::DEFAULT_TPU_CONNECTION_POOL_SIZE,
|
||||||
std::{
|
std::{
|
||||||
sync::{atomic::Ordering, Arc, RwLock},
|
sync::{atomic::Ordering, Arc, RwLock},
|
||||||
thread::sleep,
|
thread::sleep,
|
||||||
|
|
|
@ -18,7 +18,6 @@ solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-runtime = { path = "../runtime", version = "=1.15.0" }
|
solana-runtime = { path = "../runtime", version = "=1.15.0" }
|
||||||
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
||||||
solana-send-transaction-service = { path = "../send-transaction-service", version = "=1.15.0" }
|
solana-send-transaction-service = { path = "../send-transaction-service", version = "=1.15.0" }
|
||||||
solana-tpu-client = { path = "../tpu-client", version = "=1.15.0", default-features = false }
|
|
||||||
tarpc = { version = "0.29.0", features = ["full"] }
|
tarpc = { version = "0.29.0", features = ["full"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-serde = { version = "0.8", features = ["bincode"] }
|
tokio-serde = { version = "0.8", features = ["bincode"] }
|
||||||
|
|
|
@ -7,6 +7,7 @@ use {
|
||||||
BanksTransactionResultWithSimulation, TransactionConfirmationStatus, TransactionMetadata,
|
BanksTransactionResultWithSimulation, TransactionConfirmationStatus, TransactionMetadata,
|
||||||
TransactionSimulationDetails, TransactionStatus,
|
TransactionSimulationDetails, TransactionStatus,
|
||||||
},
|
},
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_runtime::{
|
solana_runtime::{
|
||||||
bank::{Bank, TransactionExecutionResult, TransactionSimulationResult},
|
bank::{Bank, TransactionExecutionResult, TransactionSimulationResult},
|
||||||
bank_forks::BankForks,
|
bank_forks::BankForks,
|
||||||
|
@ -28,7 +29,6 @@ use {
|
||||||
send_transaction_service::{SendTransactionService, TransactionInfo},
|
send_transaction_service::{SendTransactionService, TransactionInfo},
|
||||||
tpu_info::NullTpuInfo,
|
tpu_info::NullTpuInfo,
|
||||||
},
|
},
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
std::{
|
std::{
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
io,
|
io,
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
use {
|
use {
|
||||||
crate::banks_server::start_tcp_server,
|
crate::banks_server::start_tcp_server,
|
||||||
futures::{future::FutureExt, pin_mut, prelude::stream::StreamExt, select},
|
futures::{future::FutureExt, pin_mut, prelude::stream::StreamExt, select},
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_runtime::{bank_forks::BankForks, commitment::BlockCommitmentCache},
|
solana_runtime::{bank_forks::BankForks, commitment::BlockCommitmentCache},
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
std::{
|
std::{
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
sync::{
|
sync::{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::bench_tps_client::{BenchTpsClient, BenchTpsError, Result},
|
crate::bench_tps_client::{BenchTpsClient, BenchTpsError, Result},
|
||||||
|
solana_client::thin_client::ThinClient,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
client::{AsyncClient, Client, SyncClient},
|
client::{AsyncClient, Client, SyncClient},
|
||||||
|
@ -11,7 +12,6 @@ use {
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
},
|
},
|
||||||
solana_thin_client::thin_client::ThinClient,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl BenchTpsClient for ThinClient {
|
impl BenchTpsClient for ThinClient {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use {
|
use {
|
||||||
crate::bench_tps_client::{BenchTpsClient, BenchTpsError, Result},
|
crate::bench_tps_client::{BenchTpsClient, BenchTpsError, Result},
|
||||||
|
solana_client::tpu_client::TpuClient,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::Account, commitment_config::CommitmentConfig, epoch_info::EpochInfo, hash::Hash,
|
account::Account, commitment_config::CommitmentConfig, epoch_info::EpochInfo, hash::Hash,
|
||||||
message::Message, pubkey::Pubkey, signature::Signature, transaction::Transaction,
|
message::Message, pubkey::Pubkey, signature::Signature, transaction::Transaction,
|
||||||
},
|
},
|
||||||
solana_tpu_client::tpu_client::TpuClient,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl BenchTpsClient for TpuClient {
|
impl BenchTpsClient for TpuClient {
|
||||||
|
|
|
@ -8,7 +8,9 @@ use {
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{read_keypair_file, Keypair},
|
signature::{read_keypair_file, Keypair},
|
||||||
},
|
},
|
||||||
solana_tpu_client::connection_cache::{DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_USE_QUIC},
|
solana_tpu_client::tpu_connection_cache::{
|
||||||
|
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_USE_QUIC,
|
||||||
|
},
|
||||||
std::{net::SocketAddr, process::exit, time::Duration},
|
std::{net::SocketAddr, process::exit, time::Duration},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,11 @@ use {
|
||||||
keypairs::get_keypairs,
|
keypairs::get_keypairs,
|
||||||
send_batch::{generate_durable_nonce_accounts, generate_keypairs},
|
send_batch::{generate_durable_nonce_accounts, generate_keypairs},
|
||||||
},
|
},
|
||||||
|
solana_client::{
|
||||||
|
connection_cache::ConnectionCache,
|
||||||
|
thin_client::ThinClient,
|
||||||
|
tpu_client::{TpuClient, TpuClientConfig},
|
||||||
|
},
|
||||||
solana_genesis::Base64Account,
|
solana_genesis::Base64Account,
|
||||||
solana_gossip::gossip_service::{discover_cluster, get_client, get_multi_client},
|
solana_gossip::gossip_service::{discover_cluster, get_client, get_multi_client},
|
||||||
solana_rpc_client::rpc_client::RpcClient,
|
solana_rpc_client::rpc_client::RpcClient,
|
||||||
|
@ -18,11 +23,6 @@ use {
|
||||||
system_program,
|
system_program,
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_thin_client::thin_client::ThinClient,
|
|
||||||
solana_tpu_client::{
|
|
||||||
connection_cache::ConnectionCache,
|
|
||||||
tpu_client::{TpuClient, TpuClientConfig},
|
|
||||||
},
|
|
||||||
std::{
|
std::{
|
||||||
collections::HashMap, fs::File, io::prelude::*, net::SocketAddr, path::Path, process::exit,
|
collections::HashMap, fs::File, io::prelude::*, net::SocketAddr, path::Path, process::exit,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
|
|
@ -8,6 +8,11 @@ use {
|
||||||
send_batch::generate_durable_nonce_accounts,
|
send_batch::generate_durable_nonce_accounts,
|
||||||
spl_convert::FromOtherSolana,
|
spl_convert::FromOtherSolana,
|
||||||
},
|
},
|
||||||
|
solana_client::{
|
||||||
|
connection_cache::ConnectionCache,
|
||||||
|
thin_client::ThinClient,
|
||||||
|
tpu_client::{TpuClient, TpuClientConfig},
|
||||||
|
},
|
||||||
solana_core::validator::ValidatorConfig,
|
solana_core::validator::ValidatorConfig,
|
||||||
solana_faucet::faucet::run_local_faucet,
|
solana_faucet::faucet::run_local_faucet,
|
||||||
solana_local_cluster::{
|
solana_local_cluster::{
|
||||||
|
@ -25,11 +30,6 @@ use {
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_test_validator::TestValidatorGenesis,
|
solana_test_validator::TestValidatorGenesis,
|
||||||
solana_thin_client::thin_client::ThinClient,
|
|
||||||
solana_tpu_client::{
|
|
||||||
connection_cache::ConnectionCache,
|
|
||||||
tpu_client::{TpuClient, TpuClientConfig},
|
|
||||||
},
|
|
||||||
std::{sync::Arc, time::Duration},
|
std::{sync::Arc, time::Duration},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.15.
|
||||||
solana-clap-utils = { path = "../clap-utils", version = "=1.15.0" }
|
solana-clap-utils = { path = "../clap-utils", version = "=1.15.0" }
|
||||||
solana-cli-config = { path = "../cli-config", version = "=1.15.0" }
|
solana-cli-config = { path = "../cli-config", version = "=1.15.0" }
|
||||||
solana-cli-output = { path = "../cli-output", version = "=1.15.0" }
|
solana-cli-output = { path = "../cli-output", version = "=1.15.0" }
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-config-program = { path = "../programs/config", version = "=1.15.0" }
|
solana-config-program = { path = "../programs/config", version = "=1.15.0" }
|
||||||
solana-faucet = { path = "../faucet", version = "=1.15.0" }
|
solana-faucet = { path = "../faucet", version = "=1.15.0" }
|
||||||
solana-logger = { path = "../logger", version = "=1.15.0" }
|
solana-logger = { path = "../logger", version = "=1.15.0" }
|
||||||
|
|
|
@ -31,7 +31,7 @@ use {
|
||||||
stake::{instruction::LockupArgs, state::Lockup},
|
stake::{instruction::LockupArgs, state::Lockup},
|
||||||
transaction::{TransactionError, VersionedTransaction},
|
transaction::{TransactionError, VersionedTransaction},
|
||||||
},
|
},
|
||||||
solana_tpu_client::connection_cache::DEFAULT_TPU_ENABLE_UDP,
|
solana_tpu_client::tpu_connection_cache::DEFAULT_TPU_ENABLE_UDP,
|
||||||
solana_vote_program::vote_state::VoteAuthorize,
|
solana_vote_program::vote_state::VoteAuthorize,
|
||||||
std::{collections::HashMap, error, io::stdout, str::FromStr, sync::Arc, time::Duration},
|
std::{collections::HashMap, error, io::stdout, str::FromStr, sync::Arc, time::Duration},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
|
@ -17,6 +17,10 @@ use {
|
||||||
CliUpgradeableBuffer, CliUpgradeableBuffers, CliUpgradeableProgram,
|
CliUpgradeableBuffer, CliUpgradeableBuffers, CliUpgradeableProgram,
|
||||||
CliUpgradeableProgramClosed, CliUpgradeablePrograms,
|
CliUpgradeableProgramClosed, CliUpgradeablePrograms,
|
||||||
},
|
},
|
||||||
|
solana_client::{
|
||||||
|
connection_cache::ConnectionCache,
|
||||||
|
tpu_client::{TpuClient, TpuClientConfig},
|
||||||
|
},
|
||||||
solana_program_runtime::invoke_context::InvokeContext,
|
solana_program_runtime::invoke_context::InvokeContext,
|
||||||
solana_rbpf::{
|
solana_rbpf::{
|
||||||
elf::Executable,
|
elf::Executable,
|
||||||
|
@ -48,10 +52,6 @@ use {
|
||||||
transaction::{Transaction, TransactionError},
|
transaction::{Transaction, TransactionError},
|
||||||
transaction_context::TransactionContext,
|
transaction_context::TransactionContext,
|
||||||
},
|
},
|
||||||
solana_tpu_client::{
|
|
||||||
connection_cache::ConnectionCache,
|
|
||||||
tpu_client::{TpuClient, TpuClientConfig},
|
|
||||||
},
|
|
||||||
std::{
|
std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
|
|
|
@ -10,17 +10,35 @@ license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1.57"
|
||||||
|
bincode = "1.3.3"
|
||||||
|
enum_dispatch = "0.3.8"
|
||||||
|
futures = "0.3"
|
||||||
|
futures-util = "0.3.21"
|
||||||
|
indexmap = "1.9.1"
|
||||||
|
indicatif = { version = "0.17.1" }
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
rand = "0.7.0"
|
||||||
|
rayon = "1.5.3"
|
||||||
solana-measure = { path = "../measure", version = "=1.15.0" }
|
solana-measure = { path = "../measure", version = "=1.15.0" }
|
||||||
|
solana-metrics = { path = "../metrics", version = "=1.15.0" }
|
||||||
|
solana-net-utils = { path = "../net-utils", version = "=1.15.0" }
|
||||||
solana-pubsub-client = { path = "../pubsub-client", version = "=1.15.0" }
|
solana-pubsub-client = { path = "../pubsub-client", version = "=1.15.0" }
|
||||||
|
solana-quic-client = { path = "../quic-client", version = "=1.15.0" }
|
||||||
solana-rpc-client = { path = "../rpc-client", version = "=1.15.0" }
|
solana-rpc-client = { path = "../rpc-client", version = "=1.15.0" }
|
||||||
solana-rpc-client-api = { path = "../rpc-client-api", version = "=1.15.0" }
|
solana-rpc-client-api = { path = "../rpc-client-api", version = "=1.15.0" }
|
||||||
solana-rpc-client-nonce-utils = { path = "../rpc-client-nonce-utils", version = "=1.15.0" }
|
solana-rpc-client-nonce-utils = { path = "../rpc-client-nonce-utils", version = "=1.15.0" }
|
||||||
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
||||||
|
solana-streamer = { path = "../streamer", version = "=1.15.0" }
|
||||||
solana-thin-client = { path = "../thin-client", version = "=1.15.0" }
|
solana-thin-client = { path = "../thin-client", version = "=1.15.0" }
|
||||||
solana-tpu-client = { path = "../tpu-client", version = "=1.15.0" }
|
solana-tpu-client = { path = "../tpu-client", version = "=1.15.0" }
|
||||||
|
solana-udp-client = { path = "../udp-client", version = "=1.15.0" }
|
||||||
|
thiserror = "1.0"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
rand_chacha = "0.2.2"
|
||||||
|
solana-logger = { path = "../logger", version = "=1.15.0" }
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
targets = ["x86_64-unknown-linux-gnu"]
|
targets = ["x86_64-unknown-linux-gnu"]
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
pub use crate::tpu_connection_cache::{
|
pub use solana_tpu_client::tpu_connection_cache::{
|
||||||
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
|
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
|
||||||
};
|
};
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
connection_cache_stats::{ConnectionCacheStats, CONNECTION_STAT_SUBMISSION_INTERVAL},
|
nonblocking::tpu_connection::NonblockingConnection, tpu_connection::BlockingConnection,
|
||||||
nonblocking::{
|
|
||||||
quic_client::{QuicClient, QuicClientCertificate, QuicLazyInitializedEndpoint},
|
|
||||||
tpu_connection::NonblockingConnection,
|
|
||||||
},
|
|
||||||
tpu_connection::BlockingConnection,
|
|
||||||
tpu_connection_cache::MAX_CONNECTIONS,
|
|
||||||
},
|
},
|
||||||
indexmap::map::{Entry, IndexMap},
|
indexmap::map::{Entry, IndexMap},
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
solana_measure::measure::Measure,
|
solana_measure::measure::Measure,
|
||||||
|
solana_quic_client::nonblocking::quic_client::{
|
||||||
|
QuicClient, QuicClientCertificate, QuicLazyInitializedEndpoint,
|
||||||
|
},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
pubkey::Pubkey, quic::QUIC_PORT_OFFSET, signature::Keypair, timing::AtomicInterval,
|
pubkey::Pubkey, quic::QUIC_PORT_OFFSET, signature::Keypair, timing::AtomicInterval,
|
||||||
},
|
},
|
||||||
|
@ -22,6 +19,10 @@ use {
|
||||||
streamer::StakedNodes,
|
streamer::StakedNodes,
|
||||||
tls_certificates::new_self_signed_tls_certificate_chain,
|
tls_certificates::new_self_signed_tls_certificate_chain,
|
||||||
},
|
},
|
||||||
|
solana_tpu_client::{
|
||||||
|
connection_cache_stats::{ConnectionCacheStats, CONNECTION_STAT_SUBMISSION_INTERVAL},
|
||||||
|
tpu_connection_cache::MAX_CONNECTIONS,
|
||||||
|
},
|
||||||
std::{
|
std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
|
@ -1,6 +1,16 @@
|
||||||
#![allow(clippy::integer_arithmetic)]
|
#![allow(clippy::integer_arithmetic)]
|
||||||
|
|
||||||
|
pub mod connection_cache;
|
||||||
|
pub mod nonblocking;
|
||||||
|
pub mod quic_client;
|
||||||
|
pub mod thin_client;
|
||||||
|
pub mod tpu_client;
|
||||||
|
pub mod tpu_connection;
|
||||||
pub mod transaction_executor;
|
pub mod transaction_executor;
|
||||||
|
pub mod udp_client;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate solana_metrics;
|
||||||
|
|
||||||
pub use solana_rpc_client::mock_sender_for_cli;
|
pub use solana_rpc_client::mock_sender_for_cli;
|
||||||
|
|
||||||
|
@ -12,50 +22,6 @@ pub mod client_error {
|
||||||
reqwest, Error as ClientError, ErrorKind as ClientErrorKind, Result,
|
reqwest, Error as ClientError, ErrorKind as ClientErrorKind, Result,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub mod connection_cache {
|
|
||||||
pub use solana_tpu_client::connection_cache::*;
|
|
||||||
}
|
|
||||||
pub mod nonblocking {
|
|
||||||
pub mod blockhash_query {
|
|
||||||
pub use solana_rpc_client_nonce_utils::nonblocking::blockhash_query::*;
|
|
||||||
}
|
|
||||||
/// Durable transaction nonce helpers.
|
|
||||||
pub mod nonce_utils {
|
|
||||||
pub use solana_rpc_client_nonce_utils::nonblocking::*;
|
|
||||||
}
|
|
||||||
pub mod pubsub_client {
|
|
||||||
pub use solana_pubsub_client::nonblocking::pubsub_client::*;
|
|
||||||
}
|
|
||||||
/// Simple nonblocking client that connects to a given UDP port with the QUIC protocol
|
|
||||||
/// and provides an interface for sending transactions which is restricted by the
|
|
||||||
/// server's flow control.
|
|
||||||
pub mod quic_client {
|
|
||||||
pub use solana_tpu_client::nonblocking::quic_client::*;
|
|
||||||
}
|
|
||||||
/// Communication with a Solana node over RPC asynchronously .
|
|
||||||
///
|
|
||||||
/// Software that interacts with the Solana blockchain, whether querying its
|
|
||||||
/// state or submitting transactions, communicates with a Solana node over
|
|
||||||
/// [JSON-RPC], using the [`RpcClient`] type.
|
|
||||||
///
|
|
||||||
/// [JSON-RPC]: https://www.jsonrpc.org/specification
|
|
||||||
/// [`RpcClient`]: crate::nonblocking::rpc_client::RpcClient
|
|
||||||
pub mod rpc_client {
|
|
||||||
pub use solana_rpc_client::nonblocking::rpc_client::*;
|
|
||||||
}
|
|
||||||
pub mod tpu_client {
|
|
||||||
pub use solana_tpu_client::nonblocking::tpu_client::*;
|
|
||||||
}
|
|
||||||
/// Trait defining async send functions, to be used for UDP or QUIC sending
|
|
||||||
pub mod tpu_connection {
|
|
||||||
pub use solana_tpu_client::nonblocking::tpu_connection::*;
|
|
||||||
}
|
|
||||||
/// Simple UDP client that communicates with the given UDP port with UDP and provides
|
|
||||||
/// an interface for sending transactions
|
|
||||||
pub mod udp_client {
|
|
||||||
pub use solana_tpu_client::nonblocking::udp_client::*;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Durable transaction nonce helpers.
|
/// Durable transaction nonce helpers.
|
||||||
pub mod nonce_utils {
|
pub mod nonce_utils {
|
||||||
pub use solana_rpc_client_nonce_utils::*;
|
pub use solana_rpc_client_nonce_utils::*;
|
||||||
|
@ -63,11 +29,6 @@ pub mod nonce_utils {
|
||||||
pub mod pubsub_client {
|
pub mod pubsub_client {
|
||||||
pub use solana_pubsub_client::pubsub_client::*;
|
pub use solana_pubsub_client::pubsub_client::*;
|
||||||
}
|
}
|
||||||
/// Simple client that connects to a given UDP port with the QUIC protocol and provides
|
|
||||||
/// an interface for sending transactions which is restricted by the server's flow control.
|
|
||||||
pub mod quic_client {
|
|
||||||
pub use solana_tpu_client::quic_client::*;
|
|
||||||
}
|
|
||||||
/// Communication with a Solana node over RPC.
|
/// Communication with a Solana node over RPC.
|
||||||
///
|
///
|
||||||
/// Software that interacts with the Solana blockchain, whether querying its
|
/// Software that interacts with the Solana blockchain, whether querying its
|
||||||
|
@ -102,21 +63,3 @@ pub mod rpc_response {
|
||||||
pub mod rpc_sender {
|
pub mod rpc_sender {
|
||||||
pub use solana_rpc_client::rpc_sender::*;
|
pub use solana_rpc_client::rpc_sender::*;
|
||||||
}
|
}
|
||||||
/// The `thin_client` module is a client-side object that interfaces with
|
|
||||||
/// a server-side TPU. Client code should use this object instead of writing
|
|
||||||
/// messages to the network directly. The binary encoding of its messages are
|
|
||||||
/// unstable and may change in future releases.
|
|
||||||
pub mod thin_client {
|
|
||||||
pub use solana_thin_client::thin_client::*;
|
|
||||||
}
|
|
||||||
pub mod tpu_client {
|
|
||||||
pub use solana_tpu_client::tpu_client::*;
|
|
||||||
}
|
|
||||||
pub mod tpu_connection {
|
|
||||||
pub use solana_tpu_client::tpu_connection::*;
|
|
||||||
}
|
|
||||||
/// Simple TPU client that communicates with the given UDP port with UDP and provides
|
|
||||||
/// an interface for sending transactions
|
|
||||||
pub mod udp_client {
|
|
||||||
pub use solana_tpu_client::udp_client::*;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
pub mod quic_client;
|
||||||
|
pub mod tpu_client;
|
||||||
|
pub mod tpu_connection;
|
||||||
|
pub mod udp_client;
|
||||||
|
|
||||||
|
pub mod blockhash_query {
|
||||||
|
pub use solana_rpc_client_nonce_utils::nonblocking::blockhash_query::*;
|
||||||
|
}
|
||||||
|
/// Durable transaction nonce helpers.
|
||||||
|
pub mod nonce_utils {
|
||||||
|
pub use solana_rpc_client_nonce_utils::nonblocking::*;
|
||||||
|
}
|
||||||
|
pub mod pubsub_client {
|
||||||
|
pub use solana_pubsub_client::nonblocking::pubsub_client::*;
|
||||||
|
}
|
||||||
|
/// Communication with a Solana node over RPC asynchronously .
|
||||||
|
///
|
||||||
|
/// Software that interacts with the Solana blockchain, whether querying its
|
||||||
|
/// state or submitting transactions, communicates with a Solana node over
|
||||||
|
/// [JSON-RPC], using the [`RpcClient`] type.
|
||||||
|
///
|
||||||
|
/// [JSON-RPC]: https://www.jsonrpc.org/specification
|
||||||
|
/// [`RpcClient`]: crate::nonblocking::rpc_client::RpcClient
|
||||||
|
pub mod rpc_client {
|
||||||
|
pub use solana_rpc_client::nonblocking::rpc_client::*;
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
//! Simple nonblocking client that connects to a given UDP port with the QUIC protocol
|
||||||
|
//! and provides an interface for sending transactions which is restricted by the
|
||||||
|
//! server's flow control.
|
||||||
|
pub use solana_quic_client::nonblocking::quic_client::{
|
||||||
|
QuicClient, QuicClientCertificate, QuicError, QuicLazyInitializedEndpoint, QuicTpuConnection,
|
||||||
|
};
|
||||||
|
use {
|
||||||
|
crate::nonblocking::tpu_connection::TpuConnection,
|
||||||
|
async_trait::async_trait,
|
||||||
|
log::*,
|
||||||
|
solana_sdk::transport::Result as TransportResult,
|
||||||
|
solana_tpu_client::tpu_connection::ClientStats,
|
||||||
|
std::{net::SocketAddr, sync::Arc},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl TpuConnection for QuicTpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
self.client.tpu_addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
let stats = ClientStats::default();
|
||||||
|
let len = buffers.len();
|
||||||
|
let res = self
|
||||||
|
.client
|
||||||
|
.send_batch(buffers, &stats, self.connection_stats.clone())
|
||||||
|
.await;
|
||||||
|
self.connection_stats
|
||||||
|
.add_client_stats(&stats, len, res.is_ok());
|
||||||
|
res?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
let stats = Arc::new(ClientStats::default());
|
||||||
|
let send_buffer =
|
||||||
|
self.client
|
||||||
|
.send_buffer(wire_transaction, &stats, self.connection_stats.clone());
|
||||||
|
if let Err(e) = send_buffer.await {
|
||||||
|
warn!(
|
||||||
|
"Failed to send transaction async to {}, error: {:?} ",
|
||||||
|
self.tpu_addr(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
datapoint_warn!("send-wire-async", ("failure", 1, i64),);
|
||||||
|
self.connection_stats.add_client_stats(&stats, 1, false);
|
||||||
|
} else {
|
||||||
|
self.connection_stats.add_client_stats(&stats, 1, true);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,343 @@
|
||||||
|
pub use solana_tpu_client::nonblocking::tpu_client::{LeaderTpuService, TpuSenderError};
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
connection_cache::ConnectionCache,
|
||||||
|
nonblocking::tpu_connection::TpuConnection,
|
||||||
|
tpu_client::{TpuClientConfig, MAX_FANOUT_SLOTS},
|
||||||
|
},
|
||||||
|
bincode::serialize,
|
||||||
|
futures_util::future::join_all,
|
||||||
|
solana_rpc_client::{nonblocking::rpc_client::RpcClient, spinner},
|
||||||
|
solana_rpc_client_api::request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
||||||
|
solana_sdk::{
|
||||||
|
message::Message,
|
||||||
|
signers::Signers,
|
||||||
|
transaction::{Transaction, TransactionError},
|
||||||
|
transport::{Result as TransportResult, TransportError},
|
||||||
|
},
|
||||||
|
solana_tpu_client::{
|
||||||
|
nonblocking::tpu_client::temporary_pub::*,
|
||||||
|
tpu_client::temporary_pub::{SEND_TRANSACTION_INTERVAL, TRANSACTION_RESEND_INTERVAL},
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
collections::HashMap,
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tokio::time::{sleep, Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Client which sends transactions directly to the current leader's TPU port over UDP.
|
||||||
|
/// The client uses RPC to determine the current leader and fetch node contact info
|
||||||
|
pub struct TpuClient {
|
||||||
|
fanout_slots: u64,
|
||||||
|
leader_tpu_service: LeaderTpuService,
|
||||||
|
exit: Arc<AtomicBool>,
|
||||||
|
rpc_client: Arc<RpcClient>,
|
||||||
|
connection_cache: Arc<ConnectionCache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction_to_addr(
|
||||||
|
connection_cache: &ConnectionCache,
|
||||||
|
addr: &SocketAddr,
|
||||||
|
wire_transaction: Vec<u8>,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let conn = connection_cache.get_nonblocking_connection(addr);
|
||||||
|
conn.send_wire_transaction(wire_transaction.clone()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction_batch_to_addr(
|
||||||
|
connection_cache: &ConnectionCache,
|
||||||
|
addr: &SocketAddr,
|
||||||
|
wire_transactions: &[Vec<u8>],
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let conn = connection_cache.get_nonblocking_connection(addr);
|
||||||
|
conn.send_wire_transaction_batch(wire_transactions).await
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TpuClient {
|
||||||
|
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
|
||||||
|
/// size
|
||||||
|
pub async fn send_transaction(&self, transaction: &Transaction) -> bool {
|
||||||
|
let wire_transaction = serialize(transaction).expect("serialization should succeed");
|
||||||
|
self.send_wire_transaction(wire_transaction).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
|
||||||
|
pub async fn send_wire_transaction(&self, wire_transaction: Vec<u8>) -> bool {
|
||||||
|
self.try_send_wire_transaction(wire_transaction)
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
|
||||||
|
/// size
|
||||||
|
/// Returns the last error if all sends fail
|
||||||
|
pub async fn try_send_transaction(&self, transaction: &Transaction) -> TransportResult<()> {
|
||||||
|
let wire_transaction = serialize(transaction).expect("serialization should succeed");
|
||||||
|
self.try_send_wire_transaction(wire_transaction).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
|
||||||
|
/// Returns the last error if all sends fail
|
||||||
|
pub async fn try_send_wire_transaction(
|
||||||
|
&self,
|
||||||
|
wire_transaction: Vec<u8>,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let leaders = self
|
||||||
|
.leader_tpu_service
|
||||||
|
.leader_tpu_sockets(self.fanout_slots);
|
||||||
|
let futures = leaders
|
||||||
|
.iter()
|
||||||
|
.map(|addr| {
|
||||||
|
send_wire_transaction_to_addr(
|
||||||
|
&self.connection_cache,
|
||||||
|
addr,
|
||||||
|
wire_transaction.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let results: Vec<TransportResult<()>> = join_all(futures).await;
|
||||||
|
|
||||||
|
let mut last_error: Option<TransportError> = None;
|
||||||
|
let mut some_success = false;
|
||||||
|
for result in results {
|
||||||
|
if let Err(e) = result {
|
||||||
|
if last_error.is_none() {
|
||||||
|
last_error = Some(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
some_success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !some_success {
|
||||||
|
Err(if let Some(err) = last_error {
|
||||||
|
err
|
||||||
|
} else {
|
||||||
|
std::io::Error::new(std::io::ErrorKind::Other, "No sends attempted").into()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a batch of wire transactions to the current and upcoming leader TPUs according to
|
||||||
|
/// fanout size
|
||||||
|
/// Returns the last error if all sends fail
|
||||||
|
pub async fn try_send_wire_transaction_batch(
|
||||||
|
&self,
|
||||||
|
wire_transactions: Vec<Vec<u8>>,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let leaders = self
|
||||||
|
.leader_tpu_service
|
||||||
|
.leader_tpu_sockets(self.fanout_slots);
|
||||||
|
let futures = leaders
|
||||||
|
.iter()
|
||||||
|
.map(|addr| {
|
||||||
|
send_wire_transaction_batch_to_addr(
|
||||||
|
&self.connection_cache,
|
||||||
|
addr,
|
||||||
|
&wire_transactions,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let results: Vec<TransportResult<()>> = join_all(futures).await;
|
||||||
|
|
||||||
|
let mut last_error: Option<TransportError> = None;
|
||||||
|
let mut some_success = false;
|
||||||
|
for result in results {
|
||||||
|
if let Err(e) = result {
|
||||||
|
if last_error.is_none() {
|
||||||
|
last_error = Some(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
some_success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !some_success {
|
||||||
|
Err(if let Some(err) = last_error {
|
||||||
|
err
|
||||||
|
} else {
|
||||||
|
std::io::Error::new(std::io::ErrorKind::Other, "No sends attempted").into()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new client that disconnects when dropped
|
||||||
|
pub async fn new(
|
||||||
|
rpc_client: Arc<RpcClient>,
|
||||||
|
websocket_url: &str,
|
||||||
|
config: TpuClientConfig,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let connection_cache = Arc::new(ConnectionCache::default());
|
||||||
|
Self::new_with_connection_cache(rpc_client, websocket_url, config, connection_cache).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new client that disconnects when dropped
|
||||||
|
pub async fn new_with_connection_cache(
|
||||||
|
rpc_client: Arc<RpcClient>,
|
||||||
|
websocket_url: &str,
|
||||||
|
config: TpuClientConfig,
|
||||||
|
connection_cache: Arc<ConnectionCache>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
|
let leader_tpu_service =
|
||||||
|
LeaderTpuService::new(rpc_client.clone(), websocket_url, exit.clone()).await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
fanout_slots: config.fanout_slots.clamp(1, MAX_FANOUT_SLOTS),
|
||||||
|
leader_tpu_service,
|
||||||
|
exit,
|
||||||
|
rpc_client,
|
||||||
|
connection_cache,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_and_confirm_messages_with_spinner<T: Signers>(
|
||||||
|
&self,
|
||||||
|
messages: &[Message],
|
||||||
|
signers: &T,
|
||||||
|
) -> Result<Vec<Option<TransactionError>>> {
|
||||||
|
let mut expired_blockhash_retries = 5;
|
||||||
|
let progress_bar = spinner::new_progress_bar();
|
||||||
|
progress_bar.set_message("Setting up...");
|
||||||
|
|
||||||
|
let mut transactions = messages
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, message)| (i, Transaction::new_unsigned(message.clone())))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let total_transactions = transactions.len();
|
||||||
|
let mut transaction_errors = vec![None; transactions.len()];
|
||||||
|
let mut confirmed_transactions = 0;
|
||||||
|
let mut block_height = self.rpc_client.get_block_height().await?;
|
||||||
|
while expired_blockhash_retries > 0 {
|
||||||
|
let (blockhash, last_valid_block_height) = self
|
||||||
|
.rpc_client
|
||||||
|
.get_latest_blockhash_with_commitment(self.rpc_client.commitment())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut pending_transactions = HashMap::new();
|
||||||
|
for (i, mut transaction) in transactions {
|
||||||
|
transaction.try_sign(signers, blockhash)?;
|
||||||
|
pending_transactions.insert(transaction.signatures[0], (i, transaction));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut last_resend = Instant::now() - TRANSACTION_RESEND_INTERVAL;
|
||||||
|
while block_height <= last_valid_block_height {
|
||||||
|
let num_transactions = pending_transactions.len();
|
||||||
|
|
||||||
|
// Periodically re-send all pending transactions
|
||||||
|
if Instant::now().duration_since(last_resend) > TRANSACTION_RESEND_INTERVAL {
|
||||||
|
for (index, (_i, transaction)) in pending_transactions.values().enumerate() {
|
||||||
|
if !self.send_transaction(transaction).await {
|
||||||
|
let _result = self.rpc_client.send_transaction(transaction).await.ok();
|
||||||
|
}
|
||||||
|
set_message_for_confirmed_transactions(
|
||||||
|
&progress_bar,
|
||||||
|
confirmed_transactions,
|
||||||
|
total_transactions,
|
||||||
|
None, //block_height,
|
||||||
|
last_valid_block_height,
|
||||||
|
&format!("Sending {}/{} transactions", index + 1, num_transactions,),
|
||||||
|
);
|
||||||
|
sleep(SEND_TRANSACTION_INTERVAL).await;
|
||||||
|
}
|
||||||
|
last_resend = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the next block before checking for transaction statuses
|
||||||
|
let mut block_height_refreshes = 10;
|
||||||
|
set_message_for_confirmed_transactions(
|
||||||
|
&progress_bar,
|
||||||
|
confirmed_transactions,
|
||||||
|
total_transactions,
|
||||||
|
Some(block_height),
|
||||||
|
last_valid_block_height,
|
||||||
|
&format!(
|
||||||
|
"Waiting for next block, {} transactions pending...",
|
||||||
|
num_transactions
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let mut new_block_height = block_height;
|
||||||
|
while block_height == new_block_height && block_height_refreshes > 0 {
|
||||||
|
sleep(Duration::from_millis(500)).await;
|
||||||
|
new_block_height = self.rpc_client.get_block_height().await?;
|
||||||
|
block_height_refreshes -= 1;
|
||||||
|
}
|
||||||
|
block_height = new_block_height;
|
||||||
|
|
||||||
|
// Collect statuses for the transactions, drop those that are confirmed
|
||||||
|
let pending_signatures = pending_transactions.keys().cloned().collect::<Vec<_>>();
|
||||||
|
for pending_signatures_chunk in
|
||||||
|
pending_signatures.chunks(MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS)
|
||||||
|
{
|
||||||
|
if let Ok(result) = self
|
||||||
|
.rpc_client
|
||||||
|
.get_signature_statuses(pending_signatures_chunk)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let statuses = result.value;
|
||||||
|
for (signature, status) in
|
||||||
|
pending_signatures_chunk.iter().zip(statuses.into_iter())
|
||||||
|
{
|
||||||
|
if let Some(status) = status {
|
||||||
|
if status.satisfies_commitment(self.rpc_client.commitment()) {
|
||||||
|
if let Some((i, _)) = pending_transactions.remove(signature) {
|
||||||
|
confirmed_transactions += 1;
|
||||||
|
if status.err.is_some() {
|
||||||
|
progress_bar.println(format!(
|
||||||
|
"Failed transaction: {:?}",
|
||||||
|
status
|
||||||
|
));
|
||||||
|
}
|
||||||
|
transaction_errors[i] = status.err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_message_for_confirmed_transactions(
|
||||||
|
&progress_bar,
|
||||||
|
confirmed_transactions,
|
||||||
|
total_transactions,
|
||||||
|
Some(block_height),
|
||||||
|
last_valid_block_height,
|
||||||
|
"Checking transaction status...",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if pending_transactions.is_empty() {
|
||||||
|
return Ok(transaction_errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions = pending_transactions.into_values().collect();
|
||||||
|
progress_bar.println(format!(
|
||||||
|
"Blockhash expired. {} retries remaining",
|
||||||
|
expired_blockhash_retries
|
||||||
|
));
|
||||||
|
expired_blockhash_retries -= 1;
|
||||||
|
}
|
||||||
|
Err(TpuSenderError::Custom("Max retries exceeded".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rpc_client(&self) -> &RpcClient {
|
||||||
|
&self.rpc_client
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn shutdown(&mut self) {
|
||||||
|
self.exit.store(true, Ordering::Relaxed);
|
||||||
|
self.leader_tpu_service.join().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for TpuClient {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.exit.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
//! Trait defining async send functions, to be used for UDP or QUIC sending
|
||||||
|
|
||||||
|
use {
|
||||||
|
async_trait::async_trait,
|
||||||
|
enum_dispatch::enum_dispatch,
|
||||||
|
solana_quic_client::nonblocking::quic_client::QuicTpuConnection,
|
||||||
|
solana_sdk::{transaction::VersionedTransaction, transport::Result as TransportResult},
|
||||||
|
solana_udp_client::nonblocking::udp_client::UdpTpuConnection,
|
||||||
|
std::net::SocketAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Due to the existence of `crate::connection_cache::Connection`, if this is named
|
||||||
|
// `Connection`, enum_dispatch gets confused between the two and throws errors when
|
||||||
|
// trying to convert later.
|
||||||
|
#[enum_dispatch]
|
||||||
|
pub enum NonblockingConnection {
|
||||||
|
QuicTpuConnection,
|
||||||
|
UdpTpuConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
#[enum_dispatch(NonblockingConnection)]
|
||||||
|
pub trait TpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr;
|
||||||
|
|
||||||
|
async fn serialize_and_send_transaction(
|
||||||
|
&self,
|
||||||
|
transaction: &VersionedTransaction,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let wire_transaction =
|
||||||
|
bincode::serialize(transaction).expect("serialize Transaction in send_batch");
|
||||||
|
self.send_wire_transaction(&wire_transaction).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync;
|
||||||
|
|
||||||
|
async fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
//! Simple UDP client that communicates with the given UDP port with UDP and provides
|
||||||
|
//! an interface for sending transactions
|
||||||
|
|
||||||
|
pub use solana_udp_client::nonblocking::udp_client::UdpTpuConnection;
|
||||||
|
use {
|
||||||
|
crate::nonblocking::tpu_connection::TpuConnection, async_trait::async_trait,
|
||||||
|
core::iter::repeat, solana_sdk::transport::Result as TransportResult,
|
||||||
|
solana_streamer::nonblocking::sendmmsg::batch_send, std::net::SocketAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl TpuConnection for UdpTpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
&self.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
self.socket
|
||||||
|
.send_to(wire_transaction.as_ref(), self.addr)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
let pkts: Vec<_> = buffers.iter().zip(repeat(self.tpu_addr())).collect();
|
||||||
|
batch_send(&self.socket, &pkts).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
//! Simple client that connects to a given UDP port with the QUIC protocol and provides
|
||||||
|
//! an interface for sending transactions which is restricted by the server's flow control.
|
||||||
|
|
||||||
|
pub use solana_quic_client::quic_client::QuicTpuConnection;
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
nonblocking::tpu_connection::TpuConnection as NonblockingTpuConnection,
|
||||||
|
tpu_connection::TpuConnection,
|
||||||
|
},
|
||||||
|
solana_quic_client::quic_client::temporary_pub::*,
|
||||||
|
solana_sdk::transport::Result as TransportResult,
|
||||||
|
std::net::SocketAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl TpuConnection for QuicTpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
self.inner.tpu_addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
RUNTIME.block_on(self.inner.send_wire_transaction_batch(buffers))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_async(&self, wire_transaction: Vec<u8>) -> TransportResult<()> {
|
||||||
|
let _lock = ASYNC_TASK_SEMAPHORE.acquire();
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
|
||||||
|
let _ = RUNTIME
|
||||||
|
.spawn(async move { send_wire_transaction_async(inner, wire_transaction).await });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_batch_async(&self, buffers: Vec<Vec<u8>>) -> TransportResult<()> {
|
||||||
|
let _lock = ASYNC_TASK_SEMAPHORE.acquire();
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
let _ =
|
||||||
|
RUNTIME.spawn(async move { send_wire_transaction_batch_async(inner, buffers).await });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,546 @@
|
||||||
|
//! The `thin_client` module is a client-side object that interfaces with
|
||||||
|
//! a server-side TPU. Client code should use this object instead of writing
|
||||||
|
//! messages to the network directly. The binary encoding of its messages are
|
||||||
|
//! unstable and may change in future releases.
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
||||||
|
log::*,
|
||||||
|
solana_rpc_client::rpc_client::RpcClient,
|
||||||
|
solana_rpc_client_api::{config::RpcProgramAccountsConfig, response::Response},
|
||||||
|
solana_sdk::{
|
||||||
|
account::Account,
|
||||||
|
client::{AsyncClient, Client, SyncClient},
|
||||||
|
clock::{Slot, MAX_PROCESSING_AGE},
|
||||||
|
commitment_config::CommitmentConfig,
|
||||||
|
epoch_info::EpochInfo,
|
||||||
|
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||||
|
hash::Hash,
|
||||||
|
instruction::Instruction,
|
||||||
|
message::Message,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signature::{Keypair, Signature, Signer},
|
||||||
|
signers::Signers,
|
||||||
|
system_instruction,
|
||||||
|
timing::duration_as_ms,
|
||||||
|
transaction::{self, Transaction, VersionedTransaction},
|
||||||
|
transport::Result as TransportResult,
|
||||||
|
},
|
||||||
|
solana_thin_client::thin_client::temporary_pub::*,
|
||||||
|
std::{
|
||||||
|
io,
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::Arc,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An object for querying and sending transactions to the network.
|
||||||
|
pub struct ThinClient {
|
||||||
|
rpc_clients: Vec<RpcClient>,
|
||||||
|
tpu_addrs: Vec<SocketAddr>,
|
||||||
|
optimizer: ClientOptimizer,
|
||||||
|
connection_cache: Arc<ConnectionCache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThinClient {
|
||||||
|
/// Create a new ThinClient that will interface with the Rpc at `rpc_addr` using TCP
|
||||||
|
/// and the Tpu at `tpu_addr` over `transactions_socket` using Quic or UDP
|
||||||
|
/// (currently hardcoded to UDP)
|
||||||
|
pub fn new(
|
||||||
|
rpc_addr: SocketAddr,
|
||||||
|
tpu_addr: SocketAddr,
|
||||||
|
connection_cache: Arc<ConnectionCache>,
|
||||||
|
) -> Self {
|
||||||
|
Self::new_from_client(RpcClient::new_socket(rpc_addr), tpu_addr, connection_cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_socket_with_timeout(
|
||||||
|
rpc_addr: SocketAddr,
|
||||||
|
tpu_addr: SocketAddr,
|
||||||
|
timeout: Duration,
|
||||||
|
connection_cache: Arc<ConnectionCache>,
|
||||||
|
) -> Self {
|
||||||
|
let rpc_client = RpcClient::new_socket_with_timeout(rpc_addr, timeout);
|
||||||
|
Self::new_from_client(rpc_client, tpu_addr, connection_cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_from_client(
|
||||||
|
rpc_client: RpcClient,
|
||||||
|
tpu_addr: SocketAddr,
|
||||||
|
connection_cache: Arc<ConnectionCache>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
rpc_clients: vec![rpc_client],
|
||||||
|
tpu_addrs: vec![tpu_addr],
|
||||||
|
optimizer: ClientOptimizer::new(0),
|
||||||
|
connection_cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_addrs(
|
||||||
|
rpc_addrs: Vec<SocketAddr>,
|
||||||
|
tpu_addrs: Vec<SocketAddr>,
|
||||||
|
connection_cache: Arc<ConnectionCache>,
|
||||||
|
) -> Self {
|
||||||
|
assert!(!rpc_addrs.is_empty());
|
||||||
|
assert_eq!(rpc_addrs.len(), tpu_addrs.len());
|
||||||
|
|
||||||
|
let rpc_clients: Vec<_> = rpc_addrs.into_iter().map(RpcClient::new_socket).collect();
|
||||||
|
let optimizer = ClientOptimizer::new(rpc_clients.len());
|
||||||
|
Self {
|
||||||
|
rpc_clients,
|
||||||
|
tpu_addrs,
|
||||||
|
optimizer,
|
||||||
|
connection_cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
&self.tpu_addrs[self.optimizer.best()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rpc_client(&self) -> &RpcClient {
|
||||||
|
&self.rpc_clients[self.optimizer.best()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retry a sending a signed Transaction to the server for processing.
|
||||||
|
pub fn retry_transfer_until_confirmed(
|
||||||
|
&self,
|
||||||
|
keypair: &Keypair,
|
||||||
|
transaction: &mut Transaction,
|
||||||
|
tries: usize,
|
||||||
|
min_confirmed_blocks: usize,
|
||||||
|
) -> TransportResult<Signature> {
|
||||||
|
self.send_and_confirm_transaction(&[keypair], transaction, tries, min_confirmed_blocks)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retry sending a signed Transaction with one signing Keypair to the server for processing.
|
||||||
|
pub fn retry_transfer(
|
||||||
|
&self,
|
||||||
|
keypair: &Keypair,
|
||||||
|
transaction: &mut Transaction,
|
||||||
|
tries: usize,
|
||||||
|
) -> TransportResult<Signature> {
|
||||||
|
self.send_and_confirm_transaction(&[keypair], transaction, tries, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_and_confirm_transaction<T: Signers>(
|
||||||
|
&self,
|
||||||
|
keypairs: &T,
|
||||||
|
transaction: &mut Transaction,
|
||||||
|
tries: usize,
|
||||||
|
pending_confirmations: usize,
|
||||||
|
) -> TransportResult<Signature> {
|
||||||
|
for x in 0..tries {
|
||||||
|
let now = Instant::now();
|
||||||
|
let mut num_confirmed = 0;
|
||||||
|
let mut wait_time = MAX_PROCESSING_AGE;
|
||||||
|
// resend the same transaction until the transaction has no chance of succeeding
|
||||||
|
let wire_transaction =
|
||||||
|
bincode::serialize(&transaction).expect("transaction serialization failed");
|
||||||
|
while now.elapsed().as_secs() < wait_time as u64 {
|
||||||
|
if num_confirmed == 0 {
|
||||||
|
let conn = self.connection_cache.get_connection(self.tpu_addr());
|
||||||
|
// Send the transaction if there has been no confirmation (e.g. the first time)
|
||||||
|
#[allow(clippy::needless_borrow)]
|
||||||
|
conn.send_wire_transaction(&wire_transaction)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(confirmed_blocks) = self.poll_for_signature_confirmation(
|
||||||
|
&transaction.signatures[0],
|
||||||
|
pending_confirmations,
|
||||||
|
) {
|
||||||
|
num_confirmed = confirmed_blocks;
|
||||||
|
if confirmed_blocks >= pending_confirmations {
|
||||||
|
return Ok(transaction.signatures[0]);
|
||||||
|
}
|
||||||
|
// Since network has seen the transaction, wait longer to receive
|
||||||
|
// all pending confirmations. Resending the transaction could result into
|
||||||
|
// extra transaction fees
|
||||||
|
wait_time = wait_time.max(
|
||||||
|
MAX_PROCESSING_AGE * pending_confirmations.saturating_sub(num_confirmed),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("{} tries failed transfer to {}", x, self.tpu_addr());
|
||||||
|
let blockhash = self.get_latest_blockhash()?;
|
||||||
|
transaction.sign(keypairs, blockhash);
|
||||||
|
}
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("retry_transfer failed in {} retries", tries),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_get_balance(&self, pubkey: &Pubkey) -> TransportResult<u64> {
|
||||||
|
self.poll_get_balance_with_commitment(pubkey, CommitmentConfig::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_get_balance_with_commitment(
|
||||||
|
&self,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<u64> {
|
||||||
|
self.rpc_client()
|
||||||
|
.poll_get_balance_with_commitment(pubkey, commitment_config)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_balance(&self, pubkey: &Pubkey, expected_balance: Option<u64>) -> Option<u64> {
|
||||||
|
self.rpc_client().wait_for_balance_with_commitment(
|
||||||
|
pubkey,
|
||||||
|
expected_balance,
|
||||||
|
CommitmentConfig::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_program_accounts_with_config(
|
||||||
|
&self,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
config: RpcProgramAccountsConfig,
|
||||||
|
) -> TransportResult<Vec<(Pubkey, Account)>> {
|
||||||
|
self.rpc_client()
|
||||||
|
.get_program_accounts_with_config(pubkey, config)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_balance_with_commitment(
|
||||||
|
&self,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
expected_balance: Option<u64>,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> Option<u64> {
|
||||||
|
self.rpc_client().wait_for_balance_with_commitment(
|
||||||
|
pubkey,
|
||||||
|
expected_balance,
|
||||||
|
commitment_config,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_for_signature_with_commitment(
|
||||||
|
&self,
|
||||||
|
signature: &Signature,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
self.rpc_client()
|
||||||
|
.poll_for_signature_with_commitment(signature, commitment_config)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_num_blocks_since_signature_confirmation(
|
||||||
|
&mut self,
|
||||||
|
sig: &Signature,
|
||||||
|
) -> TransportResult<usize> {
|
||||||
|
self.rpc_client()
|
||||||
|
.get_num_blocks_since_signature_confirmation(sig)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client for ThinClient {
|
||||||
|
fn tpu_addr(&self) -> String {
|
||||||
|
self.tpu_addr().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyncClient for ThinClient {
|
||||||
|
fn send_and_confirm_message<T: Signers>(
|
||||||
|
&self,
|
||||||
|
keypairs: &T,
|
||||||
|
message: Message,
|
||||||
|
) -> TransportResult<Signature> {
|
||||||
|
let blockhash = self.get_latest_blockhash()?;
|
||||||
|
let mut transaction = Transaction::new(keypairs, message, blockhash);
|
||||||
|
let signature = self.send_and_confirm_transaction(keypairs, &mut transaction, 5, 0)?;
|
||||||
|
Ok(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_and_confirm_instruction(
|
||||||
|
&self,
|
||||||
|
keypair: &Keypair,
|
||||||
|
instruction: Instruction,
|
||||||
|
) -> TransportResult<Signature> {
|
||||||
|
let message = Message::new(&[instruction], Some(&keypair.pubkey()));
|
||||||
|
self.send_and_confirm_message(&[keypair], message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_and_confirm(
|
||||||
|
&self,
|
||||||
|
lamports: u64,
|
||||||
|
keypair: &Keypair,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
) -> TransportResult<Signature> {
|
||||||
|
let transfer_instruction =
|
||||||
|
system_instruction::transfer(&keypair.pubkey(), pubkey, lamports);
|
||||||
|
self.send_and_confirm_instruction(keypair, transfer_instruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_account_data(&self, pubkey: &Pubkey) -> TransportResult<Option<Vec<u8>>> {
|
||||||
|
Ok(self.rpc_client().get_account_data(pubkey).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_account(&self, pubkey: &Pubkey) -> TransportResult<Option<Account>> {
|
||||||
|
let account = self.rpc_client().get_account(pubkey);
|
||||||
|
match account {
|
||||||
|
Ok(value) => Ok(Some(value)),
|
||||||
|
Err(_) => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_account_with_commitment(
|
||||||
|
&self,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<Option<Account>> {
|
||||||
|
self.rpc_client()
|
||||||
|
.get_account_with_commitment(pubkey, commitment_config)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.map(|r| r.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_balance(&self, pubkey: &Pubkey) -> TransportResult<u64> {
|
||||||
|
self.rpc_client().get_balance(pubkey).map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_balance_with_commitment(
|
||||||
|
&self,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<u64> {
|
||||||
|
self.rpc_client()
|
||||||
|
.get_balance_with_commitment(pubkey, commitment_config)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.map(|r| r.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> TransportResult<u64> {
|
||||||
|
self.rpc_client()
|
||||||
|
.get_minimum_balance_for_rent_exemption(data_len)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let (blockhash, fee_calculator, _last_valid_slot) =
|
||||||
|
self.get_recent_blockhash_with_commitment(CommitmentConfig::default())?;
|
||||||
|
Ok((blockhash, fee_calculator))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_recent_blockhash_with_commitment(
|
||||||
|
&self,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<(Hash, FeeCalculator, Slot)> {
|
||||||
|
let index = self.optimizer.experiment();
|
||||||
|
let now = Instant::now();
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let recent_blockhash =
|
||||||
|
self.rpc_clients[index].get_recent_blockhash_with_commitment(commitment_config);
|
||||||
|
match recent_blockhash {
|
||||||
|
Ok(Response { value, .. }) => {
|
||||||
|
self.optimizer.report(index, duration_as_ms(&now.elapsed()));
|
||||||
|
Ok((value.0, value.1, value.2))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.optimizer.report(index, std::u64::MAX);
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fee_calculator_for_blockhash(
|
||||||
|
&self,
|
||||||
|
blockhash: &Hash,
|
||||||
|
) -> TransportResult<Option<FeeCalculator>> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
self.rpc_client()
|
||||||
|
.get_fee_calculator_for_blockhash(blockhash)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fee_rate_governor(&self) -> TransportResult<FeeRateGovernor> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
self.rpc_client()
|
||||||
|
.get_fee_rate_governor()
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.map(|r| r.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_signature_status(
|
||||||
|
&self,
|
||||||
|
signature: &Signature,
|
||||||
|
) -> TransportResult<Option<transaction::Result<()>>> {
|
||||||
|
let status = self
|
||||||
|
.rpc_client()
|
||||||
|
.get_signature_status(signature)
|
||||||
|
.map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("send_transaction failed with error {:?}", err),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_signature_status_with_commitment(
|
||||||
|
&self,
|
||||||
|
signature: &Signature,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<Option<transaction::Result<()>>> {
|
||||||
|
let status = self
|
||||||
|
.rpc_client()
|
||||||
|
.get_signature_status_with_commitment(signature, commitment_config)
|
||||||
|
.map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("send_transaction failed with error {:?}", err),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_slot(&self) -> TransportResult<u64> {
|
||||||
|
self.get_slot_with_commitment(CommitmentConfig::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_slot_with_commitment(
|
||||||
|
&self,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<u64> {
|
||||||
|
let slot = self
|
||||||
|
.rpc_client()
|
||||||
|
.get_slot_with_commitment(commitment_config)
|
||||||
|
.map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("send_transaction failed with error {:?}", err),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_epoch_info(&self) -> TransportResult<EpochInfo> {
|
||||||
|
self.rpc_client().get_epoch_info().map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transaction_count(&self) -> TransportResult<u64> {
|
||||||
|
let index = self.optimizer.experiment();
|
||||||
|
let now = Instant::now();
|
||||||
|
match self.rpc_client().get_transaction_count() {
|
||||||
|
Ok(transaction_count) => {
|
||||||
|
self.optimizer.report(index, duration_as_ms(&now.elapsed()));
|
||||||
|
Ok(transaction_count)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.optimizer.report(index, std::u64::MAX);
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transaction_count_with_commitment(
|
||||||
|
&self,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<u64> {
|
||||||
|
let index = self.optimizer.experiment();
|
||||||
|
let now = Instant::now();
|
||||||
|
match self
|
||||||
|
.rpc_client()
|
||||||
|
.get_transaction_count_with_commitment(commitment_config)
|
||||||
|
{
|
||||||
|
Ok(transaction_count) => {
|
||||||
|
self.optimizer.report(index, duration_as_ms(&now.elapsed()));
|
||||||
|
Ok(transaction_count)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.optimizer.report(index, std::u64::MAX);
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll the server until the signature has been confirmed by at least `min_confirmed_blocks`
|
||||||
|
fn poll_for_signature_confirmation(
|
||||||
|
&self,
|
||||||
|
signature: &Signature,
|
||||||
|
min_confirmed_blocks: usize,
|
||||||
|
) -> TransportResult<usize> {
|
||||||
|
self.rpc_client()
|
||||||
|
.poll_for_signature_confirmation(signature, min_confirmed_blocks)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_for_signature(&self, signature: &Signature) -> TransportResult<()> {
|
||||||
|
self.rpc_client()
|
||||||
|
.poll_for_signature(signature)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_new_blockhash(&self, blockhash: &Hash) -> TransportResult<(Hash, FeeCalculator)> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
self.rpc_client()
|
||||||
|
.get_new_blockhash(blockhash)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_latest_blockhash(&self) -> TransportResult<Hash> {
|
||||||
|
let (blockhash, _) =
|
||||||
|
self.get_latest_blockhash_with_commitment(CommitmentConfig::default())?;
|
||||||
|
Ok(blockhash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_latest_blockhash_with_commitment(
|
||||||
|
&self,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<(Hash, u64)> {
|
||||||
|
let index = self.optimizer.experiment();
|
||||||
|
let now = Instant::now();
|
||||||
|
match self.rpc_clients[index].get_latest_blockhash_with_commitment(commitment_config) {
|
||||||
|
Ok((blockhash, last_valid_block_height)) => {
|
||||||
|
self.optimizer.report(index, duration_as_ms(&now.elapsed()));
|
||||||
|
Ok((blockhash, last_valid_block_height))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.optimizer.report(index, std::u64::MAX);
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_blockhash_valid(
|
||||||
|
&self,
|
||||||
|
blockhash: &Hash,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> TransportResult<bool> {
|
||||||
|
self.rpc_client()
|
||||||
|
.is_blockhash_valid(blockhash, commitment_config)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fee_for_message(&self, message: &Message) -> TransportResult<u64> {
|
||||||
|
self.rpc_client()
|
||||||
|
.get_fee_for_message(message)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncClient for ThinClient {
|
||||||
|
fn async_send_versioned_transaction(
|
||||||
|
&self,
|
||||||
|
transaction: VersionedTransaction,
|
||||||
|
) -> TransportResult<Signature> {
|
||||||
|
let conn = self.connection_cache.get_connection(self.tpu_addr());
|
||||||
|
conn.serialize_and_send_transaction(&transaction)?;
|
||||||
|
Ok(transaction.signatures[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn async_send_versioned_transaction_batch(
|
||||||
|
&self,
|
||||||
|
batch: Vec<VersionedTransaction>,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let conn = self.connection_cache.get_connection(self.tpu_addr());
|
||||||
|
conn.par_serialize_and_send_transaction_batch(&batch[..])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
pub use {
|
||||||
|
crate::nonblocking::tpu_client::TpuSenderError,
|
||||||
|
solana_tpu_client::tpu_client::{TpuClientConfig, DEFAULT_FANOUT_SLOTS, MAX_FANOUT_SLOTS},
|
||||||
|
};
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
connection_cache::ConnectionCache,
|
||||||
|
nonblocking::tpu_client::TpuClient as NonblockingTpuClient,
|
||||||
|
},
|
||||||
|
rayon::iter::{IntoParallelIterator, ParallelIterator},
|
||||||
|
solana_rpc_client::rpc_client::RpcClient,
|
||||||
|
solana_sdk::{
|
||||||
|
message::Message,
|
||||||
|
signers::Signers,
|
||||||
|
transaction::{Transaction, TransactionError},
|
||||||
|
transport::Result as TransportResult,
|
||||||
|
},
|
||||||
|
solana_tpu_client::tpu_client::temporary_pub::Result,
|
||||||
|
std::{net::UdpSocket, sync::Arc},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Client which sends transactions directly to the current leader's TPU port over UDP.
|
||||||
|
/// The client uses RPC to determine the current leader and fetch node contact info
|
||||||
|
pub struct TpuClient {
|
||||||
|
_deprecated: UdpSocket, // TpuClient now uses the connection_cache to choose a send_socket
|
||||||
|
//todo: get rid of this field
|
||||||
|
rpc_client: Arc<RpcClient>,
|
||||||
|
tpu_client: Arc<NonblockingTpuClient>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TpuClient {
|
||||||
|
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
|
||||||
|
/// size
|
||||||
|
pub fn send_transaction(&self, transaction: &Transaction) -> bool {
|
||||||
|
self.invoke(self.tpu_client.send_transaction(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
|
||||||
|
pub fn send_wire_transaction(&self, wire_transaction: Vec<u8>) -> bool {
|
||||||
|
self.invoke(self.tpu_client.send_wire_transaction(wire_transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
|
||||||
|
/// size
|
||||||
|
/// Returns the last error if all sends fail
|
||||||
|
pub fn try_send_transaction(&self, transaction: &Transaction) -> TransportResult<()> {
|
||||||
|
self.invoke(self.tpu_client.try_send_transaction(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize and send a batch of transactions to the current and upcoming leader TPUs according
|
||||||
|
/// to fanout size
|
||||||
|
/// Returns the last error if all sends fail
|
||||||
|
pub fn try_send_transaction_batch(&self, transactions: &[Transaction]) -> TransportResult<()> {
|
||||||
|
let wire_transactions = transactions
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|tx| bincode::serialize(&tx).expect("serialize Transaction in send_batch"))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.invoke(
|
||||||
|
self.tpu_client
|
||||||
|
.try_send_wire_transaction_batch(wire_transactions),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
|
||||||
|
/// Returns the last error if all sends fail
|
||||||
|
pub fn try_send_wire_transaction(&self, wire_transaction: Vec<u8>) -> TransportResult<()> {
|
||||||
|
self.invoke(self.tpu_client.try_send_wire_transaction(wire_transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new client that disconnects when dropped
|
||||||
|
pub fn new(
|
||||||
|
rpc_client: Arc<RpcClient>,
|
||||||
|
websocket_url: &str,
|
||||||
|
config: TpuClientConfig,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let create_tpu_client =
|
||||||
|
NonblockingTpuClient::new(rpc_client.get_inner_client().clone(), websocket_url, config);
|
||||||
|
let tpu_client =
|
||||||
|
tokio::task::block_in_place(|| rpc_client.runtime().block_on(create_tpu_client))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
_deprecated: UdpSocket::bind("0.0.0.0:0").unwrap(),
|
||||||
|
rpc_client,
|
||||||
|
tpu_client: Arc::new(tpu_client),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new client that disconnects when dropped
|
||||||
|
pub fn new_with_connection_cache(
|
||||||
|
rpc_client: Arc<RpcClient>,
|
||||||
|
websocket_url: &str,
|
||||||
|
config: TpuClientConfig,
|
||||||
|
connection_cache: Arc<ConnectionCache>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let create_tpu_client = NonblockingTpuClient::new_with_connection_cache(
|
||||||
|
rpc_client.get_inner_client().clone(),
|
||||||
|
websocket_url,
|
||||||
|
config,
|
||||||
|
connection_cache,
|
||||||
|
);
|
||||||
|
let tpu_client =
|
||||||
|
tokio::task::block_in_place(|| rpc_client.runtime().block_on(create_tpu_client))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
_deprecated: UdpSocket::bind("0.0.0.0:0").unwrap(),
|
||||||
|
rpc_client,
|
||||||
|
tpu_client: Arc::new(tpu_client),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_and_confirm_messages_with_spinner<T: Signers>(
|
||||||
|
&self,
|
||||||
|
messages: &[Message],
|
||||||
|
signers: &T,
|
||||||
|
) -> Result<Vec<Option<TransactionError>>> {
|
||||||
|
self.invoke(
|
||||||
|
self.tpu_client
|
||||||
|
.send_and_confirm_messages_with_spinner(messages, signers),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rpc_client(&self) -> &RpcClient {
|
||||||
|
&self.rpc_client
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke<T, F: std::future::Future<Output = T>>(&self, f: F) -> T {
|
||||||
|
// `block_on()` panics if called within an asynchronous execution context. Whereas
|
||||||
|
// `block_in_place()` only panics if called from a current_thread runtime, which is the
|
||||||
|
// lesser evil.
|
||||||
|
tokio::task::block_in_place(move || self.rpc_client.runtime().block_on(f))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
pub use solana_tpu_client::tpu_connection::ClientStats;
|
||||||
|
use {
|
||||||
|
enum_dispatch::enum_dispatch,
|
||||||
|
rayon::iter::{IntoParallelIterator, ParallelIterator},
|
||||||
|
solana_quic_client::quic_client::QuicTpuConnection,
|
||||||
|
solana_sdk::{transaction::VersionedTransaction, transport::Result as TransportResult},
|
||||||
|
solana_udp_client::udp_client::UdpTpuConnection,
|
||||||
|
std::net::SocketAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[enum_dispatch]
|
||||||
|
pub enum BlockingConnection {
|
||||||
|
UdpTpuConnection,
|
||||||
|
QuicTpuConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[enum_dispatch(BlockingConnection)]
|
||||||
|
pub trait TpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr;
|
||||||
|
|
||||||
|
fn serialize_and_send_transaction(
|
||||||
|
&self,
|
||||||
|
transaction: &VersionedTransaction,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let wire_transaction =
|
||||||
|
bincode::serialize(transaction).expect("serialize Transaction in send_batch");
|
||||||
|
self.send_wire_transaction(wire_transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
self.send_wire_transaction_batch(&[wire_transaction])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_async(&self, wire_transaction: Vec<u8>) -> TransportResult<()>;
|
||||||
|
|
||||||
|
fn par_serialize_and_send_transaction_batch(
|
||||||
|
&self,
|
||||||
|
transactions: &[VersionedTransaction],
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let buffers = transactions
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|tx| bincode::serialize(&tx).expect("serialize Transaction in send_batch"))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
self.send_wire_transaction_batch(&buffers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync;
|
||||||
|
|
||||||
|
fn send_wire_transaction_batch_async(&self, buffers: Vec<Vec<u8>>) -> TransportResult<()>;
|
||||||
|
}
|
|
@ -1,39 +1,13 @@
|
||||||
//! Simple TPU client that communicates with the given UDP port with UDP and provides
|
//! Simple TPU client that communicates with the given UDP port with UDP and provides
|
||||||
//! an interface for sending transactions
|
//! an interface for sending transactions
|
||||||
|
|
||||||
|
pub use solana_udp_client::udp_client::UdpTpuConnection;
|
||||||
use {
|
use {
|
||||||
crate::{connection_cache_stats::ConnectionCacheStats, tpu_connection::TpuConnection},
|
crate::tpu_connection::TpuConnection, core::iter::repeat,
|
||||||
core::iter::repeat,
|
solana_sdk::transport::Result as TransportResult, solana_streamer::sendmmsg::batch_send,
|
||||||
solana_sdk::transport::Result as TransportResult,
|
std::net::SocketAddr,
|
||||||
solana_streamer::sendmmsg::batch_send,
|
|
||||||
std::{
|
|
||||||
net::{SocketAddr, UdpSocket},
|
|
||||||
sync::Arc,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct UdpTpuConnection {
|
|
||||||
socket: Arc<UdpSocket>,
|
|
||||||
addr: SocketAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UdpTpuConnection {
|
|
||||||
pub fn new_from_addr(local_socket: Arc<UdpSocket>, tpu_addr: SocketAddr) -> Self {
|
|
||||||
Self {
|
|
||||||
socket: local_socket,
|
|
||||||
addr: tpu_addr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(
|
|
||||||
local_socket: Arc<UdpSocket>,
|
|
||||||
tpu_addr: SocketAddr,
|
|
||||||
_connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
) -> Self {
|
|
||||||
Self::new_from_addr(local_socket, tpu_addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TpuConnection for UdpTpuConnection {
|
impl TpuConnection for UdpTpuConnection {
|
||||||
fn tpu_addr(&self) -> &SocketAddr {
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
&self.addr
|
&self.addr
|
|
@ -38,6 +38,7 @@ serde = "1.0.144"
|
||||||
serde_derive = "1.0.103"
|
serde_derive = "1.0.103"
|
||||||
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.15.0" }
|
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.15.0" }
|
||||||
solana-bloom = { path = "../bloom", version = "=1.15.0" }
|
solana-bloom = { path = "../bloom", version = "=1.15.0" }
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-entry = { path = "../entry", version = "=1.15.0" }
|
solana-entry = { path = "../entry", version = "=1.15.0" }
|
||||||
solana-frozen-abi = { path = "../frozen-abi", version = "=1.15.0" }
|
solana-frozen-abi = { path = "../frozen-abi", version = "=1.15.0" }
|
||||||
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.15.0" }
|
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.15.0" }
|
||||||
|
|
|
@ -8,6 +8,7 @@ use {
|
||||||
log::*,
|
log::*,
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
rayon::prelude::*,
|
rayon::prelude::*,
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_core::{
|
solana_core::{
|
||||||
banking_stage::{BankingStage, BankingStageStats},
|
banking_stage::{BankingStage, BankingStageStats},
|
||||||
leader_slot_banking_stage_metrics::LeaderSlotMetricsTracker,
|
leader_slot_banking_stage_metrics::LeaderSlotMetricsTracker,
|
||||||
|
@ -37,7 +38,6 @@ use {
|
||||||
transaction::{Transaction, VersionedTransaction},
|
transaction::{Transaction, VersionedTransaction},
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
solana_vote_program::{
|
solana_vote_program::{
|
||||||
vote_state::VoteStateUpdate, vote_transaction::new_vote_state_update_transaction,
|
vote_state::VoteStateUpdate, vote_transaction::new_vote_state_update_transaction,
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,6 +26,7 @@ use {
|
||||||
},
|
},
|
||||||
histogram::Histogram,
|
histogram::Histogram,
|
||||||
itertools::Itertools,
|
itertools::Itertools,
|
||||||
|
solana_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
||||||
solana_entry::entry::hash_transactions,
|
solana_entry::entry::hash_transactions,
|
||||||
solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo},
|
solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo},
|
||||||
solana_ledger::{
|
solana_ledger::{
|
||||||
|
@ -66,7 +67,6 @@ use {
|
||||||
transport::TransportError,
|
transport::TransportError,
|
||||||
},
|
},
|
||||||
solana_streamer::sendmmsg::batch_send,
|
solana_streamer::sendmmsg::batch_send,
|
||||||
solana_tpu_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
|
||||||
solana_transaction_status::{
|
solana_transaction_status::{
|
||||||
token_balances::TransactionTokenBalancesSet, TransactionTokenBalance,
|
token_balances::TransactionTokenBalancesSet, TransactionTokenBalance,
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,7 +13,7 @@ use {
|
||||||
solana_streamer::streamer::{
|
solana_streamer::streamer::{
|
||||||
self, PacketBatchReceiver, PacketBatchSender, StreamerReceiveStats,
|
self, PacketBatchReceiver, PacketBatchSender, StreamerReceiveStats,
|
||||||
},
|
},
|
||||||
solana_tpu_client::connection_cache::DEFAULT_TPU_ENABLE_UDP,
|
solana_tpu_client::tpu_connection_cache::DEFAULT_TPU_ENABLE_UDP,
|
||||||
std::{
|
std::{
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
sync::{
|
sync::{
|
||||||
|
|
|
@ -16,6 +16,7 @@ use {
|
||||||
staked_nodes_updater_service::StakedNodesUpdaterService,
|
staked_nodes_updater_service::StakedNodesUpdaterService,
|
||||||
},
|
},
|
||||||
crossbeam_channel::{unbounded, Receiver},
|
crossbeam_channel::{unbounded, Receiver},
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_gossip::cluster_info::ClusterInfo,
|
solana_gossip::cluster_info::ClusterInfo,
|
||||||
solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusSender},
|
solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusSender},
|
||||||
solana_poh::poh_recorder::{PohRecorder, WorkingBankEntry},
|
solana_poh::poh_recorder::{PohRecorder, WorkingBankEntry},
|
||||||
|
@ -34,7 +35,6 @@ use {
|
||||||
quic::{spawn_server, StreamStats, MAX_STAKED_CONNECTIONS, MAX_UNSTAKED_CONNECTIONS},
|
quic::{spawn_server, StreamStats, MAX_STAKED_CONNECTIONS, MAX_UNSTAKED_CONNECTIONS},
|
||||||
streamer::StakedNodes,
|
streamer::StakedNodes,
|
||||||
},
|
},
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
std::{
|
std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
|
|
|
@ -28,6 +28,7 @@ use {
|
||||||
window_service::WindowService,
|
window_service::WindowService,
|
||||||
},
|
},
|
||||||
crossbeam_channel::{unbounded, Receiver},
|
crossbeam_channel::{unbounded, Receiver},
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_geyser_plugin_manager::block_metadata_notifier_interface::BlockMetadataNotifierLock,
|
solana_geyser_plugin_manager::block_metadata_notifier_interface::BlockMetadataNotifierLock,
|
||||||
solana_gossip::cluster_info::ClusterInfo,
|
solana_gossip::cluster_info::ClusterInfo,
|
||||||
solana_ledger::{
|
solana_ledger::{
|
||||||
|
@ -45,7 +46,6 @@ use {
|
||||||
prioritization_fee_cache::PrioritizationFeeCache, vote_sender_types::ReplayVoteSender,
|
prioritization_fee_cache::PrioritizationFeeCache, vote_sender_types::ReplayVoteSender,
|
||||||
},
|
},
|
||||||
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Keypair},
|
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Keypair},
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
std::{
|
std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
|
|
|
@ -25,6 +25,7 @@ use {
|
||||||
},
|
},
|
||||||
crossbeam_channel::{bounded, unbounded, Receiver},
|
crossbeam_channel::{bounded, unbounded, Receiver},
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_entry::poh::compute_hash_time_ns,
|
solana_entry::poh::compute_hash_time_ns,
|
||||||
solana_geyser_plugin_manager::geyser_plugin_service::GeyserPluginService,
|
solana_geyser_plugin_manager::geyser_plugin_service::GeyserPluginService,
|
||||||
solana_gossip::{
|
solana_gossip::{
|
||||||
|
@ -99,7 +100,6 @@ use {
|
||||||
},
|
},
|
||||||
solana_send_transaction_service::send_transaction_service,
|
solana_send_transaction_service::send_transaction_service,
|
||||||
solana_streamer::{socket::SocketAddrSpace, streamer::StakedNodes},
|
solana_streamer::{socket::SocketAddrSpace, streamer::StakedNodes},
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
solana_vote_program::vote_state,
|
solana_vote_program::vote_state,
|
||||||
std::{
|
std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
@ -2153,7 +2153,7 @@ mod tests {
|
||||||
crossbeam_channel::{bounded, RecvTimeoutError},
|
crossbeam_channel::{bounded, RecvTimeoutError},
|
||||||
solana_ledger::{create_new_tmp_ledger, genesis_utils::create_genesis_config_with_leader},
|
solana_ledger::{create_new_tmp_ledger, genesis_utils::create_genesis_config_with_leader},
|
||||||
solana_sdk::{genesis_config::create_genesis_config, poh_config::PohConfig},
|
solana_sdk::{genesis_config::create_genesis_config, poh_config::PohConfig},
|
||||||
solana_tpu_client::connection_cache::{
|
solana_tpu_client::tpu_connection_cache::{
|
||||||
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
|
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
|
||||||
},
|
},
|
||||||
std::{fs::remove_dir_all, thread, time::Duration},
|
std::{fs::remove_dir_all, thread, time::Duration},
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
use {
|
use {
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
|
solana_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
||||||
solana_gossip::cluster_info::ClusterInfo,
|
solana_gossip::cluster_info::ClusterInfo,
|
||||||
solana_poh::poh_recorder::PohRecorder,
|
solana_poh::poh_recorder::PohRecorder,
|
||||||
solana_tpu_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
|
||||||
std::{
|
std::{
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
|
|
|
@ -18,6 +18,7 @@ log = "0.4.17"
|
||||||
rand = "0.7.0"
|
rand = "0.7.0"
|
||||||
serde = "1.0.144"
|
serde = "1.0.144"
|
||||||
solana-bench-tps = { path = "../bench-tps", version = "=1.15.0" }
|
solana-bench-tps = { path = "../bench-tps", version = "=1.15.0" }
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-core = { path = "../core", version = "=1.15.0" }
|
solana-core = { path = "../core", version = "=1.15.0" }
|
||||||
solana-faucet = { path = "../faucet", version = "=1.15.0" }
|
solana-faucet = { path = "../faucet", version = "=1.15.0" }
|
||||||
solana-gossip = { path = "../gossip", version = "=1.15.0" }
|
solana-gossip = { path = "../gossip", version = "=1.15.0" }
|
||||||
|
|
|
@ -45,6 +45,7 @@ use {
|
||||||
log::*,
|
log::*,
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
solana_bench_tps::{bench::generate_and_fund_keypairs, bench_tps_client::BenchTpsClient},
|
solana_bench_tps::{bench::generate_and_fund_keypairs, bench_tps_client::BenchTpsClient},
|
||||||
|
solana_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
||||||
solana_core::serve_repair::{RepairProtocol, RepairRequestHeader, ServeRepair},
|
solana_core::serve_repair::{RepairProtocol, RepairRequestHeader, ServeRepair},
|
||||||
solana_dos::cli::*,
|
solana_dos::cli::*,
|
||||||
solana_gossip::{
|
solana_gossip::{
|
||||||
|
@ -66,10 +67,7 @@ use {
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_tpu_client::{
|
solana_tpu_client::tpu_connection_cache::DEFAULT_TPU_CONNECTION_POOL_SIZE,
|
||||||
connection_cache::{ConnectionCache, DEFAULT_TPU_CONNECTION_POOL_SIZE},
|
|
||||||
tpu_connection::TpuConnection,
|
|
||||||
},
|
|
||||||
std::{
|
std::{
|
||||||
net::{SocketAddr, UdpSocket},
|
net::{SocketAddr, UdpSocket},
|
||||||
process::exit,
|
process::exit,
|
||||||
|
@ -786,6 +784,7 @@ fn main() {
|
||||||
pub mod test {
|
pub mod test {
|
||||||
use {
|
use {
|
||||||
super::*,
|
super::*,
|
||||||
|
solana_client::thin_client::ThinClient,
|
||||||
solana_core::validator::ValidatorConfig,
|
solana_core::validator::ValidatorConfig,
|
||||||
solana_faucet::faucet::run_local_faucet,
|
solana_faucet::faucet::run_local_faucet,
|
||||||
solana_local_cluster::{
|
solana_local_cluster::{
|
||||||
|
@ -795,7 +794,6 @@ pub mod test {
|
||||||
},
|
},
|
||||||
solana_rpc::rpc::JsonRpcConfig,
|
solana_rpc::rpc::JsonRpcConfig,
|
||||||
solana_sdk::timing::timestamp,
|
solana_sdk::timing::timestamp,
|
||||||
solana_thin_client::thin_client::ThinClient,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TEST_SEND_BATCH_SIZE: usize = 1;
|
const TEST_SEND_BATCH_SIZE: usize = 1;
|
||||||
|
|
|
@ -29,6 +29,7 @@ serde_bytes = "0.11"
|
||||||
serde_derive = "1.0.103"
|
serde_derive = "1.0.103"
|
||||||
solana-bloom = { path = "../bloom", version = "=1.15.0" }
|
solana-bloom = { path = "../bloom", version = "=1.15.0" }
|
||||||
solana-clap-utils = { path = "../clap-utils", version = "=1.15.0" }
|
solana-clap-utils = { path = "../clap-utils", version = "=1.15.0" }
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-entry = { path = "../entry", version = "=1.15.0" }
|
solana-entry = { path = "../entry", version = "=1.15.0" }
|
||||||
solana-frozen-abi = { path = "../frozen-abi", version = "=1.15.0" }
|
solana-frozen-abi = { path = "../frozen-abi", version = "=1.15.0" }
|
||||||
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.15.0" }
|
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.15.0" }
|
||||||
|
|
|
@ -4,6 +4,7 @@ use {
|
||||||
crate::{cluster_info::ClusterInfo, contact_info::ContactInfo},
|
crate::{cluster_info::ClusterInfo, contact_info::ContactInfo},
|
||||||
crossbeam_channel::{unbounded, Sender},
|
crossbeam_channel::{unbounded, Sender},
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
|
solana_client::{connection_cache::ConnectionCache, thin_client::ThinClient},
|
||||||
solana_perf::recycler::Recycler,
|
solana_perf::recycler::Recycler,
|
||||||
solana_runtime::bank_forks::BankForks,
|
solana_runtime::bank_forks::BankForks,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
|
@ -14,8 +15,6 @@ use {
|
||||||
socket::SocketAddrSpace,
|
socket::SocketAddrSpace,
|
||||||
streamer::{self, StreamerReceiveStats},
|
streamer::{self, StreamerReceiveStats},
|
||||||
},
|
},
|
||||||
solana_thin_client::thin_client::ThinClient,
|
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
std::{
|
std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
net::{SocketAddr, TcpListener, UdpSocket},
|
net::{SocketAddr, TcpListener, UdpSocket},
|
||||||
|
|
|
@ -16,6 +16,7 @@ itertools = "0.10.5"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
rand = "0.7.0"
|
rand = "0.7.0"
|
||||||
rayon = "1.5.3"
|
rayon = "1.5.3"
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-config-program = { path = "../programs/config", version = "=1.15.0" }
|
solana-config-program = { path = "../programs/config", version = "=1.15.0" }
|
||||||
solana-core = { path = "../core", version = "=1.15.0" }
|
solana-core = { path = "../core", version = "=1.15.0" }
|
||||||
solana-entry = { path = "../entry", version = "=1.15.0" }
|
solana-entry = { path = "../entry", version = "=1.15.0" }
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use {
|
use {
|
||||||
|
solana_client::thin_client::ThinClient,
|
||||||
solana_core::validator::{Validator, ValidatorConfig},
|
solana_core::validator::{Validator, ValidatorConfig},
|
||||||
solana_gossip::{cluster_info::Node, contact_info::ContactInfo},
|
solana_gossip::{cluster_info::Node, contact_info::ContactInfo},
|
||||||
solana_sdk::{pubkey::Pubkey, signature::Keypair},
|
solana_sdk::{pubkey::Pubkey, signature::Keypair},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_thin_client::thin_client::ThinClient,
|
|
||||||
std::{path::PathBuf, sync::Arc},
|
std::{path::PathBuf, sync::Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use log::*;
|
||||||
use {
|
use {
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
rayon::prelude::*,
|
rayon::prelude::*,
|
||||||
|
solana_client::{connection_cache::ConnectionCache, thin_client::ThinClient},
|
||||||
solana_core::consensus::VOTE_THRESHOLD_DEPTH,
|
solana_core::consensus::VOTE_THRESHOLD_DEPTH,
|
||||||
solana_entry::entry::{Entry, EntrySlice},
|
solana_entry::entry::{Entry, EntrySlice},
|
||||||
solana_gossip::{
|
solana_gossip::{
|
||||||
|
@ -31,8 +32,6 @@ use {
|
||||||
transport::TransportError,
|
transport::TransportError,
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_thin_client::thin_client::ThinClient,
|
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
solana_vote_program::vote_transaction,
|
solana_vote_program::vote_transaction,
|
||||||
std::{
|
std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
|
|
@ -6,6 +6,7 @@ use {
|
||||||
},
|
},
|
||||||
itertools::izip,
|
itertools::izip,
|
||||||
log::*,
|
log::*,
|
||||||
|
solana_client::{connection_cache::ConnectionCache, thin_client::ThinClient},
|
||||||
solana_core::{
|
solana_core::{
|
||||||
tower_storage::FileTowerStorage,
|
tower_storage::FileTowerStorage,
|
||||||
validator::{Validator, ValidatorConfig, ValidatorStartProgress},
|
validator::{Validator, ValidatorConfig, ValidatorStartProgress},
|
||||||
|
@ -41,10 +42,8 @@ use {
|
||||||
},
|
},
|
||||||
solana_stake_program::{config::create_account as create_stake_config_account, stake_state},
|
solana_stake_program::{config::create_account as create_stake_config_account, stake_state},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_thin_client::thin_client::ThinClient,
|
solana_tpu_client::tpu_connection_cache::{
|
||||||
solana_tpu_client::connection_cache::{
|
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
|
||||||
ConnectionCache, DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP,
|
|
||||||
DEFAULT_TPU_USE_QUIC,
|
|
||||||
},
|
},
|
||||||
solana_vote_program::{
|
solana_vote_program::{
|
||||||
vote_instruction,
|
vote_instruction,
|
||||||
|
|
|
@ -6,6 +6,7 @@ use {
|
||||||
gag::BufferRedirect,
|
gag::BufferRedirect,
|
||||||
log::*,
|
log::*,
|
||||||
serial_test::serial,
|
serial_test::serial,
|
||||||
|
solana_client::thin_client::ThinClient,
|
||||||
solana_core::{
|
solana_core::{
|
||||||
broadcast_stage::BroadcastStageType,
|
broadcast_stage::BroadcastStageType,
|
||||||
consensus::{Tower, SWITCH_FORK_THRESHOLD, VOTE_THRESHOLD_DEPTH},
|
consensus::{Tower, SWITCH_FORK_THRESHOLD, VOTE_THRESHOLD_DEPTH},
|
||||||
|
@ -53,7 +54,6 @@ use {
|
||||||
system_program, system_transaction,
|
system_program, system_transaction,
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_thin_client::thin_client::ThinClient,
|
|
||||||
solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY,
|
solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY,
|
||||||
std::{
|
std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
|
|
@ -4145,7 +4145,6 @@ dependencies = [
|
||||||
"solana-runtime",
|
"solana-runtime",
|
||||||
"solana-sdk 1.15.0",
|
"solana-sdk 1.15.0",
|
||||||
"solana-send-transaction-service",
|
"solana-send-transaction-service",
|
||||||
"solana-tpu-client",
|
|
||||||
"tarpc",
|
"tarpc",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-serde",
|
"tokio-serde",
|
||||||
|
@ -4257,15 +4256,31 @@ dependencies = [
|
||||||
name = "solana-client"
|
name = "solana-client"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"bincode",
|
||||||
|
"enum_dispatch",
|
||||||
|
"futures 0.3.24",
|
||||||
|
"futures-util",
|
||||||
|
"indexmap",
|
||||||
|
"indicatif",
|
||||||
"log",
|
"log",
|
||||||
|
"rand 0.7.3",
|
||||||
|
"rayon",
|
||||||
"solana-measure",
|
"solana-measure",
|
||||||
|
"solana-metrics",
|
||||||
|
"solana-net-utils",
|
||||||
"solana-pubsub-client",
|
"solana-pubsub-client",
|
||||||
|
"solana-quic-client",
|
||||||
"solana-rpc-client",
|
"solana-rpc-client",
|
||||||
"solana-rpc-client-api",
|
"solana-rpc-client-api",
|
||||||
"solana-rpc-client-nonce-utils",
|
"solana-rpc-client-nonce-utils",
|
||||||
"solana-sdk 1.15.0",
|
"solana-sdk 1.15.0",
|
||||||
|
"solana-streamer",
|
||||||
"solana-thin-client",
|
"solana-thin-client",
|
||||||
"solana-tpu-client",
|
"solana-tpu-client",
|
||||||
|
"solana-udp-client",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4317,6 +4332,7 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"solana-address-lookup-table-program",
|
"solana-address-lookup-table-program",
|
||||||
"solana-bloom",
|
"solana-bloom",
|
||||||
|
"solana-client",
|
||||||
"solana-entry",
|
"solana-entry",
|
||||||
"solana-frozen-abi 1.15.0",
|
"solana-frozen-abi 1.15.0",
|
||||||
"solana-frozen-abi-macro 1.15.0",
|
"solana-frozen-abi-macro 1.15.0",
|
||||||
|
@ -4554,6 +4570,7 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"solana-bloom",
|
"solana-bloom",
|
||||||
"solana-clap-utils",
|
"solana-clap-utils",
|
||||||
|
"solana-client",
|
||||||
"solana-entry",
|
"solana-entry",
|
||||||
"solana-frozen-abi 1.15.0",
|
"solana-frozen-abi 1.15.0",
|
||||||
"solana-frozen-abi-macro 1.15.0",
|
"solana-frozen-abi-macro 1.15.0",
|
||||||
|
@ -4913,6 +4930,31 @@ dependencies = [
|
||||||
"url 2.2.2",
|
"url 2.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "solana-quic-client"
|
||||||
|
version = "1.15.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-mutex",
|
||||||
|
"async-trait",
|
||||||
|
"futures 0.3.24",
|
||||||
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"quinn",
|
||||||
|
"quinn-proto",
|
||||||
|
"quinn-udp",
|
||||||
|
"rustls 0.20.6",
|
||||||
|
"solana-measure",
|
||||||
|
"solana-metrics",
|
||||||
|
"solana-net-utils",
|
||||||
|
"solana-rpc-client-api",
|
||||||
|
"solana-sdk 1.15.0",
|
||||||
|
"solana-streamer",
|
||||||
|
"solana-tpu-client",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solana-rayon-threadlimit"
|
name = "solana-rayon-threadlimit"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
@ -4962,6 +5004,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"soketto",
|
"soketto",
|
||||||
"solana-account-decoder",
|
"solana-account-decoder",
|
||||||
|
"solana-client",
|
||||||
"solana-entry",
|
"solana-entry",
|
||||||
"solana-faucet",
|
"solana-faucet",
|
||||||
"solana-gossip",
|
"solana-gossip",
|
||||||
|
@ -5623,6 +5666,7 @@ version = "1.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"log",
|
"log",
|
||||||
|
"solana-client",
|
||||||
"solana-measure",
|
"solana-measure",
|
||||||
"solana-metrics",
|
"solana-metrics",
|
||||||
"solana-runtime",
|
"solana-runtime",
|
||||||
|
@ -5776,23 +5820,14 @@ dependencies = [
|
||||||
name = "solana-tpu-client"
|
name = "solana-tpu-client"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-mutex",
|
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bincode",
|
"bincode",
|
||||||
"enum_dispatch",
|
|
||||||
"futures 0.3.24",
|
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"itertools",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"quinn",
|
|
||||||
"quinn-proto",
|
|
||||||
"quinn-udp",
|
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rayon",
|
"rayon",
|
||||||
"rustls 0.20.6",
|
|
||||||
"solana-measure",
|
"solana-measure",
|
||||||
"solana-metrics",
|
"solana-metrics",
|
||||||
"solana-net-utils",
|
"solana-net-utils",
|
||||||
|
@ -5800,7 +5835,6 @@ dependencies = [
|
||||||
"solana-rpc-client",
|
"solana-rpc-client",
|
||||||
"solana-rpc-client-api",
|
"solana-rpc-client-api",
|
||||||
"solana-sdk 1.15.0",
|
"solana-sdk 1.15.0",
|
||||||
"solana-streamer",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
@ -5829,6 +5863,19 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "solana-udp-client"
|
||||||
|
version = "1.15.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"solana-net-utils",
|
||||||
|
"solana-sdk 1.15.0",
|
||||||
|
"solana-streamer",
|
||||||
|
"solana-tpu-client",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solana-validator"
|
name = "solana-validator"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
|
@ -10,11 +10,27 @@ documentation = "https://docs.rs/solana-quic-client"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-mutex = "1.4.0"
|
||||||
|
async-trait = "0.1.57"
|
||||||
|
futures = "0.3"
|
||||||
|
itertools = "0.10.5"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
log = "0.4.17"
|
||||||
|
quinn = "0.8.4"
|
||||||
|
quinn-proto = "0.8.4"
|
||||||
|
quinn-udp = "0.1.3"
|
||||||
|
rustls = { version = "0.20.6", features = ["dangerous_configuration"] }
|
||||||
|
solana-measure = { path = "../measure", version = "=1.15.0" }
|
||||||
solana-metrics = { path = "../metrics", version = "=1.15.0" }
|
solana-metrics = { path = "../metrics", version = "=1.15.0" }
|
||||||
|
solana-net-utils = { path = "../net-utils", version = "=1.15.0" }
|
||||||
|
solana-rpc-client-api = { path = "../rpc-client-api", version = "=1.15.0" }
|
||||||
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
||||||
solana-streamer = { path = "../streamer", version = "=1.15.0" }
|
solana-streamer = { path = "../streamer", version = "=1.15.0" }
|
||||||
solana-tpu-client = { path = "../tpu-client", version = "=1.15.0" }
|
solana-tpu-client = { path = "../tpu-client", version = "=1.15.0" }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
crossbeam-channel = "0.5"
|
||||||
solana-logger = { path = "../logger", version = "=1.15.0" }
|
solana-logger = { path = "../logger", version = "=1.15.0" }
|
||||||
|
solana-perf = { path = "../perf", version = "=1.15.0" }
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
pub mod nonblocking;
|
pub mod nonblocking;
|
||||||
pub mod quic_client;
|
pub mod quic_client;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate solana_metrics;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
nonblocking::quic_client::{
|
nonblocking::quic_client::{
|
||||||
|
|
|
@ -1,5 +1,598 @@
|
||||||
//! Simple nonblocking client that connects to a given UDP port with the QUIC protocol
|
//! Simple nonblocking client that connects to a given UDP port with the QUIC protocol
|
||||||
//! and provides an interface for sending transactions which is restricted by the
|
//! and provides an interface for sending transactions which is restricted by the
|
||||||
//! server's flow control.
|
//! server's flow control.
|
||||||
|
use {
|
||||||
|
async_mutex::Mutex,
|
||||||
|
async_trait::async_trait,
|
||||||
|
futures::future::join_all,
|
||||||
|
itertools::Itertools,
|
||||||
|
log::*,
|
||||||
|
quinn::{
|
||||||
|
ClientConfig, ConnectError, ConnectionError, Endpoint, EndpointConfig, IdleTimeout,
|
||||||
|
NewConnection, VarInt, WriteError,
|
||||||
|
},
|
||||||
|
solana_measure::measure::Measure,
|
||||||
|
solana_net_utils::VALIDATOR_PORT_RANGE,
|
||||||
|
solana_rpc_client_api::client_error::ErrorKind as ClientErrorKind,
|
||||||
|
solana_sdk::{
|
||||||
|
quic::{
|
||||||
|
QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS, QUIC_KEEP_ALIVE_MS, QUIC_MAX_TIMEOUT_MS,
|
||||||
|
QUIC_MAX_UNSTAKED_CONCURRENT_STREAMS,
|
||||||
|
},
|
||||||
|
signature::Keypair,
|
||||||
|
transport::Result as TransportResult,
|
||||||
|
},
|
||||||
|
solana_streamer::{
|
||||||
|
nonblocking::quic::ALPN_TPU_PROTOCOL_ID,
|
||||||
|
tls_certificates::new_self_signed_tls_certificate_chain,
|
||||||
|
},
|
||||||
|
solana_tpu_client::{
|
||||||
|
connection_cache_stats::ConnectionCacheStats, nonblocking::tpu_connection::TpuConnection,
|
||||||
|
tpu_connection::ClientStats,
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||||
|
sync::{atomic::Ordering, Arc},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
},
|
||||||
|
thiserror::Error,
|
||||||
|
tokio::{sync::RwLock, time::timeout},
|
||||||
|
};
|
||||||
|
|
||||||
pub use solana_tpu_client::nonblocking::quic_client::*;
|
struct SkipServerVerification;
|
||||||
|
|
||||||
|
impl SkipServerVerification {
|
||||||
|
pub fn new() -> Arc<Self> {
|
||||||
|
Arc::new(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl rustls::client::ServerCertVerifier for SkipServerVerification {
|
||||||
|
fn verify_server_cert(
|
||||||
|
&self,
|
||||||
|
_end_entity: &rustls::Certificate,
|
||||||
|
_intermediates: &[rustls::Certificate],
|
||||||
|
_server_name: &rustls::ServerName,
|
||||||
|
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||||
|
_ocsp_response: &[u8],
|
||||||
|
_now: std::time::SystemTime,
|
||||||
|
) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
|
||||||
|
Ok(rustls::client::ServerCertVerified::assertion())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QuicClientCertificate {
|
||||||
|
pub certificates: Vec<rustls::Certificate>,
|
||||||
|
pub key: rustls::PrivateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A lazy-initialized Quic Endpoint
|
||||||
|
pub struct QuicLazyInitializedEndpoint {
|
||||||
|
endpoint: RwLock<Option<Arc<Endpoint>>>,
|
||||||
|
client_certificate: Arc<QuicClientCertificate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum QuicError {
|
||||||
|
#[error(transparent)]
|
||||||
|
WriteError(#[from] WriteError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ConnectionError(#[from] ConnectionError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ConnectError(#[from] ConnectError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<QuicError> for ClientErrorKind {
|
||||||
|
fn from(quic_error: QuicError) -> Self {
|
||||||
|
Self::Custom(format!("{:?}", quic_error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuicLazyInitializedEndpoint {
|
||||||
|
pub fn new(client_certificate: Arc<QuicClientCertificate>) -> Self {
|
||||||
|
Self {
|
||||||
|
endpoint: RwLock::new(None),
|
||||||
|
client_certificate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_endpoint(&self) -> Endpoint {
|
||||||
|
let (_, client_socket) = solana_net_utils::bind_in_range(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
|
VALIDATOR_PORT_RANGE,
|
||||||
|
)
|
||||||
|
.expect("QuicLazyInitializedEndpoint::create_endpoint bind_in_range");
|
||||||
|
|
||||||
|
let mut crypto = rustls::ClientConfig::builder()
|
||||||
|
.with_safe_defaults()
|
||||||
|
.with_custom_certificate_verifier(SkipServerVerification::new())
|
||||||
|
.with_single_cert(
|
||||||
|
self.client_certificate.certificates.clone(),
|
||||||
|
self.client_certificate.key.clone(),
|
||||||
|
)
|
||||||
|
.expect("Failed to set QUIC client certificates");
|
||||||
|
crypto.enable_early_data = true;
|
||||||
|
crypto.alpn_protocols = vec![ALPN_TPU_PROTOCOL_ID.to_vec()];
|
||||||
|
|
||||||
|
let mut endpoint =
|
||||||
|
QuicNewConnection::create_endpoint(EndpointConfig::default(), client_socket);
|
||||||
|
|
||||||
|
let mut config = ClientConfig::new(Arc::new(crypto));
|
||||||
|
let transport_config = Arc::get_mut(&mut config.transport)
|
||||||
|
.expect("QuicLazyInitializedEndpoint::create_endpoint Arc::get_mut");
|
||||||
|
let timeout = IdleTimeout::from(VarInt::from_u32(QUIC_MAX_TIMEOUT_MS));
|
||||||
|
transport_config.max_idle_timeout(Some(timeout));
|
||||||
|
transport_config.keep_alive_interval(Some(Duration::from_millis(QUIC_KEEP_ALIVE_MS)));
|
||||||
|
|
||||||
|
endpoint.set_default_client_config(config);
|
||||||
|
endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_endpoint(&self) -> Arc<Endpoint> {
|
||||||
|
let lock = self.endpoint.read().await;
|
||||||
|
let endpoint = lock.as_ref();
|
||||||
|
|
||||||
|
match endpoint {
|
||||||
|
Some(endpoint) => endpoint.clone(),
|
||||||
|
None => {
|
||||||
|
drop(lock);
|
||||||
|
let mut lock = self.endpoint.write().await;
|
||||||
|
let endpoint = lock.as_ref();
|
||||||
|
|
||||||
|
match endpoint {
|
||||||
|
Some(endpoint) => endpoint.clone(),
|
||||||
|
None => {
|
||||||
|
let connection = Arc::new(self.create_endpoint());
|
||||||
|
*lock = Some(connection.clone());
|
||||||
|
connection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for QuicLazyInitializedEndpoint {
|
||||||
|
fn default() -> Self {
|
||||||
|
let (certs, priv_key) = new_self_signed_tls_certificate_chain(
|
||||||
|
&Keypair::new(),
|
||||||
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
|
)
|
||||||
|
.expect("Failed to create QUIC client certificate");
|
||||||
|
Self::new(Arc::new(QuicClientCertificate {
|
||||||
|
certificates: certs,
|
||||||
|
key: priv_key,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper over NewConnection with additional capability to create the endpoint as part
|
||||||
|
/// of creating a new connection.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct QuicNewConnection {
|
||||||
|
endpoint: Arc<Endpoint>,
|
||||||
|
connection: Arc<NewConnection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuicNewConnection {
|
||||||
|
/// Create a QuicNewConnection given the remote address 'addr'.
|
||||||
|
async fn make_connection(
|
||||||
|
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
||||||
|
addr: SocketAddr,
|
||||||
|
stats: &ClientStats,
|
||||||
|
) -> Result<Self, QuicError> {
|
||||||
|
let mut make_connection_measure = Measure::start("make_connection_measure");
|
||||||
|
let endpoint = endpoint.get_endpoint().await;
|
||||||
|
|
||||||
|
let connecting = endpoint.connect(addr, "connect")?;
|
||||||
|
stats.total_connections.fetch_add(1, Ordering::Relaxed);
|
||||||
|
if let Ok(connecting_result) = timeout(
|
||||||
|
Duration::from_millis(QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS),
|
||||||
|
connecting,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if connecting_result.is_err() {
|
||||||
|
stats.connection_errors.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
make_connection_measure.stop();
|
||||||
|
stats
|
||||||
|
.make_connection_ms
|
||||||
|
.fetch_add(make_connection_measure.as_ms(), Ordering::Relaxed);
|
||||||
|
|
||||||
|
let connection = connecting_result?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
endpoint,
|
||||||
|
connection: Arc::new(connection),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(ConnectionError::TimedOut.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_endpoint(config: EndpointConfig, client_socket: UdpSocket) -> Endpoint {
|
||||||
|
quinn::Endpoint::new(config, None, client_socket)
|
||||||
|
.expect("QuicNewConnection::create_endpoint quinn::Endpoint::new")
|
||||||
|
.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to make a faster connection by taking advantage of pre-existing key material.
|
||||||
|
// Only works if connection to this endpoint was previously established.
|
||||||
|
async fn make_connection_0rtt(
|
||||||
|
&mut self,
|
||||||
|
addr: SocketAddr,
|
||||||
|
stats: &ClientStats,
|
||||||
|
) -> Result<Arc<NewConnection>, QuicError> {
|
||||||
|
let connecting = self.endpoint.connect(addr, "connect")?;
|
||||||
|
stats.total_connections.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let connection = match connecting.into_0rtt() {
|
||||||
|
Ok((connection, zero_rtt)) => {
|
||||||
|
if let Ok(zero_rtt) = timeout(
|
||||||
|
Duration::from_millis(QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS),
|
||||||
|
zero_rtt,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if zero_rtt {
|
||||||
|
stats.zero_rtt_accepts.fetch_add(1, Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
stats.zero_rtt_rejects.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
connection
|
||||||
|
} else {
|
||||||
|
return Err(ConnectionError::TimedOut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(connecting) => {
|
||||||
|
stats.connection_errors.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
if let Ok(connecting_result) = timeout(
|
||||||
|
Duration::from_millis(QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS),
|
||||||
|
connecting,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
connecting_result?
|
||||||
|
} else {
|
||||||
|
return Err(ConnectionError::TimedOut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.connection = Arc::new(connection);
|
||||||
|
Ok(self.connection.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QuicClient {
|
||||||
|
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
||||||
|
connection: Arc<Mutex<Option<QuicNewConnection>>>,
|
||||||
|
addr: SocketAddr,
|
||||||
|
stats: Arc<ClientStats>,
|
||||||
|
chunk_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuicClient {
|
||||||
|
pub fn new(
|
||||||
|
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
||||||
|
addr: SocketAddr,
|
||||||
|
chunk_size: usize,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
endpoint,
|
||||||
|
connection: Arc::new(Mutex::new(None)),
|
||||||
|
addr,
|
||||||
|
stats: Arc::new(ClientStats::default()),
|
||||||
|
chunk_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _send_buffer_using_conn(
|
||||||
|
data: &[u8],
|
||||||
|
connection: &NewConnection,
|
||||||
|
) -> Result<(), QuicError> {
|
||||||
|
let mut send_stream = connection.connection.open_uni().await?;
|
||||||
|
|
||||||
|
send_stream.write_all(data).await?;
|
||||||
|
send_stream.finish().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to send data, connecting/reconnecting as necessary
|
||||||
|
// On success, returns the connection used to successfully send the data
|
||||||
|
async fn _send_buffer(
|
||||||
|
&self,
|
||||||
|
data: &[u8],
|
||||||
|
stats: &ClientStats,
|
||||||
|
connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
) -> Result<Arc<NewConnection>, QuicError> {
|
||||||
|
let mut connection_try_count = 0;
|
||||||
|
let mut last_connection_id = 0;
|
||||||
|
let mut last_error = None;
|
||||||
|
|
||||||
|
while connection_try_count < 2 {
|
||||||
|
let connection = {
|
||||||
|
let mut conn_guard = self.connection.lock().await;
|
||||||
|
|
||||||
|
let maybe_conn = conn_guard.as_mut();
|
||||||
|
match maybe_conn {
|
||||||
|
Some(conn) => {
|
||||||
|
if conn.connection.connection.stable_id() == last_connection_id {
|
||||||
|
// this is the problematic connection we had used before, create a new one
|
||||||
|
let conn = conn.make_connection_0rtt(self.addr, stats).await;
|
||||||
|
match conn {
|
||||||
|
Ok(conn) => {
|
||||||
|
info!(
|
||||||
|
"Made 0rtt connection to {} with id {} try_count {}, last_connection_id: {}, last_error: {:?}",
|
||||||
|
self.addr,
|
||||||
|
conn.connection.stable_id(),
|
||||||
|
connection_try_count,
|
||||||
|
last_connection_id,
|
||||||
|
last_error,
|
||||||
|
);
|
||||||
|
connection_try_count += 1;
|
||||||
|
conn
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
info!(
|
||||||
|
"Cannot make 0rtt connection to {}, error {:}",
|
||||||
|
self.addr, err
|
||||||
|
);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stats.connection_reuse.fetch_add(1, Ordering::Relaxed);
|
||||||
|
conn.connection.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let conn = QuicNewConnection::make_connection(
|
||||||
|
self.endpoint.clone(),
|
||||||
|
self.addr,
|
||||||
|
stats,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match conn {
|
||||||
|
Ok(conn) => {
|
||||||
|
*conn_guard = Some(conn.clone());
|
||||||
|
info!(
|
||||||
|
"Made connection to {} id {} try_count {}",
|
||||||
|
self.addr,
|
||||||
|
conn.connection.connection.stable_id(),
|
||||||
|
connection_try_count
|
||||||
|
);
|
||||||
|
connection_try_count += 1;
|
||||||
|
conn.connection.clone()
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
info!("Cannot make connection to {}, error {:}", self.addr, err);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_stats = connection.connection.stats();
|
||||||
|
|
||||||
|
connection_stats
|
||||||
|
.total_client_stats
|
||||||
|
.congestion_events
|
||||||
|
.update_stat(
|
||||||
|
&self.stats.congestion_events,
|
||||||
|
new_stats.path.congestion_events,
|
||||||
|
);
|
||||||
|
|
||||||
|
connection_stats
|
||||||
|
.total_client_stats
|
||||||
|
.tx_streams_blocked_uni
|
||||||
|
.update_stat(
|
||||||
|
&self.stats.tx_streams_blocked_uni,
|
||||||
|
new_stats.frame_tx.streams_blocked_uni,
|
||||||
|
);
|
||||||
|
|
||||||
|
connection_stats
|
||||||
|
.total_client_stats
|
||||||
|
.tx_data_blocked
|
||||||
|
.update_stat(&self.stats.tx_data_blocked, new_stats.frame_tx.data_blocked);
|
||||||
|
|
||||||
|
connection_stats
|
||||||
|
.total_client_stats
|
||||||
|
.tx_acks
|
||||||
|
.update_stat(&self.stats.tx_acks, new_stats.frame_tx.acks);
|
||||||
|
|
||||||
|
last_connection_id = connection.connection.stable_id();
|
||||||
|
match Self::_send_buffer_using_conn(data, &connection).await {
|
||||||
|
Ok(()) => {
|
||||||
|
return Ok(connection);
|
||||||
|
}
|
||||||
|
Err(err) => match err {
|
||||||
|
QuicError::ConnectionError(_) => {
|
||||||
|
last_error = Some(err);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
info!(
|
||||||
|
"Error sending to {} with id {}, error {:?} thread: {:?}",
|
||||||
|
self.addr,
|
||||||
|
connection.connection.stable_id(),
|
||||||
|
err,
|
||||||
|
thread::current().id(),
|
||||||
|
);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we come here, that means we have exhausted maximum retries, return the error
|
||||||
|
info!(
|
||||||
|
"Ran into an error sending transactions {:?}, exhausted retries to {}",
|
||||||
|
last_error, self.addr
|
||||||
|
);
|
||||||
|
// If we get here but last_error is None, then we have a logic error
|
||||||
|
// in this function, so panic here with an expect to help debugging
|
||||||
|
Err(last_error.expect("QuicClient::_send_buffer last_error.expect"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_buffer<T>(
|
||||||
|
&self,
|
||||||
|
data: T,
|
||||||
|
stats: &ClientStats,
|
||||||
|
connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
) -> Result<(), ClientErrorKind>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
self._send_buffer(data.as_ref(), stats, connection_stats)
|
||||||
|
.await
|
||||||
|
.map_err(Into::<ClientErrorKind>::into)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_batch<T>(
|
||||||
|
&self,
|
||||||
|
buffers: &[T],
|
||||||
|
stats: &ClientStats,
|
||||||
|
connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
) -> Result<(), ClientErrorKind>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
// Start off by "testing" the connection by sending the first transaction
|
||||||
|
// This will also connect to the server if not already connected
|
||||||
|
// and reconnect and retry if the first send attempt failed
|
||||||
|
// (for example due to a timed out connection), returning an error
|
||||||
|
// or the connection that was used to successfully send the transaction.
|
||||||
|
// We will use the returned connection to send the rest of the transactions in the batch
|
||||||
|
// to avoid touching the mutex in self, and not bother reconnecting if we fail along the way
|
||||||
|
// since testing even in the ideal GCE environment has found no cases
|
||||||
|
// where reconnecting and retrying in the middle of a batch send
|
||||||
|
// (i.e. we encounter a connection error in the middle of a batch send, which presumably cannot
|
||||||
|
// be due to a timed out connection) has succeeded
|
||||||
|
if buffers.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let connection = self
|
||||||
|
._send_buffer(buffers[0].as_ref(), stats, connection_stats)
|
||||||
|
.await
|
||||||
|
.map_err(Into::<ClientErrorKind>::into)?;
|
||||||
|
|
||||||
|
// Used to avoid dereferencing the Arc multiple times below
|
||||||
|
// by just getting a reference to the NewConnection once
|
||||||
|
let connection_ref: &NewConnection = &connection;
|
||||||
|
|
||||||
|
let chunks = buffers[1..buffers.len()].iter().chunks(self.chunk_size);
|
||||||
|
|
||||||
|
let futures: Vec<_> = chunks
|
||||||
|
.into_iter()
|
||||||
|
.map(|buffs| {
|
||||||
|
join_all(
|
||||||
|
buffs
|
||||||
|
.into_iter()
|
||||||
|
.map(|buf| Self::_send_buffer_using_conn(buf.as_ref(), connection_ref)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for f in futures {
|
||||||
|
f.await
|
||||||
|
.into_iter()
|
||||||
|
.try_for_each(|res| res)
|
||||||
|
.map_err(Into::<ClientErrorKind>::into)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
&self.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stats(&self) -> Arc<ClientStats> {
|
||||||
|
self.stats.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QuicTpuConnection {
|
||||||
|
pub client: Arc<QuicClient>,
|
||||||
|
pub connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuicTpuConnection {
|
||||||
|
pub fn base_stats(&self) -> Arc<ClientStats> {
|
||||||
|
self.client.stats()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connection_stats(&self) -> Arc<ConnectionCacheStats> {
|
||||||
|
self.connection_stats.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
||||||
|
addr: SocketAddr,
|
||||||
|
connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
) -> Self {
|
||||||
|
let client = Arc::new(QuicClient::new(
|
||||||
|
endpoint,
|
||||||
|
addr,
|
||||||
|
QUIC_MAX_UNSTAKED_CONCURRENT_STREAMS,
|
||||||
|
));
|
||||||
|
Self::new_with_client(client, connection_stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_client(
|
||||||
|
client: Arc<QuicClient>,
|
||||||
|
connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
connection_stats,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl TpuConnection for QuicTpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
self.client.tpu_addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
let stats = ClientStats::default();
|
||||||
|
let len = buffers.len();
|
||||||
|
let res = self
|
||||||
|
.client
|
||||||
|
.send_batch(buffers, &stats, self.connection_stats.clone())
|
||||||
|
.await;
|
||||||
|
self.connection_stats
|
||||||
|
.add_client_stats(&stats, len, res.is_ok());
|
||||||
|
res?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
let stats = Arc::new(ClientStats::default());
|
||||||
|
let send_buffer =
|
||||||
|
self.client
|
||||||
|
.send_buffer(wire_transaction, &stats, self.connection_stats.clone());
|
||||||
|
if let Err(e) = send_buffer.await {
|
||||||
|
warn!(
|
||||||
|
"Failed to send transaction async to {}, error: {:?} ",
|
||||||
|
self.tpu_addr(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
datapoint_warn!("send-wire-async", ("failure", 1, i64),);
|
||||||
|
self.connection_stats.add_client_stats(&stats, 1, false);
|
||||||
|
} else {
|
||||||
|
self.connection_stats.add_client_stats(&stats, 1, true);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,189 @@
|
||||||
//! Simple client that connects to a given UDP port with the QUIC protocol and provides
|
//! Simple client that connects to a given UDP port with the QUIC protocol and provides
|
||||||
//! an interface for sending transactions which is restricted by the server's flow control.
|
//! an interface for sending transactions which is restricted by the server's flow control.
|
||||||
|
|
||||||
pub use solana_tpu_client::quic_client::*;
|
use {
|
||||||
|
crate::nonblocking::quic_client::{
|
||||||
|
QuicClient, QuicLazyInitializedEndpoint, QuicTpuConnection as NonblockingQuicTpuConnection,
|
||||||
|
},
|
||||||
|
lazy_static::lazy_static,
|
||||||
|
log::*,
|
||||||
|
solana_sdk::transport::{Result as TransportResult, TransportError},
|
||||||
|
solana_tpu_client::{
|
||||||
|
connection_cache_stats::ConnectionCacheStats,
|
||||||
|
nonblocking::tpu_connection::TpuConnection as NonblockingTpuConnection,
|
||||||
|
tpu_connection::{ClientStats, TpuConnection},
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::{atomic::Ordering, Arc, Condvar, Mutex, MutexGuard},
|
||||||
|
time::Duration,
|
||||||
|
},
|
||||||
|
tokio::{runtime::Runtime, time::timeout},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod temporary_pub {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub const MAX_OUTSTANDING_TASK: u64 = 2000;
|
||||||
|
pub const SEND_TRANSACTION_TIMEOUT_MS: u64 = 10000;
|
||||||
|
|
||||||
|
/// A semaphore used for limiting the number of asynchronous tasks spawn to the
|
||||||
|
/// runtime. Before spawnning a task, use acquire. After the task is done (be it
|
||||||
|
/// succsess or failure), call release.
|
||||||
|
pub struct AsyncTaskSemaphore {
|
||||||
|
/// Keep the counter info about the usage
|
||||||
|
counter: Mutex<u64>,
|
||||||
|
/// Conditional variable for signaling when counter is decremented
|
||||||
|
cond_var: Condvar,
|
||||||
|
/// The maximum usage allowed by this semaphore.
|
||||||
|
permits: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncTaskSemaphore {
|
||||||
|
pub fn new(permits: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
counter: Mutex::new(0),
|
||||||
|
cond_var: Condvar::new(),
|
||||||
|
permits,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When returned, the lock has been locked and usage count has been
|
||||||
|
/// incremented. When the returned MutexGuard is dropped the lock is dropped
|
||||||
|
/// without decrementing the usage count.
|
||||||
|
pub fn acquire(&self) -> MutexGuard<u64> {
|
||||||
|
let mut count = self.counter.lock().unwrap();
|
||||||
|
*count += 1;
|
||||||
|
while *count > self.permits {
|
||||||
|
count = self.cond_var.wait(count).unwrap();
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Acquire the lock and decrement the usage count
|
||||||
|
pub fn release(&self) {
|
||||||
|
let mut count = self.counter.lock().unwrap();
|
||||||
|
*count -= 1;
|
||||||
|
self.cond_var.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref ASYNC_TASK_SEMAPHORE: AsyncTaskSemaphore =
|
||||||
|
AsyncTaskSemaphore::new(MAX_OUTSTANDING_TASK);
|
||||||
|
pub static ref RUNTIME: Runtime = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.thread_name("quic-client")
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_wire_transaction_async(
|
||||||
|
connection: Arc<NonblockingQuicTpuConnection>,
|
||||||
|
wire_transaction: Vec<u8>,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let result = timeout(
|
||||||
|
Duration::from_millis(SEND_TRANSACTION_TIMEOUT_MS),
|
||||||
|
connection.send_wire_transaction(wire_transaction),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
ASYNC_TASK_SEMAPHORE.release();
|
||||||
|
handle_send_result(result, connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_wire_transaction_batch_async(
|
||||||
|
connection: Arc<NonblockingQuicTpuConnection>,
|
||||||
|
buffers: Vec<Vec<u8>>,
|
||||||
|
) -> TransportResult<()> {
|
||||||
|
let time_out = SEND_TRANSACTION_TIMEOUT_MS * buffers.len() as u64;
|
||||||
|
|
||||||
|
let result = timeout(
|
||||||
|
Duration::from_millis(time_out),
|
||||||
|
connection.send_wire_transaction_batch(&buffers),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
ASYNC_TASK_SEMAPHORE.release();
|
||||||
|
handle_send_result(result, connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the send result and update stats if timedout. Returns the checked result.
|
||||||
|
pub fn handle_send_result(
|
||||||
|
result: Result<Result<(), TransportError>, tokio::time::error::Elapsed>,
|
||||||
|
connection: Arc<NonblockingQuicTpuConnection>,
|
||||||
|
) -> Result<(), TransportError> {
|
||||||
|
match result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_err) => {
|
||||||
|
let client_stats = ClientStats::default();
|
||||||
|
client_stats.send_timeout.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let stats = connection.connection_stats();
|
||||||
|
stats.add_client_stats(&client_stats, 0, false);
|
||||||
|
info!("Timedout sending transaction {:?}", connection.tpu_addr());
|
||||||
|
Err(TransportError::Custom(
|
||||||
|
"Timedout sending transaction".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
use temporary_pub::*;
|
||||||
|
|
||||||
|
pub struct QuicTpuConnection {
|
||||||
|
pub inner: Arc<NonblockingQuicTpuConnection>,
|
||||||
|
}
|
||||||
|
impl QuicTpuConnection {
|
||||||
|
pub fn new(
|
||||||
|
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
||||||
|
tpu_addr: SocketAddr,
|
||||||
|
connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
) -> Self {
|
||||||
|
let inner = Arc::new(NonblockingQuicTpuConnection::new(
|
||||||
|
endpoint,
|
||||||
|
tpu_addr,
|
||||||
|
connection_stats,
|
||||||
|
));
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_client(
|
||||||
|
client: Arc<QuicClient>,
|
||||||
|
connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
) -> Self {
|
||||||
|
let inner = Arc::new(NonblockingQuicTpuConnection::new_with_client(
|
||||||
|
client,
|
||||||
|
connection_stats,
|
||||||
|
));
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TpuConnection for QuicTpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
self.inner.tpu_addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
RUNTIME.block_on(self.inner.send_wire_transaction_batch(buffers))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_async(&self, wire_transaction: Vec<u8>) -> TransportResult<()> {
|
||||||
|
let _lock = ASYNC_TASK_SEMAPHORE.acquire();
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
|
||||||
|
let _ = RUNTIME
|
||||||
|
.spawn(async move { send_wire_transaction_async(inner, wire_transaction).await });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_batch_async(&self, buffers: Vec<Vec<u8>>) -> TransportResult<()> {
|
||||||
|
let _lock = ASYNC_TASK_SEMAPHORE.acquire();
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
let _ =
|
||||||
|
RUNTIME.spawn(async move { send_wire_transaction_batch_async(inner, buffers).await });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,12 +3,10 @@ mod tests {
|
||||||
use {
|
use {
|
||||||
crossbeam_channel::{unbounded, Receiver},
|
crossbeam_channel::{unbounded, Receiver},
|
||||||
solana_perf::packet::PacketBatch,
|
solana_perf::packet::PacketBatch,
|
||||||
|
solana_quic_client::nonblocking::quic_client::QuicLazyInitializedEndpoint,
|
||||||
solana_sdk::{packet::PACKET_DATA_SIZE, signature::Keypair},
|
solana_sdk::{packet::PACKET_DATA_SIZE, signature::Keypair},
|
||||||
solana_streamer::{quic::StreamStats, streamer::StakedNodes},
|
solana_streamer::{quic::StreamStats, streamer::StakedNodes},
|
||||||
solana_tpu_client::{
|
solana_tpu_client::connection_cache_stats::ConnectionCacheStats,
|
||||||
connection_cache_stats::ConnectionCacheStats,
|
|
||||||
nonblocking::quic_client::QuicLazyInitializedEndpoint,
|
|
||||||
},
|
|
||||||
std::{
|
std::{
|
||||||
net::{IpAddr, SocketAddr, UdpSocket},
|
net::{IpAddr, SocketAddr, UdpSocket},
|
||||||
sync::{
|
sync::{
|
||||||
|
@ -62,7 +60,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_quic_client_multiple_writes() {
|
fn test_quic_client_multiple_writes() {
|
||||||
use solana_tpu_client::{quic_client::QuicTpuConnection, tpu_connection::TpuConnection};
|
use {
|
||||||
|
solana_quic_client::quic_client::QuicTpuConnection,
|
||||||
|
solana_tpu_client::tpu_connection::TpuConnection,
|
||||||
|
};
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
let (sender, receiver) = unbounded();
|
let (sender, receiver) = unbounded();
|
||||||
let staked_nodes = Arc::new(RwLock::new(StakedNodes::default()));
|
let staked_nodes = Arc::new(RwLock::new(StakedNodes::default()));
|
||||||
|
@ -106,8 +107,9 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_nonblocking_quic_client_multiple_writes() {
|
async fn test_nonblocking_quic_client_multiple_writes() {
|
||||||
use solana_tpu_client::nonblocking::{
|
use {
|
||||||
quic_client::QuicTpuConnection, tpu_connection::TpuConnection,
|
solana_quic_client::nonblocking::quic_client::QuicTpuConnection,
|
||||||
|
solana_tpu_client::nonblocking::tpu_connection::TpuConnection,
|
||||||
};
|
};
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
let (sender, receiver) = unbounded();
|
let (sender, receiver) = unbounded();
|
|
@ -20,6 +20,7 @@ reqwest = { version = "0.11.12", default-features = false, features = ["blocking
|
||||||
serde = "1.0.144"
|
serde = "1.0.144"
|
||||||
serde_json = "1.0.83"
|
serde_json = "1.0.83"
|
||||||
solana-account-decoder = { path = "../account-decoder", version = "=1.15.0" }
|
solana-account-decoder = { path = "../account-decoder", version = "=1.15.0" }
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-pubsub-client = { path = "../pubsub-client", version = "=1.15.0" }
|
solana-pubsub-client = { path = "../pubsub-client", version = "=1.15.0" }
|
||||||
solana-rpc = { path = "../rpc", version = "=1.15.0" }
|
solana-rpc = { path = "../rpc", version = "=1.15.0" }
|
||||||
solana-rpc-client = { path = "../rpc-client", version = "=1.15.0", default-features = false }
|
solana-rpc-client = { path = "../rpc-client", version = "=1.15.0", default-features = false }
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use {
|
use {
|
||||||
solana_sdk::{clock::DEFAULT_MS_PER_SLOT, pubkey::Pubkey, system_transaction},
|
solana_client::{
|
||||||
solana_test_validator::TestValidatorGenesis,
|
|
||||||
solana_tpu_client::{
|
|
||||||
nonblocking::tpu_client::{LeaderTpuService, TpuClient},
|
nonblocking::tpu_client::{LeaderTpuService, TpuClient},
|
||||||
tpu_client::TpuClientConfig,
|
tpu_client::TpuClientConfig,
|
||||||
},
|
},
|
||||||
|
solana_sdk::{clock::DEFAULT_MS_PER_SLOT, pubkey::Pubkey, system_transaction},
|
||||||
|
solana_test_validator::TestValidatorGenesis,
|
||||||
std::sync::{
|
std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
|
|
|
@ -6,6 +6,10 @@ use {
|
||||||
reqwest::{self, header::CONTENT_TYPE},
|
reqwest::{self, header::CONTENT_TYPE},
|
||||||
serde_json::{json, Value},
|
serde_json::{json, Value},
|
||||||
solana_account_decoder::UiAccount,
|
solana_account_decoder::UiAccount,
|
||||||
|
solana_client::{
|
||||||
|
connection_cache::{ConnectionCache, DEFAULT_TPU_CONNECTION_POOL_SIZE},
|
||||||
|
tpu_client::{TpuClient, TpuClientConfig},
|
||||||
|
},
|
||||||
solana_pubsub_client::nonblocking::pubsub_client::PubsubClient,
|
solana_pubsub_client::nonblocking::pubsub_client::PubsubClient,
|
||||||
solana_rpc_client::rpc_client::RpcClient,
|
solana_rpc_client::rpc_client::RpcClient,
|
||||||
solana_rpc_client_api::{
|
solana_rpc_client_api::{
|
||||||
|
@ -25,10 +29,6 @@ use {
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_test_validator::TestValidator,
|
solana_test_validator::TestValidator,
|
||||||
solana_tpu_client::{
|
|
||||||
connection_cache::{ConnectionCache, DEFAULT_TPU_CONNECTION_POOL_SIZE},
|
|
||||||
tpu_client::{TpuClient, TpuClientConfig},
|
|
||||||
},
|
|
||||||
solana_transaction_status::TransactionStatus,
|
solana_transaction_status::TransactionStatus,
|
||||||
std::{
|
std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
|
|
@ -30,6 +30,7 @@ serde_derive = "1.0.103"
|
||||||
serde_json = "1.0.83"
|
serde_json = "1.0.83"
|
||||||
soketto = "0.7"
|
soketto = "0.7"
|
||||||
solana-account-decoder = { path = "../account-decoder", version = "=1.15.0" }
|
solana-account-decoder = { path = "../account-decoder", version = "=1.15.0" }
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-entry = { path = "../entry", version = "=1.15.0" }
|
solana-entry = { path = "../entry", version = "=1.15.0" }
|
||||||
solana-faucet = { path = "../faucet", version = "=1.15.0" }
|
solana-faucet = { path = "../faucet", version = "=1.15.0" }
|
||||||
solana-gossip = { path = "../gossip", version = "=1.15.0" }
|
solana-gossip = { path = "../gossip", version = "=1.15.0" }
|
||||||
|
|
|
@ -13,6 +13,7 @@ use {
|
||||||
parse_token::{is_known_spl_token_id, token_amount_to_ui_amount, UiTokenAmount},
|
parse_token::{is_known_spl_token_id, token_amount_to_ui_amount, UiTokenAmount},
|
||||||
UiAccount, UiAccountEncoding, UiDataSliceConfig, MAX_BASE58_BYTES,
|
UiAccount, UiAccountEncoding, UiDataSliceConfig, MAX_BASE58_BYTES,
|
||||||
},
|
},
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_entry::entry::Entry,
|
solana_entry::entry::Entry,
|
||||||
solana_faucet::faucet::request_airdrop_transaction,
|
solana_faucet::faucet::request_airdrop_transaction,
|
||||||
solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo},
|
solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo},
|
||||||
|
@ -81,7 +82,6 @@ use {
|
||||||
solana_stake_program,
|
solana_stake_program,
|
||||||
solana_storage_bigtable::Error as StorageError,
|
solana_storage_bigtable::Error as StorageError,
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
solana_transaction_status::{
|
solana_transaction_status::{
|
||||||
BlockEncodingOptions, ConfirmedBlock, ConfirmedTransactionStatusWithSignature,
|
BlockEncodingOptions, ConfirmedBlock, ConfirmedTransactionStatusWithSignature,
|
||||||
ConfirmedTransactionWithStatusMeta, EncodedConfirmedTransactionWithStatusMeta, Reward,
|
ConfirmedTransactionWithStatusMeta, EncodedConfirmedTransactionWithStatusMeta, Reward,
|
||||||
|
|
|
@ -19,6 +19,7 @@ use {
|
||||||
RequestMiddlewareAction, ServerBuilder,
|
RequestMiddlewareAction, ServerBuilder,
|
||||||
},
|
},
|
||||||
regex::Regex,
|
regex::Regex,
|
||||||
|
solana_client::connection_cache::ConnectionCache,
|
||||||
solana_gossip::cluster_info::ClusterInfo,
|
solana_gossip::cluster_info::ClusterInfo,
|
||||||
solana_ledger::{
|
solana_ledger::{
|
||||||
bigtable_upload::ConfirmedBlockUploadConfig,
|
bigtable_upload::ConfirmedBlockUploadConfig,
|
||||||
|
@ -40,7 +41,6 @@ use {
|
||||||
},
|
},
|
||||||
solana_send_transaction_service::send_transaction_service::{self, SendTransactionService},
|
solana_send_transaction_service::send_transaction_service::{self, SendTransactionService},
|
||||||
solana_storage_bigtable::CredentialType,
|
solana_storage_bigtable::CredentialType,
|
||||||
solana_tpu_client::connection_cache::ConnectionCache,
|
|
||||||
std::{
|
std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
|
|
|
@ -12,6 +12,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
solana-client = { path = "../client", version = "=1.15.0" }
|
||||||
solana-measure = { path = "../measure", version = "=1.15.0" }
|
solana-measure = { path = "../measure", version = "=1.15.0" }
|
||||||
solana-metrics = { path = "../metrics", version = "=1.15.0" }
|
solana-metrics = { path = "../metrics", version = "=1.15.0" }
|
||||||
solana-runtime = { path = "../runtime", version = "=1.15.0" }
|
solana-runtime = { path = "../runtime", version = "=1.15.0" }
|
||||||
|
|
|
@ -2,6 +2,7 @@ use {
|
||||||
crate::tpu_info::TpuInfo,
|
crate::tpu_info::TpuInfo,
|
||||||
crossbeam_channel::{Receiver, RecvTimeoutError},
|
crossbeam_channel::{Receiver, RecvTimeoutError},
|
||||||
log::*,
|
log::*,
|
||||||
|
solana_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
||||||
solana_measure::measure::Measure,
|
solana_measure::measure::Measure,
|
||||||
solana_metrics::datapoint_warn,
|
solana_metrics::datapoint_warn,
|
||||||
solana_runtime::{bank::Bank, bank_forks::BankForks},
|
solana_runtime::{bank::Bank, bank_forks::BankForks},
|
||||||
|
@ -9,7 +10,6 @@ use {
|
||||||
hash::Hash, nonce_account, pubkey::Pubkey, saturating_add_assign, signature::Signature,
|
hash::Hash, nonce_account, pubkey::Pubkey, saturating_add_assign, signature::Signature,
|
||||||
timing::AtomicInterval, transport::TransportError,
|
timing::AtomicInterval, transport::TransportError,
|
||||||
},
|
},
|
||||||
solana_tpu_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
|
||||||
std::{
|
std::{
|
||||||
collections::{
|
collections::{
|
||||||
hash_map::{Entry, HashMap},
|
hash_map::{Entry, HashMap},
|
||||||
|
|
|
@ -44,7 +44,7 @@ use {
|
||||||
signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
|
signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_tpu_client::connection_cache::{
|
solana_tpu_client::tpu_connection_cache::{
|
||||||
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
|
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP, DEFAULT_TPU_USE_QUIC,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
|
|
@ -25,7 +25,10 @@ use {
|
||||||
transaction::{self, Transaction, VersionedTransaction},
|
transaction::{self, Transaction, VersionedTransaction},
|
||||||
transport::Result as TransportResult,
|
transport::Result as TransportResult,
|
||||||
},
|
},
|
||||||
solana_tpu_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection},
|
solana_tpu_client::{
|
||||||
|
tpu_connection::TpuConnection,
|
||||||
|
tpu_connection_cache::{ConnectionPool, TpuConnectionCache},
|
||||||
|
},
|
||||||
std::{
|
std::{
|
||||||
io,
|
io,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
|
@ -37,7 +40,10 @@ use {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ClientOptimizer {
|
pub mod temporary_pub {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct ClientOptimizer {
|
||||||
cur_index: AtomicUsize,
|
cur_index: AtomicUsize,
|
||||||
experiment_index: AtomicUsize,
|
experiment_index: AtomicUsize,
|
||||||
experiment_done: AtomicBool,
|
experiment_done: AtomicBool,
|
||||||
|
@ -45,20 +51,8 @@ struct ClientOptimizer {
|
||||||
num_clients: usize,
|
num_clients: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min_index(array: &[u64]) -> (u64, usize) {
|
|
||||||
let mut min_time = std::u64::MAX;
|
|
||||||
let mut min_index = 0;
|
|
||||||
for (i, time) in array.iter().enumerate() {
|
|
||||||
if *time < min_time {
|
|
||||||
min_time = *time;
|
|
||||||
min_index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(min_time, min_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClientOptimizer {
|
impl ClientOptimizer {
|
||||||
fn new(num_clients: usize) -> Self {
|
pub fn new(num_clients: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cur_index: AtomicUsize::new(0),
|
cur_index: AtomicUsize::new(0),
|
||||||
experiment_index: AtomicUsize::new(0),
|
experiment_index: AtomicUsize::new(0),
|
||||||
|
@ -68,7 +62,7 @@ impl ClientOptimizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn experiment(&self) -> usize {
|
pub fn experiment(&self) -> usize {
|
||||||
if self.experiment_index.load(Ordering::Relaxed) < self.num_clients {
|
if self.experiment_index.load(Ordering::Relaxed) < self.num_clients {
|
||||||
let old = self.experiment_index.fetch_add(1, Ordering::Relaxed);
|
let old = self.experiment_index.fetch_add(1, Ordering::Relaxed);
|
||||||
if old < self.num_clients {
|
if old < self.num_clients {
|
||||||
|
@ -81,7 +75,7 @@ impl ClientOptimizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report(&self, index: usize, time_ms: u64) {
|
pub fn report(&self, index: usize, time_ms: u64) {
|
||||||
if self.num_clients > 1
|
if self.num_clients > 1
|
||||||
&& (!self.experiment_done.load(Ordering::Relaxed) || time_ms == std::u64::MAX)
|
&& (!self.experiment_done.load(Ordering::Relaxed) || time_ms == std::u64::MAX)
|
||||||
{
|
{
|
||||||
|
@ -111,27 +105,29 @@ impl ClientOptimizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn best(&self) -> usize {
|
pub fn best(&self) -> usize {
|
||||||
self.cur_index.load(Ordering::Relaxed)
|
self.cur_index.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
use temporary_pub::*;
|
||||||
|
|
||||||
/// An object for querying and sending transactions to the network.
|
/// An object for querying and sending transactions to the network.
|
||||||
pub struct ThinClient {
|
pub struct ThinClient<P: ConnectionPool> {
|
||||||
rpc_clients: Vec<RpcClient>,
|
rpc_clients: Vec<RpcClient>,
|
||||||
tpu_addrs: Vec<SocketAddr>,
|
tpu_addrs: Vec<SocketAddr>,
|
||||||
optimizer: ClientOptimizer,
|
optimizer: ClientOptimizer,
|
||||||
connection_cache: Arc<ConnectionCache>,
|
connection_cache: Arc<TpuConnectionCache<P>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThinClient {
|
impl<P: ConnectionPool> ThinClient<P> {
|
||||||
/// Create a new ThinClient that will interface with the Rpc at `rpc_addr` using TCP
|
/// Create a new ThinClient that will interface with the Rpc at `rpc_addr` using TCP
|
||||||
/// and the Tpu at `tpu_addr` over `transactions_socket` using Quic or UDP
|
/// and the Tpu at `tpu_addr` over `transactions_socket` using Quic or UDP
|
||||||
/// (currently hardcoded to UDP)
|
/// (currently hardcoded to UDP)
|
||||||
pub fn new(
|
pub fn new(
|
||||||
rpc_addr: SocketAddr,
|
rpc_addr: SocketAddr,
|
||||||
tpu_addr: SocketAddr,
|
tpu_addr: SocketAddr,
|
||||||
connection_cache: Arc<ConnectionCache>,
|
connection_cache: Arc<TpuConnectionCache<P>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_from_client(RpcClient::new_socket(rpc_addr), tpu_addr, connection_cache)
|
Self::new_from_client(RpcClient::new_socket(rpc_addr), tpu_addr, connection_cache)
|
||||||
}
|
}
|
||||||
|
@ -140,7 +136,7 @@ impl ThinClient {
|
||||||
rpc_addr: SocketAddr,
|
rpc_addr: SocketAddr,
|
||||||
tpu_addr: SocketAddr,
|
tpu_addr: SocketAddr,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
connection_cache: Arc<ConnectionCache>,
|
connection_cache: Arc<TpuConnectionCache<P>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rpc_client = RpcClient::new_socket_with_timeout(rpc_addr, timeout);
|
let rpc_client = RpcClient::new_socket_with_timeout(rpc_addr, timeout);
|
||||||
Self::new_from_client(rpc_client, tpu_addr, connection_cache)
|
Self::new_from_client(rpc_client, tpu_addr, connection_cache)
|
||||||
|
@ -149,7 +145,7 @@ impl ThinClient {
|
||||||
fn new_from_client(
|
fn new_from_client(
|
||||||
rpc_client: RpcClient,
|
rpc_client: RpcClient,
|
||||||
tpu_addr: SocketAddr,
|
tpu_addr: SocketAddr,
|
||||||
connection_cache: Arc<ConnectionCache>,
|
connection_cache: Arc<TpuConnectionCache<P>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rpc_clients: vec![rpc_client],
|
rpc_clients: vec![rpc_client],
|
||||||
|
@ -162,7 +158,7 @@ impl ThinClient {
|
||||||
pub fn new_from_addrs(
|
pub fn new_from_addrs(
|
||||||
rpc_addrs: Vec<SocketAddr>,
|
rpc_addrs: Vec<SocketAddr>,
|
||||||
tpu_addrs: Vec<SocketAddr>,
|
tpu_addrs: Vec<SocketAddr>,
|
||||||
connection_cache: Arc<ConnectionCache>,
|
connection_cache: Arc<TpuConnectionCache<P>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
assert!(!rpc_addrs.is_empty());
|
assert!(!rpc_addrs.is_empty());
|
||||||
assert_eq!(rpc_addrs.len(), tpu_addrs.len());
|
assert_eq!(rpc_addrs.len(), tpu_addrs.len());
|
||||||
|
@ -320,13 +316,13 @@ impl ThinClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client for ThinClient {
|
impl<P: ConnectionPool> Client for ThinClient<P> {
|
||||||
fn tpu_addr(&self) -> String {
|
fn tpu_addr(&self) -> String {
|
||||||
self.tpu_addr().to_string()
|
self.tpu_addr().to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncClient for ThinClient {
|
impl<P: ConnectionPool> SyncClient for ThinClient<P> {
|
||||||
fn send_and_confirm_message<T: Signers>(
|
fn send_and_confirm_message<T: Signers>(
|
||||||
&self,
|
&self,
|
||||||
keypairs: &T,
|
keypairs: &T,
|
||||||
|
@ -606,7 +602,7 @@ impl SyncClient for ThinClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncClient for ThinClient {
|
impl<P: ConnectionPool> AsyncClient for ThinClient<P> {
|
||||||
fn async_send_versioned_transaction(
|
fn async_send_versioned_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: VersionedTransaction,
|
transaction: VersionedTransaction,
|
||||||
|
@ -626,6 +622,18 @@ impl AsyncClient for ThinClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn min_index(array: &[u64]) -> (u64, usize) {
|
||||||
|
let mut min_time = std::u64::MAX;
|
||||||
|
let mut min_index = 0;
|
||||||
|
for (i, time) in array.iter().enumerate() {
|
||||||
|
if *time < min_time {
|
||||||
|
min_time = *time;
|
||||||
|
min_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(min_time, min_index)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use {super::*, rayon::prelude::*};
|
use {super::*, rayon::prelude::*};
|
||||||
|
|
|
@ -10,23 +10,14 @@ documentation = "https://docs.rs/solana-tpu-client"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-mutex = "1.4.0"
|
|
||||||
async-trait = "0.1.57"
|
async-trait = "0.1.57"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
enum_dispatch = "0.3.8"
|
|
||||||
futures = "0.3"
|
|
||||||
futures-util = "0.3.21"
|
futures-util = "0.3.21"
|
||||||
indexmap = "1.9.1"
|
indexmap = "1.9.1"
|
||||||
indicatif = { version = "0.17.1", optional = true }
|
indicatif = { version = "0.17.1", optional = true }
|
||||||
itertools = "0.10.5"
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
quinn = "0.8.4"
|
|
||||||
quinn-proto = "0.8.4"
|
|
||||||
quinn-udp = "0.1.3"
|
|
||||||
rand = "0.7.0"
|
rand = "0.7.0"
|
||||||
rayon = "1.5.3"
|
rayon = "1.5.3"
|
||||||
rustls = { version = "0.20.6", features = ["dangerous_configuration"] }
|
|
||||||
solana-measure = { path = "../measure", version = "=1.15.0" }
|
solana-measure = { path = "../measure", version = "=1.15.0" }
|
||||||
solana-metrics = { path = "../metrics", version = "=1.15.0" }
|
solana-metrics = { path = "../metrics", version = "=1.15.0" }
|
||||||
solana-net-utils = { path = "../net-utils", version = "=1.15.0" }
|
solana-net-utils = { path = "../net-utils", version = "=1.15.0" }
|
||||||
|
@ -34,15 +25,12 @@ solana-pubsub-client = { path = "../pubsub-client", version = "=1.15.0" }
|
||||||
solana-rpc-client = { path = "../rpc-client", version = "=1.15.0", default-features = false }
|
solana-rpc-client = { path = "../rpc-client", version = "=1.15.0", default-features = false }
|
||||||
solana-rpc-client-api = { path = "../rpc-client-api", version = "=1.15.0" }
|
solana-rpc-client-api = { path = "../rpc-client-api", version = "=1.15.0" }
|
||||||
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
||||||
solana-streamer = { path = "../streamer", version = "=1.15.0" }
|
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
crossbeam-channel = "0.5"
|
|
||||||
rand_chacha = "0.2.2"
|
rand_chacha = "0.2.2"
|
||||||
solana-logger = { path = "../logger", version = "=1.15.0" }
|
solana-logger = { path = "../logger", version = "=1.15.0" }
|
||||||
solana-perf = { path = "../perf", version = "=1.15.0" }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["spinner"]
|
default = ["spinner"]
|
||||||
|
|
|
@ -5,25 +5,25 @@ use {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ConnectionCacheStats {
|
pub struct ConnectionCacheStats {
|
||||||
pub(crate) cache_hits: AtomicU64,
|
pub cache_hits: AtomicU64,
|
||||||
pub(crate) cache_misses: AtomicU64,
|
pub cache_misses: AtomicU64,
|
||||||
pub(crate) cache_evictions: AtomicU64,
|
pub cache_evictions: AtomicU64,
|
||||||
pub(crate) eviction_time_ms: AtomicU64,
|
pub eviction_time_ms: AtomicU64,
|
||||||
pub(crate) sent_packets: AtomicU64,
|
pub sent_packets: AtomicU64,
|
||||||
pub(crate) total_batches: AtomicU64,
|
pub total_batches: AtomicU64,
|
||||||
pub(crate) batch_success: AtomicU64,
|
pub batch_success: AtomicU64,
|
||||||
pub(crate) batch_failure: AtomicU64,
|
pub batch_failure: AtomicU64,
|
||||||
pub(crate) get_connection_ms: AtomicU64,
|
pub get_connection_ms: AtomicU64,
|
||||||
pub(crate) get_connection_lock_ms: AtomicU64,
|
pub get_connection_lock_ms: AtomicU64,
|
||||||
pub(crate) get_connection_hit_ms: AtomicU64,
|
pub get_connection_hit_ms: AtomicU64,
|
||||||
pub(crate) get_connection_miss_ms: AtomicU64,
|
pub get_connection_miss_ms: AtomicU64,
|
||||||
|
|
||||||
// Need to track these separately per-connection
|
// Need to track these separately per-connection
|
||||||
// because we need to track the base stat value from quinn
|
// because we need to track the base stat value from quinn
|
||||||
pub total_client_stats: ClientStats,
|
pub total_client_stats: ClientStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const CONNECTION_STAT_SUBMISSION_INTERVAL: u64 = 2000;
|
pub const CONNECTION_STAT_SUBMISSION_INTERVAL: u64 = 2000;
|
||||||
|
|
||||||
impl ConnectionCacheStats {
|
impl ConnectionCacheStats {
|
||||||
pub fn add_client_stats(
|
pub fn add_client_stats(
|
||||||
|
@ -70,7 +70,7 @@ impl ConnectionCacheStats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn report(&self) {
|
pub fn report(&self) {
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
"quic-client-connection-stats",
|
"quic-client-connection-stats",
|
||||||
(
|
(
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
#![allow(clippy::integer_arithmetic)]
|
#![allow(clippy::integer_arithmetic)]
|
||||||
|
|
||||||
pub mod connection_cache;
|
|
||||||
pub mod connection_cache_stats;
|
pub mod connection_cache_stats;
|
||||||
pub mod nonblocking;
|
pub mod nonblocking;
|
||||||
pub mod quic_client;
|
|
||||||
pub mod tpu_client;
|
pub mod tpu_client;
|
||||||
pub mod tpu_connection;
|
pub mod tpu_connection;
|
||||||
pub mod tpu_connection_cache;
|
pub mod tpu_connection_cache;
|
||||||
pub mod udp_client;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate solana_metrics;
|
extern crate solana_metrics;
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
pub mod quic_client;
|
|
||||||
pub mod tpu_client;
|
pub mod tpu_client;
|
||||||
pub mod tpu_connection;
|
pub mod tpu_connection;
|
||||||
pub mod udp_client;
|
|
||||||
|
|
|
@ -1,598 +0,0 @@
|
||||||
//! Simple nonblocking client that connects to a given UDP port with the QUIC protocol
|
|
||||||
//! and provides an interface for sending transactions which is restricted by the
|
|
||||||
//! server's flow control.
|
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
connection_cache_stats::ConnectionCacheStats, nonblocking::tpu_connection::TpuConnection,
|
|
||||||
tpu_connection::ClientStats,
|
|
||||||
},
|
|
||||||
async_mutex::Mutex,
|
|
||||||
async_trait::async_trait,
|
|
||||||
futures::future::join_all,
|
|
||||||
itertools::Itertools,
|
|
||||||
log::*,
|
|
||||||
quinn::{
|
|
||||||
ClientConfig, ConnectError, ConnectionError, Endpoint, EndpointConfig, IdleTimeout,
|
|
||||||
NewConnection, VarInt, WriteError,
|
|
||||||
},
|
|
||||||
solana_measure::measure::Measure,
|
|
||||||
solana_net_utils::VALIDATOR_PORT_RANGE,
|
|
||||||
solana_rpc_client_api::client_error::ErrorKind as ClientErrorKind,
|
|
||||||
solana_sdk::{
|
|
||||||
quic::{
|
|
||||||
QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS, QUIC_KEEP_ALIVE_MS, QUIC_MAX_TIMEOUT_MS,
|
|
||||||
QUIC_MAX_UNSTAKED_CONCURRENT_STREAMS,
|
|
||||||
},
|
|
||||||
signature::Keypair,
|
|
||||||
transport::Result as TransportResult,
|
|
||||||
},
|
|
||||||
solana_streamer::{
|
|
||||||
nonblocking::quic::ALPN_TPU_PROTOCOL_ID,
|
|
||||||
tls_certificates::new_self_signed_tls_certificate_chain,
|
|
||||||
},
|
|
||||||
std::{
|
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
|
||||||
sync::{atomic::Ordering, Arc},
|
|
||||||
thread,
|
|
||||||
time::Duration,
|
|
||||||
},
|
|
||||||
thiserror::Error,
|
|
||||||
tokio::{sync::RwLock, time::timeout},
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SkipServerVerification;
|
|
||||||
|
|
||||||
impl SkipServerVerification {
|
|
||||||
pub fn new() -> Arc<Self> {
|
|
||||||
Arc::new(Self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rustls::client::ServerCertVerifier for SkipServerVerification {
|
|
||||||
fn verify_server_cert(
|
|
||||||
&self,
|
|
||||||
_end_entity: &rustls::Certificate,
|
|
||||||
_intermediates: &[rustls::Certificate],
|
|
||||||
_server_name: &rustls::ServerName,
|
|
||||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
|
||||||
_ocsp_response: &[u8],
|
|
||||||
_now: std::time::SystemTime,
|
|
||||||
) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
|
|
||||||
Ok(rustls::client::ServerCertVerified::assertion())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct QuicClientCertificate {
|
|
||||||
pub certificates: Vec<rustls::Certificate>,
|
|
||||||
pub key: rustls::PrivateKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A lazy-initialized Quic Endpoint
|
|
||||||
pub struct QuicLazyInitializedEndpoint {
|
|
||||||
endpoint: RwLock<Option<Arc<Endpoint>>>,
|
|
||||||
client_certificate: Arc<QuicClientCertificate>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum QuicError {
|
|
||||||
#[error(transparent)]
|
|
||||||
WriteError(#[from] WriteError),
|
|
||||||
#[error(transparent)]
|
|
||||||
ConnectionError(#[from] ConnectionError),
|
|
||||||
#[error(transparent)]
|
|
||||||
ConnectError(#[from] ConnectError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<QuicError> for ClientErrorKind {
|
|
||||||
fn from(quic_error: QuicError) -> Self {
|
|
||||||
Self::Custom(format!("{:?}", quic_error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuicLazyInitializedEndpoint {
|
|
||||||
pub fn new(client_certificate: Arc<QuicClientCertificate>) -> Self {
|
|
||||||
Self {
|
|
||||||
endpoint: RwLock::new(None),
|
|
||||||
client_certificate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_endpoint(&self) -> Endpoint {
|
|
||||||
let (_, client_socket) = solana_net_utils::bind_in_range(
|
|
||||||
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
|
||||||
VALIDATOR_PORT_RANGE,
|
|
||||||
)
|
|
||||||
.expect("QuicLazyInitializedEndpoint::create_endpoint bind_in_range");
|
|
||||||
|
|
||||||
let mut crypto = rustls::ClientConfig::builder()
|
|
||||||
.with_safe_defaults()
|
|
||||||
.with_custom_certificate_verifier(SkipServerVerification::new())
|
|
||||||
.with_single_cert(
|
|
||||||
self.client_certificate.certificates.clone(),
|
|
||||||
self.client_certificate.key.clone(),
|
|
||||||
)
|
|
||||||
.expect("Failed to set QUIC client certificates");
|
|
||||||
crypto.enable_early_data = true;
|
|
||||||
crypto.alpn_protocols = vec![ALPN_TPU_PROTOCOL_ID.to_vec()];
|
|
||||||
|
|
||||||
let mut endpoint =
|
|
||||||
QuicNewConnection::create_endpoint(EndpointConfig::default(), client_socket);
|
|
||||||
|
|
||||||
let mut config = ClientConfig::new(Arc::new(crypto));
|
|
||||||
let transport_config = Arc::get_mut(&mut config.transport)
|
|
||||||
.expect("QuicLazyInitializedEndpoint::create_endpoint Arc::get_mut");
|
|
||||||
let timeout = IdleTimeout::from(VarInt::from_u32(QUIC_MAX_TIMEOUT_MS));
|
|
||||||
transport_config.max_idle_timeout(Some(timeout));
|
|
||||||
transport_config.keep_alive_interval(Some(Duration::from_millis(QUIC_KEEP_ALIVE_MS)));
|
|
||||||
|
|
||||||
endpoint.set_default_client_config(config);
|
|
||||||
endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_endpoint(&self) -> Arc<Endpoint> {
|
|
||||||
let lock = self.endpoint.read().await;
|
|
||||||
let endpoint = lock.as_ref();
|
|
||||||
|
|
||||||
match endpoint {
|
|
||||||
Some(endpoint) => endpoint.clone(),
|
|
||||||
None => {
|
|
||||||
drop(lock);
|
|
||||||
let mut lock = self.endpoint.write().await;
|
|
||||||
let endpoint = lock.as_ref();
|
|
||||||
|
|
||||||
match endpoint {
|
|
||||||
Some(endpoint) => endpoint.clone(),
|
|
||||||
None => {
|
|
||||||
let connection = Arc::new(self.create_endpoint());
|
|
||||||
*lock = Some(connection.clone());
|
|
||||||
connection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for QuicLazyInitializedEndpoint {
|
|
||||||
fn default() -> Self {
|
|
||||||
let (certs, priv_key) = new_self_signed_tls_certificate_chain(
|
|
||||||
&Keypair::new(),
|
|
||||||
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
|
||||||
)
|
|
||||||
.expect("Failed to create QUIC client certificate");
|
|
||||||
Self::new(Arc::new(QuicClientCertificate {
|
|
||||||
certificates: certs,
|
|
||||||
key: priv_key,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper over NewConnection with additional capability to create the endpoint as part
|
|
||||||
/// of creating a new connection.
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct QuicNewConnection {
|
|
||||||
endpoint: Arc<Endpoint>,
|
|
||||||
connection: Arc<NewConnection>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuicNewConnection {
|
|
||||||
/// Create a QuicNewConnection given the remote address 'addr'.
|
|
||||||
async fn make_connection(
|
|
||||||
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
|
||||||
addr: SocketAddr,
|
|
||||||
stats: &ClientStats,
|
|
||||||
) -> Result<Self, QuicError> {
|
|
||||||
let mut make_connection_measure = Measure::start("make_connection_measure");
|
|
||||||
let endpoint = endpoint.get_endpoint().await;
|
|
||||||
|
|
||||||
let connecting = endpoint.connect(addr, "connect")?;
|
|
||||||
stats.total_connections.fetch_add(1, Ordering::Relaxed);
|
|
||||||
if let Ok(connecting_result) = timeout(
|
|
||||||
Duration::from_millis(QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS),
|
|
||||||
connecting,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
if connecting_result.is_err() {
|
|
||||||
stats.connection_errors.fetch_add(1, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
make_connection_measure.stop();
|
|
||||||
stats
|
|
||||||
.make_connection_ms
|
|
||||||
.fetch_add(make_connection_measure.as_ms(), Ordering::Relaxed);
|
|
||||||
|
|
||||||
let connection = connecting_result?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
endpoint,
|
|
||||||
connection: Arc::new(connection),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(ConnectionError::TimedOut.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_endpoint(config: EndpointConfig, client_socket: UdpSocket) -> Endpoint {
|
|
||||||
quinn::Endpoint::new(config, None, client_socket)
|
|
||||||
.expect("QuicNewConnection::create_endpoint quinn::Endpoint::new")
|
|
||||||
.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempts to make a faster connection by taking advantage of pre-existing key material.
|
|
||||||
// Only works if connection to this endpoint was previously established.
|
|
||||||
async fn make_connection_0rtt(
|
|
||||||
&mut self,
|
|
||||||
addr: SocketAddr,
|
|
||||||
stats: &ClientStats,
|
|
||||||
) -> Result<Arc<NewConnection>, QuicError> {
|
|
||||||
let connecting = self.endpoint.connect(addr, "connect")?;
|
|
||||||
stats.total_connections.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let connection = match connecting.into_0rtt() {
|
|
||||||
Ok((connection, zero_rtt)) => {
|
|
||||||
if let Ok(zero_rtt) = timeout(
|
|
||||||
Duration::from_millis(QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS),
|
|
||||||
zero_rtt,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
if zero_rtt {
|
|
||||||
stats.zero_rtt_accepts.fetch_add(1, Ordering::Relaxed);
|
|
||||||
} else {
|
|
||||||
stats.zero_rtt_rejects.fetch_add(1, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
connection
|
|
||||||
} else {
|
|
||||||
return Err(ConnectionError::TimedOut.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(connecting) => {
|
|
||||||
stats.connection_errors.fetch_add(1, Ordering::Relaxed);
|
|
||||||
|
|
||||||
if let Ok(connecting_result) = timeout(
|
|
||||||
Duration::from_millis(QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS),
|
|
||||||
connecting,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
connecting_result?
|
|
||||||
} else {
|
|
||||||
return Err(ConnectionError::TimedOut.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.connection = Arc::new(connection);
|
|
||||||
Ok(self.connection.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct QuicClient {
|
|
||||||
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
|
||||||
connection: Arc<Mutex<Option<QuicNewConnection>>>,
|
|
||||||
addr: SocketAddr,
|
|
||||||
stats: Arc<ClientStats>,
|
|
||||||
chunk_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuicClient {
|
|
||||||
pub fn new(
|
|
||||||
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
|
||||||
addr: SocketAddr,
|
|
||||||
chunk_size: usize,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
endpoint,
|
|
||||||
connection: Arc::new(Mutex::new(None)),
|
|
||||||
addr,
|
|
||||||
stats: Arc::new(ClientStats::default()),
|
|
||||||
chunk_size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn _send_buffer_using_conn(
|
|
||||||
data: &[u8],
|
|
||||||
connection: &NewConnection,
|
|
||||||
) -> Result<(), QuicError> {
|
|
||||||
let mut send_stream = connection.connection.open_uni().await?;
|
|
||||||
|
|
||||||
send_stream.write_all(data).await?;
|
|
||||||
send_stream.finish().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempts to send data, connecting/reconnecting as necessary
|
|
||||||
// On success, returns the connection used to successfully send the data
|
|
||||||
async fn _send_buffer(
|
|
||||||
&self,
|
|
||||||
data: &[u8],
|
|
||||||
stats: &ClientStats,
|
|
||||||
connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
) -> Result<Arc<NewConnection>, QuicError> {
|
|
||||||
let mut connection_try_count = 0;
|
|
||||||
let mut last_connection_id = 0;
|
|
||||||
let mut last_error = None;
|
|
||||||
|
|
||||||
while connection_try_count < 2 {
|
|
||||||
let connection = {
|
|
||||||
let mut conn_guard = self.connection.lock().await;
|
|
||||||
|
|
||||||
let maybe_conn = conn_guard.as_mut();
|
|
||||||
match maybe_conn {
|
|
||||||
Some(conn) => {
|
|
||||||
if conn.connection.connection.stable_id() == last_connection_id {
|
|
||||||
// this is the problematic connection we had used before, create a new one
|
|
||||||
let conn = conn.make_connection_0rtt(self.addr, stats).await;
|
|
||||||
match conn {
|
|
||||||
Ok(conn) => {
|
|
||||||
info!(
|
|
||||||
"Made 0rtt connection to {} with id {} try_count {}, last_connection_id: {}, last_error: {:?}",
|
|
||||||
self.addr,
|
|
||||||
conn.connection.stable_id(),
|
|
||||||
connection_try_count,
|
|
||||||
last_connection_id,
|
|
||||||
last_error,
|
|
||||||
);
|
|
||||||
connection_try_count += 1;
|
|
||||||
conn
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
info!(
|
|
||||||
"Cannot make 0rtt connection to {}, error {:}",
|
|
||||||
self.addr, err
|
|
||||||
);
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stats.connection_reuse.fetch_add(1, Ordering::Relaxed);
|
|
||||||
conn.connection.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let conn = QuicNewConnection::make_connection(
|
|
||||||
self.endpoint.clone(),
|
|
||||||
self.addr,
|
|
||||||
stats,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
match conn {
|
|
||||||
Ok(conn) => {
|
|
||||||
*conn_guard = Some(conn.clone());
|
|
||||||
info!(
|
|
||||||
"Made connection to {} id {} try_count {}",
|
|
||||||
self.addr,
|
|
||||||
conn.connection.connection.stable_id(),
|
|
||||||
connection_try_count
|
|
||||||
);
|
|
||||||
connection_try_count += 1;
|
|
||||||
conn.connection.clone()
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
info!("Cannot make connection to {}, error {:}", self.addr, err);
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_stats = connection.connection.stats();
|
|
||||||
|
|
||||||
connection_stats
|
|
||||||
.total_client_stats
|
|
||||||
.congestion_events
|
|
||||||
.update_stat(
|
|
||||||
&self.stats.congestion_events,
|
|
||||||
new_stats.path.congestion_events,
|
|
||||||
);
|
|
||||||
|
|
||||||
connection_stats
|
|
||||||
.total_client_stats
|
|
||||||
.tx_streams_blocked_uni
|
|
||||||
.update_stat(
|
|
||||||
&self.stats.tx_streams_blocked_uni,
|
|
||||||
new_stats.frame_tx.streams_blocked_uni,
|
|
||||||
);
|
|
||||||
|
|
||||||
connection_stats
|
|
||||||
.total_client_stats
|
|
||||||
.tx_data_blocked
|
|
||||||
.update_stat(&self.stats.tx_data_blocked, new_stats.frame_tx.data_blocked);
|
|
||||||
|
|
||||||
connection_stats
|
|
||||||
.total_client_stats
|
|
||||||
.tx_acks
|
|
||||||
.update_stat(&self.stats.tx_acks, new_stats.frame_tx.acks);
|
|
||||||
|
|
||||||
last_connection_id = connection.connection.stable_id();
|
|
||||||
match Self::_send_buffer_using_conn(data, &connection).await {
|
|
||||||
Ok(()) => {
|
|
||||||
return Ok(connection);
|
|
||||||
}
|
|
||||||
Err(err) => match err {
|
|
||||||
QuicError::ConnectionError(_) => {
|
|
||||||
last_error = Some(err);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
info!(
|
|
||||||
"Error sending to {} with id {}, error {:?} thread: {:?}",
|
|
||||||
self.addr,
|
|
||||||
connection.connection.stable_id(),
|
|
||||||
err,
|
|
||||||
thread::current().id(),
|
|
||||||
);
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we come here, that means we have exhausted maximum retries, return the error
|
|
||||||
info!(
|
|
||||||
"Ran into an error sending transactions {:?}, exhausted retries to {}",
|
|
||||||
last_error, self.addr
|
|
||||||
);
|
|
||||||
// If we get here but last_error is None, then we have a logic error
|
|
||||||
// in this function, so panic here with an expect to help debugging
|
|
||||||
Err(last_error.expect("QuicClient::_send_buffer last_error.expect"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_buffer<T>(
|
|
||||||
&self,
|
|
||||||
data: T,
|
|
||||||
stats: &ClientStats,
|
|
||||||
connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
) -> Result<(), ClientErrorKind>
|
|
||||||
where
|
|
||||||
T: AsRef<[u8]>,
|
|
||||||
{
|
|
||||||
self._send_buffer(data.as_ref(), stats, connection_stats)
|
|
||||||
.await
|
|
||||||
.map_err(Into::<ClientErrorKind>::into)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_batch<T>(
|
|
||||||
&self,
|
|
||||||
buffers: &[T],
|
|
||||||
stats: &ClientStats,
|
|
||||||
connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
) -> Result<(), ClientErrorKind>
|
|
||||||
where
|
|
||||||
T: AsRef<[u8]>,
|
|
||||||
{
|
|
||||||
// Start off by "testing" the connection by sending the first transaction
|
|
||||||
// This will also connect to the server if not already connected
|
|
||||||
// and reconnect and retry if the first send attempt failed
|
|
||||||
// (for example due to a timed out connection), returning an error
|
|
||||||
// or the connection that was used to successfully send the transaction.
|
|
||||||
// We will use the returned connection to send the rest of the transactions in the batch
|
|
||||||
// to avoid touching the mutex in self, and not bother reconnecting if we fail along the way
|
|
||||||
// since testing even in the ideal GCE environment has found no cases
|
|
||||||
// where reconnecting and retrying in the middle of a batch send
|
|
||||||
// (i.e. we encounter a connection error in the middle of a batch send, which presumably cannot
|
|
||||||
// be due to a timed out connection) has succeeded
|
|
||||||
if buffers.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let connection = self
|
|
||||||
._send_buffer(buffers[0].as_ref(), stats, connection_stats)
|
|
||||||
.await
|
|
||||||
.map_err(Into::<ClientErrorKind>::into)?;
|
|
||||||
|
|
||||||
// Used to avoid dereferencing the Arc multiple times below
|
|
||||||
// by just getting a reference to the NewConnection once
|
|
||||||
let connection_ref: &NewConnection = &connection;
|
|
||||||
|
|
||||||
let chunks = buffers[1..buffers.len()].iter().chunks(self.chunk_size);
|
|
||||||
|
|
||||||
let futures: Vec<_> = chunks
|
|
||||||
.into_iter()
|
|
||||||
.map(|buffs| {
|
|
||||||
join_all(
|
|
||||||
buffs
|
|
||||||
.into_iter()
|
|
||||||
.map(|buf| Self::_send_buffer_using_conn(buf.as_ref(), connection_ref)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for f in futures {
|
|
||||||
f.await
|
|
||||||
.into_iter()
|
|
||||||
.try_for_each(|res| res)
|
|
||||||
.map_err(Into::<ClientErrorKind>::into)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tpu_addr(&self) -> &SocketAddr {
|
|
||||||
&self.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stats(&self) -> Arc<ClientStats> {
|
|
||||||
self.stats.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct QuicTpuConnection {
|
|
||||||
client: Arc<QuicClient>,
|
|
||||||
connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuicTpuConnection {
|
|
||||||
pub fn base_stats(&self) -> Arc<ClientStats> {
|
|
||||||
self.client.stats()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connection_stats(&self) -> Arc<ConnectionCacheStats> {
|
|
||||||
self.connection_stats.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(
|
|
||||||
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
|
||||||
addr: SocketAddr,
|
|
||||||
connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
) -> Self {
|
|
||||||
let client = Arc::new(QuicClient::new(
|
|
||||||
endpoint,
|
|
||||||
addr,
|
|
||||||
QUIC_MAX_UNSTAKED_CONCURRENT_STREAMS,
|
|
||||||
));
|
|
||||||
Self::new_with_client(client, connection_stats)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_client(
|
|
||||||
client: Arc<QuicClient>,
|
|
||||||
connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
client,
|
|
||||||
connection_stats,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl TpuConnection for QuicTpuConnection {
|
|
||||||
fn tpu_addr(&self) -> &SocketAddr {
|
|
||||||
self.client.tpu_addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
|
||||||
where
|
|
||||||
T: AsRef<[u8]> + Send + Sync,
|
|
||||||
{
|
|
||||||
let stats = ClientStats::default();
|
|
||||||
let len = buffers.len();
|
|
||||||
let res = self
|
|
||||||
.client
|
|
||||||
.send_batch(buffers, &stats, self.connection_stats.clone())
|
|
||||||
.await;
|
|
||||||
self.connection_stats
|
|
||||||
.add_client_stats(&stats, len, res.is_ok());
|
|
||||||
res?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
|
|
||||||
where
|
|
||||||
T: AsRef<[u8]> + Send + Sync,
|
|
||||||
{
|
|
||||||
let stats = Arc::new(ClientStats::default());
|
|
||||||
let send_buffer =
|
|
||||||
self.client
|
|
||||||
.send_buffer(wire_transaction, &stats, self.connection_stats.clone());
|
|
||||||
if let Err(e) = send_buffer.await {
|
|
||||||
warn!(
|
|
||||||
"Failed to send transaction async to {}, error: {:?} ",
|
|
||||||
self.tpu_addr(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
datapoint_warn!("send-wire-async", ("failure", 1, i64),);
|
|
||||||
self.connection_stats.add_client_stats(&stats, 1, false);
|
|
||||||
} else {
|
|
||||||
self.connection_stats.add_client_stats(&stats, 1, true);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
#[cfg(feature = "spinner")]
|
#[cfg(feature = "spinner")]
|
||||||
use {
|
use {
|
||||||
crate::tpu_client::{SEND_TRANSACTION_INTERVAL, TRANSACTION_RESEND_INTERVAL},
|
crate::tpu_client::temporary_pub::{SEND_TRANSACTION_INTERVAL, TRANSACTION_RESEND_INTERVAL},
|
||||||
indicatif::ProgressBar,
|
indicatif::ProgressBar,
|
||||||
solana_rpc_client::spinner,
|
solana_rpc_client::spinner,
|
||||||
solana_rpc_client_api::request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
solana_rpc_client_api::request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
||||||
|
@ -8,9 +8,11 @@ use {
|
||||||
};
|
};
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
connection_cache::ConnectionCache,
|
|
||||||
nonblocking::tpu_connection::TpuConnection,
|
nonblocking::tpu_connection::TpuConnection,
|
||||||
tpu_client::{RecentLeaderSlots, TpuClientConfig, MAX_FANOUT_SLOTS},
|
tpu_client::{RecentLeaderSlots, TpuClientConfig, MAX_FANOUT_SLOTS},
|
||||||
|
tpu_connection_cache::{
|
||||||
|
ConnectionPool, TpuConnectionCache, DEFAULT_TPU_CONNECTION_POOL_SIZE,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
bincode::serialize,
|
bincode::serialize,
|
||||||
futures_util::{future::join_all, stream::StreamExt},
|
futures_util::{future::join_all, stream::StreamExt},
|
||||||
|
@ -46,6 +48,37 @@ use {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub mod temporary_pub {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, TpuSenderError>;
|
||||||
|
|
||||||
|
#[cfg(feature = "spinner")]
|
||||||
|
pub fn set_message_for_confirmed_transactions(
|
||||||
|
progress_bar: &ProgressBar,
|
||||||
|
confirmed_transactions: u32,
|
||||||
|
total_transactions: usize,
|
||||||
|
block_height: Option<u64>,
|
||||||
|
last_valid_block_height: u64,
|
||||||
|
status: &str,
|
||||||
|
) {
|
||||||
|
progress_bar.set_message(format!(
|
||||||
|
"{:>5.1}% | {:<40}{}",
|
||||||
|
confirmed_transactions as f64 * 100. / total_transactions as f64,
|
||||||
|
status,
|
||||||
|
match block_height {
|
||||||
|
Some(block_height) => format!(
|
||||||
|
" [block height {}; re-sign in {} blocks]",
|
||||||
|
block_height,
|
||||||
|
last_valid_block_height.saturating_sub(block_height),
|
||||||
|
),
|
||||||
|
None => String::new(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
use temporary_pub::*;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum TpuSenderError {
|
pub enum TpuSenderError {
|
||||||
#[error("Pubsub error: {0:?}")]
|
#[error("Pubsub error: {0:?}")]
|
||||||
|
@ -61,9 +94,9 @@ pub enum TpuSenderError {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LeaderTpuCacheUpdateInfo {
|
struct LeaderTpuCacheUpdateInfo {
|
||||||
maybe_cluster_nodes: Option<ClientResult<Vec<RpcContactInfo>>>,
|
pub(super) maybe_cluster_nodes: Option<ClientResult<Vec<RpcContactInfo>>>,
|
||||||
maybe_epoch_info: Option<ClientResult<EpochInfo>>,
|
pub(super) maybe_epoch_info: Option<ClientResult<EpochInfo>>,
|
||||||
maybe_slot_leaders: Option<ClientResult<Vec<Pubkey>>>,
|
pub(super) maybe_slot_leaders: Option<ClientResult<Vec<Pubkey>>>,
|
||||||
}
|
}
|
||||||
impl LeaderTpuCacheUpdateInfo {
|
impl LeaderTpuCacheUpdateInfo {
|
||||||
pub fn has_some(&self) -> bool {
|
pub fn has_some(&self) -> bool {
|
||||||
|
@ -210,20 +243,18 @@ impl LeaderTpuCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, TpuSenderError>;
|
|
||||||
|
|
||||||
/// Client which sends transactions directly to the current leader's TPU port over UDP.
|
/// Client which sends transactions directly to the current leader's TPU port over UDP.
|
||||||
/// The client uses RPC to determine the current leader and fetch node contact info
|
/// The client uses RPC to determine the current leader and fetch node contact info
|
||||||
pub struct TpuClient {
|
pub struct TpuClient<P: ConnectionPool> {
|
||||||
fanout_slots: u64,
|
fanout_slots: u64,
|
||||||
leader_tpu_service: LeaderTpuService,
|
leader_tpu_service: LeaderTpuService,
|
||||||
exit: Arc<AtomicBool>,
|
exit: Arc<AtomicBool>,
|
||||||
rpc_client: Arc<RpcClient>,
|
rpc_client: Arc<RpcClient>,
|
||||||
connection_cache: Arc<ConnectionCache>,
|
connection_cache: Arc<TpuConnectionCache<P>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_wire_transaction_to_addr(
|
async fn send_wire_transaction_to_addr<P: ConnectionPool>(
|
||||||
connection_cache: &ConnectionCache,
|
connection_cache: &TpuConnectionCache<P>,
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
wire_transaction: Vec<u8>,
|
wire_transaction: Vec<u8>,
|
||||||
) -> TransportResult<()> {
|
) -> TransportResult<()> {
|
||||||
|
@ -231,8 +262,8 @@ async fn send_wire_transaction_to_addr(
|
||||||
conn.send_wire_transaction(wire_transaction.clone()).await
|
conn.send_wire_transaction(wire_transaction.clone()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_wire_transaction_batch_to_addr(
|
async fn send_wire_transaction_batch_to_addr<P: ConnectionPool>(
|
||||||
connection_cache: &ConnectionCache,
|
connection_cache: &TpuConnectionCache<P>,
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
wire_transactions: &[Vec<u8>],
|
wire_transactions: &[Vec<u8>],
|
||||||
) -> TransportResult<()> {
|
) -> TransportResult<()> {
|
||||||
|
@ -240,7 +271,7 @@ async fn send_wire_transaction_batch_to_addr(
|
||||||
conn.send_wire_transaction_batch(wire_transactions).await
|
conn.send_wire_transaction_batch(wire_transactions).await
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TpuClient {
|
impl<P: ConnectionPool> TpuClient<P> {
|
||||||
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
|
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
|
||||||
/// size
|
/// size
|
||||||
pub async fn send_transaction(&self, transaction: &Transaction) -> bool {
|
pub async fn send_transaction(&self, transaction: &Transaction) -> bool {
|
||||||
|
@ -356,7 +387,8 @@ impl TpuClient {
|
||||||
websocket_url: &str,
|
websocket_url: &str,
|
||||||
config: TpuClientConfig,
|
config: TpuClientConfig,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let connection_cache = Arc::new(ConnectionCache::default());
|
let connection_cache =
|
||||||
|
Arc::new(TpuConnectionCache::new(DEFAULT_TPU_CONNECTION_POOL_SIZE).unwrap()); // TODO: Handle error properly, as the TpuConnectionCache ctor is now fallible.
|
||||||
Self::new_with_connection_cache(rpc_client, websocket_url, config, connection_cache).await
|
Self::new_with_connection_cache(rpc_client, websocket_url, config, connection_cache).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,7 +397,7 @@ impl TpuClient {
|
||||||
rpc_client: Arc<RpcClient>,
|
rpc_client: Arc<RpcClient>,
|
||||||
websocket_url: &str,
|
websocket_url: &str,
|
||||||
config: TpuClientConfig,
|
config: TpuClientConfig,
|
||||||
connection_cache: Arc<ConnectionCache>,
|
connection_cache: Arc<TpuConnectionCache<P>>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let exit = Arc::new(AtomicBool::new(false));
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
let leader_tpu_service =
|
let leader_tpu_service =
|
||||||
|
@ -519,7 +551,8 @@ impl TpuClient {
|
||||||
self.leader_tpu_service.join().await;
|
self.leader_tpu_service.join().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for TpuClient {
|
|
||||||
|
impl<P: ConnectionPool> Drop for TpuClient<P> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.exit.store(true, Ordering::Relaxed);
|
self.exit.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
@ -594,7 +627,7 @@ impl LeaderTpuService {
|
||||||
self.recent_slots.estimated_current_slot()
|
self.recent_slots.estimated_current_slot()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leader_tpu_sockets(&self, fanout_slots: u64) -> Vec<SocketAddr> {
|
pub fn leader_tpu_sockets(&self, fanout_slots: u64) -> Vec<SocketAddr> {
|
||||||
let current_slot = self.recent_slots.estimated_current_slot();
|
let current_slot = self.recent_slots.estimated_current_slot();
|
||||||
self.leader_tpu_cache
|
self.leader_tpu_cache
|
||||||
.read()
|
.read()
|
||||||
|
@ -721,27 +754,3 @@ async fn maybe_fetch_cache_info(
|
||||||
maybe_slot_leaders,
|
maybe_slot_leaders,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "spinner")]
|
|
||||||
fn set_message_for_confirmed_transactions(
|
|
||||||
progress_bar: &ProgressBar,
|
|
||||||
confirmed_transactions: u32,
|
|
||||||
total_transactions: usize,
|
|
||||||
block_height: Option<u64>,
|
|
||||||
last_valid_block_height: u64,
|
|
||||||
status: &str,
|
|
||||||
) {
|
|
||||||
progress_bar.set_message(format!(
|
|
||||||
"{:>5.1}% | {:<40}{}",
|
|
||||||
confirmed_transactions as f64 * 100. / total_transactions as f64,
|
|
||||||
status,
|
|
||||||
match block_height {
|
|
||||||
Some(block_height) => format!(
|
|
||||||
" [block height {}; re-sign in {} blocks]",
|
|
||||||
block_height,
|
|
||||||
last_valid_block_height.saturating_sub(block_height),
|
|
||||||
),
|
|
||||||
None => String::new(),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
//! Trait defining async send functions, to be used for UDP or QUIC sending
|
//! Trait defining async send functions, to be used for UDP or QUIC sending
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::nonblocking::{quic_client::QuicTpuConnection, udp_client::UdpTpuConnection},
|
|
||||||
async_trait::async_trait,
|
async_trait::async_trait,
|
||||||
enum_dispatch::enum_dispatch,
|
|
||||||
solana_sdk::{transaction::VersionedTransaction, transport::Result as TransportResult},
|
solana_sdk::{transaction::VersionedTransaction, transport::Result as TransportResult},
|
||||||
std::net::SocketAddr,
|
std::net::SocketAddr,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Due to the existence of `crate::connection_cache::Connection`, if this is named
|
|
||||||
// `Connection`, enum_dispatch gets confused between the two and throws errors when
|
|
||||||
// trying to convert later.
|
|
||||||
#[enum_dispatch]
|
|
||||||
pub enum NonblockingConnection {
|
|
||||||
QuicTpuConnection,
|
|
||||||
UdpTpuConnection,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[enum_dispatch(NonblockingConnection)]
|
|
||||||
pub trait TpuConnection {
|
pub trait TpuConnection {
|
||||||
fn tpu_addr(&self) -> &SocketAddr;
|
fn tpu_addr(&self) -> &SocketAddr;
|
||||||
|
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
//! Simple UDP client that communicates with the given UDP port with UDP and provides
|
|
||||||
//! an interface for sending transactions
|
|
||||||
|
|
||||||
use {
|
|
||||||
crate::nonblocking::tpu_connection::TpuConnection, async_trait::async_trait,
|
|
||||||
core::iter::repeat, solana_sdk::transport::Result as TransportResult,
|
|
||||||
solana_streamer::nonblocking::sendmmsg::batch_send, std::net::SocketAddr,
|
|
||||||
tokio::net::UdpSocket,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct UdpTpuConnection {
|
|
||||||
socket: UdpSocket,
|
|
||||||
addr: SocketAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UdpTpuConnection {
|
|
||||||
pub fn new_from_addr(socket: std::net::UdpSocket, tpu_addr: SocketAddr) -> Self {
|
|
||||||
socket.set_nonblocking(true).unwrap();
|
|
||||||
let socket = UdpSocket::from_std(socket).unwrap();
|
|
||||||
Self {
|
|
||||||
socket,
|
|
||||||
addr: tpu_addr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl TpuConnection for UdpTpuConnection {
|
|
||||||
fn tpu_addr(&self) -> &SocketAddr {
|
|
||||||
&self.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
|
|
||||||
where
|
|
||||||
T: AsRef<[u8]> + Send + Sync,
|
|
||||||
{
|
|
||||||
self.socket
|
|
||||||
.send_to(wire_transaction.as_ref(), self.addr)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
|
||||||
where
|
|
||||||
T: AsRef<[u8]> + Send + Sync,
|
|
||||||
{
|
|
||||||
let pkts: Vec<_> = buffers.iter().zip(repeat(self.tpu_addr())).collect();
|
|
||||||
batch_send(&self.socket, &pkts).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use {
|
|
||||||
crate::nonblocking::{tpu_connection::TpuConnection, udp_client::UdpTpuConnection},
|
|
||||||
solana_sdk::packet::{Packet, PACKET_DATA_SIZE},
|
|
||||||
solana_streamer::nonblocking::recvmmsg::recv_mmsg,
|
|
||||||
std::net::{IpAddr, Ipv4Addr},
|
|
||||||
tokio::net::UdpSocket,
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn check_send_one(connection: &UdpTpuConnection, reader: &UdpSocket) {
|
|
||||||
let packet = vec![111u8; PACKET_DATA_SIZE];
|
|
||||||
connection.send_wire_transaction(&packet).await.unwrap();
|
|
||||||
let mut packets = vec![Packet::default(); 32];
|
|
||||||
let recv = recv_mmsg(reader, &mut packets[..]).await.unwrap();
|
|
||||||
assert_eq!(1, recv);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_send_batch(connection: &UdpTpuConnection, reader: &UdpSocket) {
|
|
||||||
let packets: Vec<_> = (0..32).map(|_| vec![0u8; PACKET_DATA_SIZE]).collect();
|
|
||||||
connection
|
|
||||||
.send_wire_transaction_batch(&packets)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let mut packets = vec![Packet::default(); 32];
|
|
||||||
let recv = recv_mmsg(reader, &mut packets[..]).await.unwrap();
|
|
||||||
assert_eq!(32, recv);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_send_from_addr() {
|
|
||||||
let addr_str = "0.0.0.0:50100";
|
|
||||||
let addr = addr_str.parse().unwrap();
|
|
||||||
let socket =
|
|
||||||
solana_net_utils::bind_with_any_port(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))).unwrap();
|
|
||||||
let connection = UdpTpuConnection::new_from_addr(socket, addr);
|
|
||||||
let reader = UdpSocket::bind(addr_str).await.expect("bind");
|
|
||||||
check_send_one(&connection, &reader).await;
|
|
||||||
check_send_batch(&connection, &reader).await;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,187 +0,0 @@
|
||||||
//! Simple client that connects to a given UDP port with the QUIC protocol and provides
|
|
||||||
//! an interface for sending transactions which is restricted by the server's flow control.
|
|
||||||
|
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
connection_cache_stats::ConnectionCacheStats,
|
|
||||||
nonblocking::{
|
|
||||||
quic_client::{
|
|
||||||
QuicClient, QuicLazyInitializedEndpoint,
|
|
||||||
QuicTpuConnection as NonblockingQuicTpuConnection,
|
|
||||||
},
|
|
||||||
tpu_connection::TpuConnection as NonblockingTpuConnection,
|
|
||||||
},
|
|
||||||
tpu_connection::{ClientStats, TpuConnection},
|
|
||||||
},
|
|
||||||
lazy_static::lazy_static,
|
|
||||||
log::*,
|
|
||||||
solana_sdk::transport::{Result as TransportResult, TransportError},
|
|
||||||
std::{
|
|
||||||
net::SocketAddr,
|
|
||||||
sync::{atomic::Ordering, Arc, Condvar, Mutex, MutexGuard},
|
|
||||||
time::Duration,
|
|
||||||
},
|
|
||||||
tokio::{runtime::Runtime, time::timeout},
|
|
||||||
};
|
|
||||||
|
|
||||||
const MAX_OUTSTANDING_TASK: u64 = 2000;
|
|
||||||
const SEND_TRANSACTION_TIMEOUT_MS: u64 = 10000;
|
|
||||||
|
|
||||||
/// A semaphore used for limiting the number of asynchronous tasks spawn to the
|
|
||||||
/// runtime. Before spawnning a task, use acquire. After the task is done (be it
|
|
||||||
/// succsess or failure), call release.
|
|
||||||
struct AsyncTaskSemaphore {
|
|
||||||
/// Keep the counter info about the usage
|
|
||||||
counter: Mutex<u64>,
|
|
||||||
/// Conditional variable for signaling when counter is decremented
|
|
||||||
cond_var: Condvar,
|
|
||||||
/// The maximum usage allowed by this semaphore.
|
|
||||||
permits: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncTaskSemaphore {
|
|
||||||
fn new(permits: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
counter: Mutex::new(0),
|
|
||||||
cond_var: Condvar::new(),
|
|
||||||
permits,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// When returned, the lock has been locked and usage count has been
|
|
||||||
/// incremented. When the returned MutexGuard is dropped the lock is dropped
|
|
||||||
/// without decrementing the usage count.
|
|
||||||
fn acquire(&self) -> MutexGuard<u64> {
|
|
||||||
let mut count = self.counter.lock().unwrap();
|
|
||||||
*count += 1;
|
|
||||||
while *count > self.permits {
|
|
||||||
count = self.cond_var.wait(count).unwrap();
|
|
||||||
}
|
|
||||||
count
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Acquire the lock and decrement the usage count
|
|
||||||
fn release(&self) {
|
|
||||||
let mut count = self.counter.lock().unwrap();
|
|
||||||
*count -= 1;
|
|
||||||
self.cond_var.notify_one();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref ASYNC_TASK_SEMAPHORE: AsyncTaskSemaphore =
|
|
||||||
AsyncTaskSemaphore::new(MAX_OUTSTANDING_TASK);
|
|
||||||
static ref RUNTIME: Runtime = tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.thread_name("quic-client")
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct QuicTpuConnection {
|
|
||||||
inner: Arc<NonblockingQuicTpuConnection>,
|
|
||||||
}
|
|
||||||
impl QuicTpuConnection {
|
|
||||||
pub fn new(
|
|
||||||
endpoint: Arc<QuicLazyInitializedEndpoint>,
|
|
||||||
tpu_addr: SocketAddr,
|
|
||||||
connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
) -> Self {
|
|
||||||
let inner = Arc::new(NonblockingQuicTpuConnection::new(
|
|
||||||
endpoint,
|
|
||||||
tpu_addr,
|
|
||||||
connection_stats,
|
|
||||||
));
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_client(
|
|
||||||
client: Arc<QuicClient>,
|
|
||||||
connection_stats: Arc<ConnectionCacheStats>,
|
|
||||||
) -> Self {
|
|
||||||
let inner = Arc::new(NonblockingQuicTpuConnection::new_with_client(
|
|
||||||
client,
|
|
||||||
connection_stats,
|
|
||||||
));
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_wire_transaction_async(
|
|
||||||
connection: Arc<NonblockingQuicTpuConnection>,
|
|
||||||
wire_transaction: Vec<u8>,
|
|
||||||
) -> TransportResult<()> {
|
|
||||||
let result = timeout(
|
|
||||||
Duration::from_millis(SEND_TRANSACTION_TIMEOUT_MS),
|
|
||||||
connection.send_wire_transaction(wire_transaction),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
ASYNC_TASK_SEMAPHORE.release();
|
|
||||||
handle_send_result(result, connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_wire_transaction_batch_async(
|
|
||||||
connection: Arc<NonblockingQuicTpuConnection>,
|
|
||||||
buffers: Vec<Vec<u8>>,
|
|
||||||
) -> TransportResult<()> {
|
|
||||||
let time_out = SEND_TRANSACTION_TIMEOUT_MS * buffers.len() as u64;
|
|
||||||
|
|
||||||
let result = timeout(
|
|
||||||
Duration::from_millis(time_out),
|
|
||||||
connection.send_wire_transaction_batch(&buffers),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
ASYNC_TASK_SEMAPHORE.release();
|
|
||||||
handle_send_result(result, connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check the send result and update stats if timedout. Returns the checked result.
|
|
||||||
fn handle_send_result(
|
|
||||||
result: Result<Result<(), TransportError>, tokio::time::error::Elapsed>,
|
|
||||||
connection: Arc<NonblockingQuicTpuConnection>,
|
|
||||||
) -> Result<(), TransportError> {
|
|
||||||
match result {
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(_err) => {
|
|
||||||
let client_stats = ClientStats::default();
|
|
||||||
client_stats.send_timeout.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let stats = connection.connection_stats();
|
|
||||||
stats.add_client_stats(&client_stats, 0, false);
|
|
||||||
info!("Timedout sending transaction {:?}", connection.tpu_addr());
|
|
||||||
Err(TransportError::Custom(
|
|
||||||
"Timedout sending transaction".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TpuConnection for QuicTpuConnection {
|
|
||||||
fn tpu_addr(&self) -> &SocketAddr {
|
|
||||||
self.inner.tpu_addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
|
||||||
where
|
|
||||||
T: AsRef<[u8]> + Send + Sync,
|
|
||||||
{
|
|
||||||
RUNTIME.block_on(self.inner.send_wire_transaction_batch(buffers))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_wire_transaction_async(&self, wire_transaction: Vec<u8>) -> TransportResult<()> {
|
|
||||||
let _lock = ASYNC_TASK_SEMAPHORE.acquire();
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
|
|
||||||
let _ = RUNTIME
|
|
||||||
.spawn(async move { send_wire_transaction_async(inner, wire_transaction).await });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_wire_transaction_batch_async(&self, buffers: Vec<Vec<u8>>) -> TransportResult<()> {
|
|
||||||
let _lock = ASYNC_TASK_SEMAPHORE.acquire();
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
let _ =
|
|
||||||
RUNTIME.spawn(async move { send_wire_transaction_batch_async(inner, buffers).await });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,8 @@
|
||||||
pub use crate::nonblocking::tpu_client::TpuSenderError;
|
pub use crate::nonblocking::tpu_client::TpuSenderError;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
connection_cache::ConnectionCache,
|
|
||||||
nonblocking::tpu_client::TpuClient as NonblockingTpuClient,
|
nonblocking::tpu_client::TpuClient as NonblockingTpuClient,
|
||||||
|
tpu_connection_cache::{ConnectionPool, TpuConnectionCache},
|
||||||
},
|
},
|
||||||
rayon::iter::{IntoParallelIterator, ParallelIterator},
|
rayon::iter::{IntoParallelIterator, ParallelIterator},
|
||||||
solana_rpc_client::rpc_client::RpcClient,
|
solana_rpc_client::rpc_client::RpcClient,
|
||||||
|
@ -19,7 +19,19 @@ use {
|
||||||
tokio::time::Duration,
|
tokio::time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, TpuSenderError>;
|
pub mod temporary_pub {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, TpuSenderError>;
|
||||||
|
|
||||||
|
/// Send at ~100 TPS
|
||||||
|
#[cfg(feature = "spinner")]
|
||||||
|
pub const SEND_TRANSACTION_INTERVAL: Duration = Duration::from_millis(10);
|
||||||
|
/// Retry batch send after 4 seconds
|
||||||
|
#[cfg(feature = "spinner")]
|
||||||
|
pub const TRANSACTION_RESEND_INTERVAL: Duration = Duration::from_secs(4);
|
||||||
|
}
|
||||||
|
use temporary_pub::*;
|
||||||
|
|
||||||
/// Default number of slots used to build TPU socket fanout set
|
/// Default number of slots used to build TPU socket fanout set
|
||||||
pub const DEFAULT_FANOUT_SLOTS: u64 = 12;
|
pub const DEFAULT_FANOUT_SLOTS: u64 = 12;
|
||||||
|
@ -27,13 +39,6 @@ pub const DEFAULT_FANOUT_SLOTS: u64 = 12;
|
||||||
/// Maximum number of slots used to build TPU socket fanout set
|
/// Maximum number of slots used to build TPU socket fanout set
|
||||||
pub const MAX_FANOUT_SLOTS: u64 = 100;
|
pub const MAX_FANOUT_SLOTS: u64 = 100;
|
||||||
|
|
||||||
/// Send at ~100 TPS
|
|
||||||
#[cfg(feature = "spinner")]
|
|
||||||
pub(crate) const SEND_TRANSACTION_INTERVAL: Duration = Duration::from_millis(10);
|
|
||||||
/// Retry batch send after 4 seconds
|
|
||||||
#[cfg(feature = "spinner")]
|
|
||||||
pub(crate) const TRANSACTION_RESEND_INTERVAL: Duration = Duration::from_secs(4);
|
|
||||||
|
|
||||||
/// Config params for `TpuClient`
|
/// Config params for `TpuClient`
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TpuClientConfig {
|
pub struct TpuClientConfig {
|
||||||
|
@ -52,14 +57,14 @@ impl Default for TpuClientConfig {
|
||||||
|
|
||||||
/// Client which sends transactions directly to the current leader's TPU port over UDP.
|
/// Client which sends transactions directly to the current leader's TPU port over UDP.
|
||||||
/// The client uses RPC to determine the current leader and fetch node contact info
|
/// The client uses RPC to determine the current leader and fetch node contact info
|
||||||
pub struct TpuClient {
|
pub struct TpuClient<P: ConnectionPool> {
|
||||||
_deprecated: UdpSocket, // TpuClient now uses the connection_cache to choose a send_socket
|
_deprecated: UdpSocket, // TpuClient now uses the connection_cache to choose a send_socket
|
||||||
//todo: get rid of this field
|
//todo: get rid of this field
|
||||||
rpc_client: Arc<RpcClient>,
|
rpc_client: Arc<RpcClient>,
|
||||||
tpu_client: Arc<NonblockingTpuClient>,
|
tpu_client: Arc<NonblockingTpuClient<P>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TpuClient {
|
impl<P: ConnectionPool> TpuClient<P> {
|
||||||
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
|
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
|
||||||
/// size
|
/// size
|
||||||
pub fn send_transaction(&self, transaction: &Transaction) -> bool {
|
pub fn send_transaction(&self, transaction: &Transaction) -> bool {
|
||||||
|
@ -121,7 +126,7 @@ impl TpuClient {
|
||||||
rpc_client: Arc<RpcClient>,
|
rpc_client: Arc<RpcClient>,
|
||||||
websocket_url: &str,
|
websocket_url: &str,
|
||||||
config: TpuClientConfig,
|
config: TpuClientConfig,
|
||||||
connection_cache: Arc<ConnectionCache>,
|
connection_cache: Arc<TpuConnectionCache<P>>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let create_tpu_client = NonblockingTpuClient::new_with_connection_cache(
|
let create_tpu_client = NonblockingTpuClient::new_with_connection_cache(
|
||||||
rpc_client.get_inner_client().clone(),
|
rpc_client.get_inner_client().clone(),
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use {
|
use {
|
||||||
crate::{quic_client::QuicTpuConnection, udp_client::UdpTpuConnection},
|
|
||||||
enum_dispatch::enum_dispatch,
|
|
||||||
rayon::iter::{IntoParallelIterator, ParallelIterator},
|
rayon::iter::{IntoParallelIterator, ParallelIterator},
|
||||||
solana_metrics::MovingStat,
|
solana_metrics::MovingStat,
|
||||||
solana_sdk::{transaction::VersionedTransaction, transport::Result as TransportResult},
|
solana_sdk::{transaction::VersionedTransaction, transport::Result as TransportResult},
|
||||||
|
@ -24,13 +22,6 @@ pub struct ClientStats {
|
||||||
pub send_timeout: AtomicU64,
|
pub send_timeout: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
|
||||||
pub enum BlockingConnection {
|
|
||||||
UdpTpuConnection,
|
|
||||||
QuicTpuConnection,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[enum_dispatch(BlockingConnection)]
|
|
||||||
pub trait TpuConnection {
|
pub trait TpuConnection {
|
||||||
fn tpu_addr(&self) -> &SocketAddr;
|
fn tpu_addr(&self) -> &SocketAddr;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Should be non-zero
|
// Should be non-zero
|
||||||
pub(crate) static MAX_CONNECTIONS: usize = 1024;
|
pub static MAX_CONNECTIONS: usize = 1024;
|
||||||
|
|
||||||
/// Used to decide whether the TPU and underlying connection cache should use
|
/// Used to decide whether the TPU and underlying connection cache should use
|
||||||
/// QUIC connections.
|
/// QUIC connections.
|
||||||
|
@ -262,7 +262,7 @@ pub enum ConnectionPoolError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NewTpuConfig {
|
pub trait NewTpuConfig {
|
||||||
type ClientError;
|
type ClientError: std::fmt::Debug;
|
||||||
fn new() -> Result<Self, Self::ClientError>
|
fn new() -> Result<Self, Self::ClientError>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
|
@ -10,6 +10,10 @@ documentation = "https://docs.rs/solana-udp-client"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1.57"
|
||||||
solana-net-utils = { path = "../net-utils", version = "=1.15.0" }
|
solana-net-utils = { path = "../net-utils", version = "=1.15.0" }
|
||||||
|
solana-sdk = { path = "../sdk", version = "=1.15.0" }
|
||||||
|
solana-streamer = { path = "../streamer", version = "=1.15.0" }
|
||||||
solana-tpu-client = { path = "../tpu-client", version = "=1.15.0" }
|
solana-tpu-client = { path = "../tpu-client", version = "=1.15.0" }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
|
@ -1,4 +1,93 @@
|
||||||
//! Simple UDP client that communicates with the given UDP port with UDP and provides
|
//! Simple UDP client that communicates with the given UDP port with UDP and provides
|
||||||
//! an interface for sending transactions
|
//! an interface for sending transactions
|
||||||
|
|
||||||
pub use solana_tpu_client::nonblocking::udp_client::*;
|
use {
|
||||||
|
async_trait::async_trait, core::iter::repeat, solana_sdk::transport::Result as TransportResult,
|
||||||
|
solana_streamer::nonblocking::sendmmsg::batch_send,
|
||||||
|
solana_tpu_client::nonblocking::tpu_connection::TpuConnection, std::net::SocketAddr,
|
||||||
|
tokio::net::UdpSocket,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UdpTpuConnection {
|
||||||
|
pub socket: UdpSocket,
|
||||||
|
pub addr: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UdpTpuConnection {
|
||||||
|
pub fn new_from_addr(socket: std::net::UdpSocket, tpu_addr: SocketAddr) -> Self {
|
||||||
|
socket.set_nonblocking(true).unwrap();
|
||||||
|
let socket = UdpSocket::from_std(socket).unwrap();
|
||||||
|
Self {
|
||||||
|
socket,
|
||||||
|
addr: tpu_addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl TpuConnection for UdpTpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
&self.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
self.socket
|
||||||
|
.send_to(wire_transaction.as_ref(), self.addr)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
let pkts: Vec<_> = buffers.iter().zip(repeat(self.tpu_addr())).collect();
|
||||||
|
batch_send(&self.socket, &pkts).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use {
|
||||||
|
super::*,
|
||||||
|
solana_sdk::packet::{Packet, PACKET_DATA_SIZE},
|
||||||
|
solana_streamer::nonblocking::recvmmsg::recv_mmsg,
|
||||||
|
std::net::{IpAddr, Ipv4Addr},
|
||||||
|
tokio::net::UdpSocket,
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn check_send_one(connection: &UdpTpuConnection, reader: &UdpSocket) {
|
||||||
|
let packet = vec![111u8; PACKET_DATA_SIZE];
|
||||||
|
connection.send_wire_transaction(&packet).await.unwrap();
|
||||||
|
let mut packets = vec![Packet::default(); 32];
|
||||||
|
let recv = recv_mmsg(reader, &mut packets[..]).await.unwrap();
|
||||||
|
assert_eq!(1, recv);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_send_batch(connection: &UdpTpuConnection, reader: &UdpSocket) {
|
||||||
|
let packets: Vec<_> = (0..32).map(|_| vec![0u8; PACKET_DATA_SIZE]).collect();
|
||||||
|
connection
|
||||||
|
.send_wire_transaction_batch(&packets)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut packets = vec![Packet::default(); 32];
|
||||||
|
let recv = recv_mmsg(reader, &mut packets[..]).await.unwrap();
|
||||||
|
assert_eq!(32, recv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_send_from_addr() {
|
||||||
|
let addr_str = "0.0.0.0:50100";
|
||||||
|
let addr = addr_str.parse().unwrap();
|
||||||
|
let socket =
|
||||||
|
solana_net_utils::bind_with_any_port(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))).unwrap();
|
||||||
|
let connection = UdpTpuConnection::new_from_addr(socket, addr);
|
||||||
|
let reader = UdpSocket::bind(addr_str).await.expect("bind");
|
||||||
|
check_send_one(&connection, &reader).await;
|
||||||
|
check_send_batch(&connection, &reader).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,63 @@
|
||||||
//! Simple TPU client that communicates with the given UDP port with UDP and provides
|
//! Simple TPU client that communicates with the given UDP port with UDP and provides
|
||||||
//! an interface for sending transactions
|
//! an interface for sending transactions
|
||||||
|
|
||||||
pub use solana_tpu_client::udp_client::*;
|
use {
|
||||||
|
core::iter::repeat,
|
||||||
|
solana_sdk::transport::Result as TransportResult,
|
||||||
|
solana_streamer::sendmmsg::batch_send,
|
||||||
|
solana_tpu_client::{
|
||||||
|
connection_cache_stats::ConnectionCacheStats, tpu_connection::TpuConnection,
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
net::{SocketAddr, UdpSocket},
|
||||||
|
sync::Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UdpTpuConnection {
|
||||||
|
pub socket: Arc<UdpSocket>,
|
||||||
|
pub addr: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UdpTpuConnection {
|
||||||
|
pub fn new_from_addr(local_socket: Arc<UdpSocket>, tpu_addr: SocketAddr) -> Self {
|
||||||
|
Self {
|
||||||
|
socket: local_socket,
|
||||||
|
addr: tpu_addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
local_socket: Arc<UdpSocket>,
|
||||||
|
tpu_addr: SocketAddr,
|
||||||
|
_connection_stats: Arc<ConnectionCacheStats>,
|
||||||
|
) -> Self {
|
||||||
|
Self::new_from_addr(local_socket, tpu_addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TpuConnection for UdpTpuConnection {
|
||||||
|
fn tpu_addr(&self) -> &SocketAddr {
|
||||||
|
&self.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_async(&self, wire_transaction: Vec<u8>) -> TransportResult<()> {
|
||||||
|
self.socket.send_to(wire_transaction.as_ref(), self.addr)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + Send + Sync,
|
||||||
|
{
|
||||||
|
let pkts: Vec<_> = buffers.iter().zip(repeat(self.tpu_addr())).collect();
|
||||||
|
batch_send(&self.socket, &pkts)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_wire_transaction_batch_async(&self, buffers: Vec<Vec<u8>>) -> TransportResult<()> {
|
||||||
|
let pkts: Vec<_> = buffers.into_iter().zip(repeat(self.tpu_addr())).collect();
|
||||||
|
batch_send(&self.socket, &pkts)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ use {
|
||||||
self, MAX_BATCH_SEND_RATE_MS, MAX_TRANSACTION_BATCH_SIZE,
|
self, MAX_BATCH_SEND_RATE_MS, MAX_TRANSACTION_BATCH_SIZE,
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
solana_tpu_client::connection_cache::{
|
solana_tpu_client::tpu_connection_cache::{
|
||||||
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP,
|
DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP,
|
||||||
},
|
},
|
||||||
solana_validator::{
|
solana_validator::{
|
||||||
|
|
Loading…
Reference in New Issue