add MAX_RECENT_BLOCKHASSHES and some logs (#49)
adding identity to the tpu client, connection cache some refactoring in block_subscription block pooling refactoring and rework on the test scripts more refactoring after rebase some more refactoring of block listner and tpu manager limiting maximum number of parallel sents to 5 minor changes, cargo fmt, changing default value waiting sometime before adding back errored block
This commit is contained in:
parent
c2bcf6d126
commit
78cbafe0c2
|
@ -23,7 +23,12 @@ function delay(ms: number) {
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
|
|
||||||
const connection = new Connection(url, 'confirmed');
|
const connection = new Connection(url, 'finalized');
|
||||||
|
console.log('get latest blockhash')
|
||||||
|
const blockhash = await connection.getLatestBlockhash({
|
||||||
|
commitment: 'finalized'
|
||||||
|
});
|
||||||
|
console.log('blockhash : ' + blockhash.blockhash);
|
||||||
const authority = Keypair.fromSecretKey(
|
const authority = Keypair.fromSecretKey(
|
||||||
Uint8Array.from(
|
Uint8Array.from(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
|
@ -35,14 +40,18 @@ export async function main() {
|
||||||
|
|
||||||
const users = InFile.users.map(x => Keypair.fromSecretKey(Uint8Array.from(x.secretKey)));
|
const users = InFile.users.map(x => Keypair.fromSecretKey(Uint8Array.from(x.secretKey)));
|
||||||
const userAccounts = InFile.tokenAccounts.map(x => new PublicKey(x));
|
const userAccounts = InFile.tokenAccounts.map(x => new PublicKey(x));
|
||||||
let signatures_to_unpack: TransactionSignature[][] = [];
|
let signatures_to_unpack: TransactionSignature[][] = new Array<TransactionSignature[]>(forSeconds);
|
||||||
let time_taken_to_send = [];
|
let time_taken_to_send = [];
|
||||||
|
|
||||||
for (let i = 0; i < forSeconds; ++i) {
|
for (let i = 0; i < forSeconds; ++i) {
|
||||||
|
console.log('Sending transaction ' + i);
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
let signatures: TransactionSignature[] = [];
|
signatures_to_unpack[i] = new Array<TransactionSignature>(tps);
|
||||||
const blockhash = (await connection.getLatestBlockhash()).blockhash;
|
let blockhash = (await connection.getLatestBlockhash()).blockhash;
|
||||||
for (let j = 0; j < tps; ++j) {
|
for (let j = 0; j < tps; ++j) {
|
||||||
|
if (j%100 == 0) {
|
||||||
|
blockhash = (await connection.getLatestBlockhash()).blockhash;
|
||||||
|
}
|
||||||
const toIndex = Math.floor(Math.random() * users.length);
|
const toIndex = Math.floor(Math.random() * users.length);
|
||||||
let fromIndex = toIndex;
|
let fromIndex = toIndex;
|
||||||
while (fromIndex === toIndex) {
|
while (fromIndex === toIndex) {
|
||||||
|
@ -50,18 +59,14 @@ export async function main() {
|
||||||
}
|
}
|
||||||
const userFrom = userAccounts[fromIndex];
|
const userFrom = userAccounts[fromIndex];
|
||||||
const userTo = userAccounts[toIndex];
|
const userTo = userAccounts[toIndex];
|
||||||
if (skip_confirmations === false) {
|
|
||||||
const transaction = new Transaction().add(
|
const transaction = new Transaction().add(
|
||||||
splToken.createTransferInstruction(userFrom, userTo, users[fromIndex].publicKey, Math.ceil(Math.random() * 100))
|
splToken.createTransferInstruction(userFrom, userTo, users[fromIndex].publicKey, Math.ceil((Math.random()+1) * 100))
|
||||||
);
|
);
|
||||||
transaction.recentBlockhash = blockhash;
|
transaction.recentBlockhash = blockhash;
|
||||||
transaction.feePayer = authority.publicKey;
|
transaction.feePayer = authority.publicKey;
|
||||||
const p = connection.sendTransaction(transaction, [authority, users[fromIndex]], { skipPreflight: true });
|
|
||||||
signatures.push(await p)
|
connection.sendTransaction(transaction, [authority, users[fromIndex]], { skipPreflight: true }).then(p => {signatures_to_unpack[i][j] = p});
|
||||||
}
|
|
||||||
}
|
|
||||||
if (skip_confirmations === false) {
|
|
||||||
signatures_to_unpack.push(signatures)
|
|
||||||
}
|
}
|
||||||
const end = performance.now();
|
const end = performance.now();
|
||||||
const diff = (end - start);
|
const diff = (end - start);
|
||||||
|
@ -72,7 +77,7 @@ export async function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('finish sending transactions');
|
console.log('finish sending transactions');
|
||||||
await delay(5000)
|
await delay(10000)
|
||||||
console.log('checking for confirmations');
|
console.log('checking for confirmations');
|
||||||
if (skip_confirmations === false) {
|
if (skip_confirmations === false) {
|
||||||
const size = signatures_to_unpack.length
|
const size = signatures_to_unpack.length
|
||||||
|
|
|
@ -1,79 +1,85 @@
|
||||||
import { Connection, Keypair } from '@solana/web3.js';
|
import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as splToken from "@solana/spl-token";
|
import * as splToken from "@solana/spl-token";
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
|
|
||||||
// number of users
|
// number of users
|
||||||
const nbUsers = process.argv[2];
|
const nbUsers = +process.argv[2];
|
||||||
// url
|
// url
|
||||||
const url = process.argv.length > 3 ? process.argv[3] : "http://0.0.0.0:8899";
|
const url = process.argv.length > 3 ? process.argv[3] : "http://0.0.0.0:8899";
|
||||||
// outfile
|
// outfile
|
||||||
const outFile = process.argv.length > 4 ? process.argv[4] : "out.json";
|
const outFile = process.argv.length > 4 ? process.argv[4] : "out.json";
|
||||||
|
|
||||||
console.log("creating " + nbUsers + " Users on " + url + " out file " + outFile);
|
console.log("creating " + nbUsers + " Users on " + url + " out file " + outFile);
|
||||||
|
function delay(ms: number) {
|
||||||
(async () => {
|
return new Promise( resolve => setTimeout(resolve, ms) );
|
||||||
|
}
|
||||||
|
export async function main() {
|
||||||
const connection = new Connection(url, 'confirmed');
|
const connection = new Connection(url, 'confirmed');
|
||||||
|
let authority = Keypair.fromSecretKey(
|
||||||
const authority = Keypair.fromSecretKey(
|
|
||||||
Uint8Array.from(
|
Uint8Array.from(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
process.env.KEYPAIR ||
|
process.env.KEYPAIR ||
|
||||||
fs.readFileSync(os.homedir() + '/.config/solana/id.json', 'utf-8'),
|
fs.readFileSync(os.homedir() + '/.config/solana/id.json', 'utf-8'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// create n key pairs
|
let userKps = [...Array(nbUsers)].map(_x => Keypair.generate())
|
||||||
const userKps = [...Array(nbUsers)].map(_x => Keypair.generate())
|
let mint = await splToken.createMint(
|
||||||
|
|
||||||
// create and initialize new mint
|
|
||||||
const mint = await splToken.createMint(
|
|
||||||
connection,
|
connection,
|
||||||
authority,
|
authority,
|
||||||
authority.publicKey,
|
authority.publicKey,
|
||||||
null,
|
null,
|
||||||
6,
|
6,
|
||||||
);
|
);
|
||||||
|
let accounts : PublicKey[] = [];
|
||||||
// create accounts for each key pair created earlier
|
for (const user of userKps) {
|
||||||
const accounts = await Promise.all(userKps.map(x => {
|
console.log("account created");
|
||||||
return splToken.createAccount(
|
let account = await splToken.createAccount(
|
||||||
connection,
|
connection,
|
||||||
authority,
|
authority,
|
||||||
mint,
|
mint,
|
||||||
x.publicKey,
|
user.publicKey,
|
||||||
)
|
)
|
||||||
}));
|
accounts.push(account)
|
||||||
|
await delay(100)
|
||||||
|
};
|
||||||
|
|
||||||
// mint to accounts
|
for (const account of accounts) {
|
||||||
await Promise.all(accounts.map(to => {
|
console.log("account minted");
|
||||||
return splToken.mintTo(
|
await splToken.mintTo(
|
||||||
connection,
|
connection,
|
||||||
authority,
|
authority,
|
||||||
mint,
|
mint,
|
||||||
to,
|
account,
|
||||||
authority,
|
authority,
|
||||||
1_000_000_000_000,
|
1_000_000_000_000,
|
||||||
)
|
)
|
||||||
}));
|
await delay(100)
|
||||||
|
};
|
||||||
|
|
||||||
const users = userKps.map(user => {
|
const users = userKps.map(x => {
|
||||||
return {
|
const info = {
|
||||||
publicKey: user.publicKey.toBase58(),
|
'publicKey' : x.publicKey.toBase58(),
|
||||||
secretKey: Array.from(user.secretKey)
|
'secretKey' : Array.from(x.secretKey)
|
||||||
}
|
};
|
||||||
|
return info;
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
'users': users,
|
'users' : users,
|
||||||
'tokenAccounts': accounts,
|
'tokenAccounts' : accounts,
|
||||||
'mint': mint,
|
'mint' : mint,
|
||||||
'minted_amount': 1_000_000_000_000
|
'minted_amount' : 1_000_000_000_000
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('created ' + nbUsers + ' Users and minted 10^12 tokens for mint ' + mint);
|
console.log('created ' + nbUsers + ' Users and minted 10^12 tokens for mint ' + mint);
|
||||||
|
|
||||||
fs.writeFileSync(outFile, JSON.stringify(data));
|
fs.writeFileSync(outFile, JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
})()
|
main().then(x => {
|
||||||
|
console.log('finished sucessfully')
|
||||||
|
}).catch(e => {
|
||||||
|
console.log('caught an error : ' + e)
|
||||||
|
})
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,6 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
|
||||||
use log::info;
|
|
||||||
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
|
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
|
||||||
use solana_sdk::commitment_config::CommitmentConfig;
|
use solana_sdk::commitment_config::CommitmentConfig;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
@ -12,8 +11,8 @@ use crate::workers::BlockInformation;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BlockStore {
|
pub struct BlockStore {
|
||||||
blocks: Arc<DashMap<String, BlockInformation>>,
|
blocks: Arc<DashMap<String, BlockInformation>>,
|
||||||
latest_confirmed_blockhash: Arc<RwLock<String>>,
|
latest_confirmed_blockinfo: Arc<RwLock<BlockInformation>>,
|
||||||
latest_finalized_blockhash: Arc<RwLock<String>>,
|
latest_finalized_blockinfo: Arc<RwLock<BlockInformation>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockStore {
|
impl BlockStore {
|
||||||
|
@ -24,8 +23,8 @@ impl BlockStore {
|
||||||
Self::fetch_latest(rpc_client, CommitmentConfig::finalized()).await?;
|
Self::fetch_latest(rpc_client, CommitmentConfig::finalized()).await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
latest_confirmed_blockhash: Arc::new(RwLock::new(confirmed_blockhash.clone())),
|
latest_confirmed_blockinfo: Arc::new(RwLock::new(confirmed_block.clone())),
|
||||||
latest_finalized_blockhash: Arc::new(RwLock::new(finalized_blockhash.clone())),
|
latest_finalized_blockinfo: Arc::new(RwLock::new(finalized_block.clone())),
|
||||||
blocks: Arc::new({
|
blocks: Arc::new({
|
||||||
let map = DashMap::new();
|
let map = DashMap::new();
|
||||||
map.insert(confirmed_blockhash, confirmed_block);
|
map.insert(confirmed_blockhash, confirmed_block);
|
||||||
|
@ -48,7 +47,14 @@ impl BlockStore {
|
||||||
.get_slot_with_commitment(commitment_config)
|
.get_slot_with_commitment(commitment_config)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((latest_block_hash, BlockInformation { slot, block_height }))
|
Ok((
|
||||||
|
latest_block_hash.clone(),
|
||||||
|
BlockInformation {
|
||||||
|
slot,
|
||||||
|
block_height,
|
||||||
|
blockhash: latest_block_hash,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_block_info(&self, blockhash: &str) -> Option<BlockInformation> {
|
pub async fn get_block_info(&self, blockhash: &str) -> Option<BlockInformation> {
|
||||||
|
@ -59,48 +65,48 @@ impl BlockStore {
|
||||||
Some(info.value().to_owned())
|
Some(info.value().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_latest_blockhash(&self, commitment_config: CommitmentConfig) -> Arc<RwLock<String>> {
|
// private
|
||||||
|
fn get_latest_blockinfo_lock(
|
||||||
|
&self,
|
||||||
|
commitment_config: CommitmentConfig,
|
||||||
|
) -> Arc<RwLock<BlockInformation>> {
|
||||||
if commitment_config.is_finalized() {
|
if commitment_config.is_finalized() {
|
||||||
self.latest_finalized_blockhash.clone()
|
self.latest_finalized_blockinfo.clone()
|
||||||
} else {
|
} else {
|
||||||
self.latest_confirmed_blockhash.clone()
|
self.latest_confirmed_blockinfo.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_latest_block_info(
|
pub async fn get_latest_block_info(
|
||||||
&self,
|
&self,
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
) -> (String, BlockInformation) {
|
) -> BlockInformation {
|
||||||
let blockhash = self
|
let block_info = self
|
||||||
.get_latest_blockhash(commitment_config)
|
.get_latest_blockinfo_lock(commitment_config)
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.to_owned();
|
.clone();
|
||||||
|
|
||||||
let block_info = self
|
block_info
|
||||||
.blocks
|
|
||||||
.get(&blockhash)
|
|
||||||
.expect("Race Condition: Latest block not in block store")
|
|
||||||
.value()
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
(blockhash, block_info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_block(
|
pub async fn add_block(
|
||||||
&self,
|
&self,
|
||||||
blockhash: String,
|
|
||||||
block_info: BlockInformation,
|
block_info: BlockInformation,
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
) {
|
) {
|
||||||
info!("ab {blockhash} {block_info:?}");
|
let blockhash = block_info.blockhash.clone();
|
||||||
// Write to block store first in order to prevent
|
// Write to block store first in order to prevent
|
||||||
// any race condition i.e prevent some one to
|
// any race condition i.e prevent some one to
|
||||||
// ask the map what it doesn't have rn
|
// ask the map what it doesn't have rn
|
||||||
let slot = block_info.slot;
|
self.blocks.insert(blockhash, block_info.clone());
|
||||||
self.blocks.insert(blockhash.clone(), block_info);
|
let last_recent_block = self.get_latest_block_info(commitment_config).await;
|
||||||
if slot > self.get_latest_block_info(commitment_config).await.1.slot {
|
|
||||||
*self.get_latest_blockhash(commitment_config).write().await = blockhash;
|
if last_recent_block.slot < block_info.slot {
|
||||||
|
*self
|
||||||
|
.get_latest_blockinfo_lock(commitment_config)
|
||||||
|
.write()
|
||||||
|
.await = block_info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ use solana_rpc_client_api::{
|
||||||
config::{RpcContextConfig, RpcRequestAirdropConfig, RpcSignatureStatusConfig},
|
config::{RpcContextConfig, RpcRequestAirdropConfig, RpcSignatureStatusConfig},
|
||||||
response::{Response as RpcResponse, RpcBlockhash, RpcResponseContext, RpcVersionInfo},
|
response::{Response as RpcResponse, RpcBlockhash, RpcResponseContext, RpcVersionInfo},
|
||||||
};
|
};
|
||||||
use solana_sdk::clock::MAX_RECENT_BLOCKHASHES;
|
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
commitment_config::CommitmentConfig, hash::Hash, pubkey::Pubkey, signature::Keypair,
|
commitment_config::CommitmentConfig, hash::Hash, pubkey::Pubkey, signature::Keypair,
|
||||||
transaction::VersionedTransaction,
|
transaction::VersionedTransaction,
|
||||||
|
@ -249,7 +248,11 @@ impl LiteRpcServer for LiteBridge {
|
||||||
.map(|config| config.commitment.unwrap_or_default())
|
.map(|config| config.commitment.unwrap_or_default())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let (blockhash, BlockInformation { slot, block_height }) = self
|
let BlockInformation {
|
||||||
|
slot,
|
||||||
|
block_height,
|
||||||
|
blockhash,
|
||||||
|
} = self
|
||||||
.block_store
|
.block_store
|
||||||
.get_latest_block_info(commitment_config)
|
.get_latest_block_info(commitment_config)
|
||||||
.await;
|
.await;
|
||||||
|
@ -263,7 +266,7 @@ impl LiteRpcServer for LiteBridge {
|
||||||
},
|
},
|
||||||
value: RpcBlockhash {
|
value: RpcBlockhash {
|
||||||
blockhash,
|
blockhash,
|
||||||
last_valid_block_height: block_height + (MAX_RECENT_BLOCKHASHES as u64),
|
last_valid_block_height: block_height + 150,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -300,7 +303,6 @@ impl LiteRpcServer for LiteBridge {
|
||||||
.block_store
|
.block_store
|
||||||
.get_latest_block_info(commitment)
|
.get_latest_block_info(commitment)
|
||||||
.await
|
.await
|
||||||
.1
|
|
||||||
.slot;
|
.slot;
|
||||||
|
|
||||||
Ok(RpcResponse {
|
Ok(RpcResponse {
|
||||||
|
@ -335,7 +337,6 @@ impl LiteRpcServer for LiteBridge {
|
||||||
.block_store
|
.block_store
|
||||||
.get_latest_block_info(CommitmentConfig::finalized())
|
.get_latest_block_info(CommitmentConfig::finalized())
|
||||||
.await
|
.await
|
||||||
.1
|
|
||||||
.slot,
|
.slot,
|
||||||
api_version: None,
|
api_version: None,
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub const DEFAULT_WS_ADDR: &str = "ws://0.0.0.0:8900";
|
||||||
#[from_env]
|
#[from_env]
|
||||||
pub const DEFAULT_TX_MAX_RETRIES: u16 = 1;
|
pub const DEFAULT_TX_MAX_RETRIES: u16 = 1;
|
||||||
#[from_env]
|
#[from_env]
|
||||||
pub const DEFAULT_TX_BATCH_SIZE: usize = 1 << 7;
|
pub const DEFAULT_TX_BATCH_SIZE: usize = 128;
|
||||||
#[from_env]
|
#[from_env]
|
||||||
pub const DEFAULT_FANOUT_SIZE: u64 = 32;
|
pub const DEFAULT_FANOUT_SIZE: u64 = 32;
|
||||||
#[from_env]
|
#[from_env]
|
||||||
|
|
|
@ -26,7 +26,8 @@ const TPU_CONNECTION_CACHE_SIZE: usize = 8;
|
||||||
pub struct TpuManager {
|
pub struct TpuManager {
|
||||||
error_count: Arc<AtomicU32>,
|
error_count: Arc<AtomicU32>,
|
||||||
rpc_client: Arc<RpcClient>,
|
rpc_client: Arc<RpcClient>,
|
||||||
tpu_client: Arc<RwLock<QuicTpuClient>>,
|
// why arc twice / one is so that we clone rwlock and other so that we can clone tpu client
|
||||||
|
tpu_client: Arc<RwLock<Arc<QuicTpuClient>>>,
|
||||||
pub ws_addr: String,
|
pub ws_addr: String,
|
||||||
fanout_slots: u64,
|
fanout_slots: u64,
|
||||||
connection_cache: Arc<QuicConnectionCache>,
|
connection_cache: Arc<QuicConnectionCache>,
|
||||||
|
@ -54,7 +55,7 @@ impl TpuManager {
|
||||||
connection_cache.clone(),
|
connection_cache.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let tpu_client = Arc::new(RwLock::new(tpu_client));
|
let tpu_client = Arc::new(RwLock::new(Arc::new(tpu_client)));
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
rpc_client,
|
rpc_client,
|
||||||
|
@ -93,21 +94,23 @@ impl TpuManager {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.error_count.store(0, Ordering::Relaxed);
|
self.error_count.store(0, Ordering::Relaxed);
|
||||||
*self.tpu_client.write().await = tpu_client;
|
*self.tpu_client.write().await = Arc::new(tpu_client);
|
||||||
info!("TPU Reset after 5 errors");
|
info!("TPU Reset after 5 errors");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_tpu_client(&self) -> Arc<QuicTpuClient> {
|
||||||
|
self.tpu_client.read().await.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn try_send_wire_transaction_batch(
|
pub async fn try_send_wire_transaction_batch(
|
||||||
&self,
|
&self,
|
||||||
wire_transactions: Vec<Vec<u8>>,
|
wire_transactions: Vec<Vec<u8>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
match self
|
let tpu_client = self.get_tpu_client().await;
|
||||||
.tpu_client
|
match tpu_client
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.try_send_wire_transaction_batch(wire_transactions)
|
.try_send_wire_transaction_batch(wire_transactions)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -120,6 +123,7 @@ impl TpuManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn estimated_current_slot(&self) -> u64 {
|
pub async fn estimated_current_slot(&self) -> u64 {
|
||||||
self.tpu_client.read().await.estimated_current_slot()
|
let tpu_client = self.get_tpu_client().await;
|
||||||
|
tpu_client.estimated_current_slot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::sync::{
|
use std::{
|
||||||
atomic::{AtomicU64, Ordering},
|
collections::{BTreeSet, VecDeque},
|
||||||
Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use jsonrpsee::SubscriptionSink;
|
use jsonrpsee::SubscriptionSink;
|
||||||
use log::{error, info, warn};
|
use log::{info, warn};
|
||||||
use prometheus::{histogram_opts, opts, register_counter, register_histogram, Counter, Histogram};
|
use prometheus::{histogram_opts, opts, register_counter, register_histogram, Counter, Histogram};
|
||||||
|
|
||||||
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
|
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
|
||||||
|
@ -20,11 +20,14 @@ use solana_sdk::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use solana_transaction_status::{
|
use solana_transaction_status::{
|
||||||
option_serializer::OptionSerializer, EncodedTransaction, RewardType,
|
option_serializer::OptionSerializer, RewardType, TransactionConfirmationStatus,
|
||||||
TransactionConfirmationStatus, TransactionDetails, TransactionStatus, UiConfirmedBlock,
|
TransactionDetails, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding,
|
||||||
UiTransactionEncoding, UiTransactionStatusMeta,
|
UiTransactionStatusMeta,
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
sync::{mpsc::Sender, Mutex},
|
||||||
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
use tokio::{sync::mpsc::Sender, task::JoinHandle};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block_store::BlockStore,
|
block_store::BlockStore,
|
||||||
|
@ -72,6 +75,7 @@ pub struct BlockListener {
|
||||||
pub struct BlockInformation {
|
pub struct BlockInformation {
|
||||||
pub slot: u64,
|
pub slot: u64,
|
||||||
pub block_height: u64,
|
pub block_height: u64,
|
||||||
|
pub blockhash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BlockListnerNotificatons {
|
pub struct BlockListnerNotificatons {
|
||||||
|
@ -105,8 +109,7 @@ impl BlockListener {
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
sink: SubscriptionSink,
|
sink: SubscriptionSink,
|
||||||
) {
|
) {
|
||||||
let _ = self
|
self.signature_subscribers
|
||||||
.signature_subscribers
|
|
||||||
.insert((signature, commitment_config), sink);
|
.insert((signature, commitment_config), sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +132,7 @@ impl BlockListener {
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
postgres: Option<PostgresMpscSend>,
|
postgres: Option<PostgresMpscSend>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
//info!("indexing slot {} commitment {}", slot, commitment_config.commitment);
|
||||||
let comfirmation_status = match commitment_config.commitment {
|
let comfirmation_status = match commitment_config.commitment {
|
||||||
CommitmentLevel::Finalized => TransactionConfirmationStatus::Finalized,
|
CommitmentLevel::Finalized => TransactionConfirmationStatus::Finalized,
|
||||||
_ => TransactionConfirmationStatus::Confirmed,
|
_ => TransactionConfirmationStatus::Confirmed,
|
||||||
|
@ -148,8 +152,8 @@ impl BlockListener {
|
||||||
transaction_details: Some(TransactionDetails::Full),
|
transaction_details: Some(TransactionDetails::Full),
|
||||||
commitment: Some(commitment_config),
|
commitment: Some(commitment_config),
|
||||||
max_supported_transaction_version: Some(0),
|
max_supported_transaction_version: Some(0),
|
||||||
encoding: Some(UiTransactionEncoding::JsonParsed),
|
encoding: Some(UiTransactionEncoding::Binary),
|
||||||
..Default::default()
|
rewards: Some(false),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -157,6 +161,7 @@ impl BlockListener {
|
||||||
timer.observe_duration();
|
timer.observe_duration();
|
||||||
|
|
||||||
if commitment_config.is_finalized() {
|
if commitment_config.is_finalized() {
|
||||||
|
info!("finalized slot {}", slot);
|
||||||
FIN_BLOCKS_RECV.inc();
|
FIN_BLOCKS_RECV.inc();
|
||||||
} else {
|
} else {
|
||||||
CON_BLOCKS_RECV.inc();
|
CON_BLOCKS_RECV.inc();
|
||||||
|
@ -177,8 +182,11 @@ impl BlockListener {
|
||||||
|
|
||||||
self.block_store
|
self.block_store
|
||||||
.add_block(
|
.add_block(
|
||||||
blockhash.clone(),
|
BlockInformation {
|
||||||
BlockInformation { slot, block_height },
|
slot,
|
||||||
|
block_height,
|
||||||
|
blockhash: blockhash.clone(),
|
||||||
|
},
|
||||||
commitment_config,
|
commitment_config,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -211,13 +219,14 @@ impl BlockListener {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let sig = match tx.transaction {
|
let tx = match tx.transaction.decode() {
|
||||||
EncodedTransaction::Json(json) => json.signatures[0].to_string(),
|
Some(tx) => tx,
|
||||||
_ => {
|
None => {
|
||||||
error!("Expected jsonParsed encoded tx");
|
warn!("transaction could not be decoded");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let sig = tx.signatures[0].to_string();
|
||||||
|
|
||||||
if let Some(mut tx_status) = self.tx_sender.txs_sent.get_mut(&sig) {
|
if let Some(mut tx_status) = self.tx_sender.txs_sent.get_mut(&sig) {
|
||||||
//
|
//
|
||||||
|
@ -283,23 +292,34 @@ impl BlockListener {
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
postgres: Option<PostgresMpscSend>,
|
postgres: Option<PostgresMpscSend>,
|
||||||
) -> JoinHandle<anyhow::Result<()>> {
|
) -> JoinHandle<anyhow::Result<()>> {
|
||||||
let (send, recv) = flume::unbounded();
|
let slots_task_queue = Arc::new(Mutex::new(VecDeque::<(u64, u8)>::new()));
|
||||||
let get_block_errors = Arc::new(AtomicU64::new(0));
|
// task to fetch blocks
|
||||||
|
|
||||||
for _i in 0..6 {
|
for _i in 0..6 {
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
let postgres = postgres.clone();
|
let postgres = postgres.clone();
|
||||||
let recv = recv.clone();
|
let slots_task_queue = slots_task_queue.clone();
|
||||||
let send = send.clone();
|
|
||||||
let get_block_errors = get_block_errors.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Ok(slot) = recv.recv_async().await {
|
let slots_task_queue = slots_task_queue.clone();
|
||||||
if get_block_errors.load(Ordering::Relaxed) > 6 {
|
loop {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
let (slot, error_count) = {
|
||||||
get_block_errors.fetch_sub(1, Ordering::Relaxed);
|
let mut queue = slots_task_queue.lock().await;
|
||||||
|
match queue.pop_front() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if error_count > 10 {
|
||||||
|
warn!(
|
||||||
|
"unable to get block at slot {} and commitment {}",
|
||||||
|
slot, commitment_config.commitment
|
||||||
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
// println!("{i} thread in slot {slot}");
|
|
||||||
|
|
||||||
if let Err(err) = this
|
if let Err(err) = this
|
||||||
.index_slot(slot, commitment_config, postgres.clone())
|
.index_slot(slot, commitment_config, postgres.clone())
|
||||||
|
@ -308,76 +328,67 @@ impl BlockListener {
|
||||||
warn!(
|
warn!(
|
||||||
"Error while indexing {commitment_config:?} block with slot {slot} {err}"
|
"Error while indexing {commitment_config:?} block with slot {slot} {err}"
|
||||||
);
|
);
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||||
get_block_errors.fetch_add(1, Ordering::Relaxed);
|
{
|
||||||
send.send_async(slot).await.unwrap();
|
let mut queue = slots_task_queue.lock().await;
|
||||||
|
queue.push_back((slot, error_count + 1));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// println!("{i} thread done slot {slot}");
|
// println!("{i} thread done slot {slot}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let (latest_slot_send, mut latest_slot_recv) = tokio::sync::mpsc::channel(1);
|
let rpc_client = self.rpc_client.clone();
|
||||||
|
|
||||||
{
|
|
||||||
let this = self.clone();
|
|
||||||
let latest_slot_send = latest_slot_send.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
while let Some(latest_slot) = latest_slot_recv.recv().await {
|
|
||||||
if let Err(err) = this
|
|
||||||
.index_slot(latest_slot, commitment_config, postgres.clone())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
warn!(
|
|
||||||
"Error while indexing latest {commitment_config:?} block with slot {latest_slot} {err}"
|
|
||||||
);
|
|
||||||
|
|
||||||
get_block_errors.fetch_add(1, Ordering::Relaxed);
|
|
||||||
latest_slot_send.send(latest_slot).await.unwrap();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut slot = self
|
let slots_task_queue = slots_task_queue.clone();
|
||||||
|
let last_latest_slot = self
|
||||||
.block_store
|
.block_store
|
||||||
.get_latest_block_info(commitment_config)
|
.get_latest_block_info(commitment_config)
|
||||||
.await
|
.await
|
||||||
.1
|
|
||||||
.slot;
|
.slot;
|
||||||
|
// -5 for warmup
|
||||||
|
let mut last_latest_slot = last_latest_slot - 5;
|
||||||
|
|
||||||
|
// storage for recent slots processed
|
||||||
|
const SLOT_PROCESSED_SIZE: usize = 128;
|
||||||
|
let mut slot_processed = BTreeSet::<u64>::new();
|
||||||
|
let rpc_client = rpc_client.clone();
|
||||||
loop {
|
loop {
|
||||||
info!("{commitment_config:?} {slot}");
|
let new_slot = rpc_client
|
||||||
|
.get_slot_with_commitment(commitment_config)
|
||||||
let mut new_block_slots = self
|
|
||||||
.rpc_client
|
|
||||||
.get_blocks_with_commitment(slot, None, commitment_config)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// filter already processed slots
|
||||||
|
let new_block_slots: Vec<u64> = (last_latest_slot..new_slot)
|
||||||
|
.filter(|x| !slot_processed.contains(x))
|
||||||
|
.map(|x| x)
|
||||||
|
.collect();
|
||||||
|
|
||||||
if new_block_slots.is_empty() {
|
if new_block_slots.is_empty() {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||||
println!("no slots");
|
println!("no slots");
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
//info!("Received new slots {commitment_config:?} {last_latest_slot}");
|
||||||
|
|
||||||
info!("Received new slots");
|
let latest_slot = *new_block_slots.last().unwrap();
|
||||||
|
|
||||||
let Some(latest_slot) = new_block_slots.pop() else {
|
// context for lock
|
||||||
warn!("Didn't receive any block slots for {slot}");
|
{
|
||||||
continue;
|
let mut lock = slots_task_queue.lock().await;
|
||||||
};
|
for slot in new_block_slots {
|
||||||
|
lock.push_back((slot, 0));
|
||||||
slot = latest_slot;
|
if slot_processed.insert(slot) && slot_processed.len() > SLOT_PROCESSED_SIZE
|
||||||
latest_slot_send.send(latest_slot).await?;
|
{
|
||||||
|
slot_processed.pop_first();
|
||||||
for slot in new_block_slots {
|
}
|
||||||
send.send_async(slot).await?;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last_latest_slot = latest_slot;
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use log::{info, warn};
|
||||||
use prometheus::{register_counter, Counter};
|
use prometheus::{register_counter, Counter};
|
||||||
use solana_transaction_status::TransactionStatus;
|
use solana_transaction_status::TransactionStatus;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::mpsc::{error::TryRecvError, UnboundedReceiver},
|
sync::{mpsc::UnboundedReceiver, TryAcquireError},
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ pub struct TxSender {
|
||||||
pub txs_sent: Arc<DashMap<String, TxProps>>,
|
pub txs_sent: Arc<DashMap<String, TxProps>>,
|
||||||
/// TpuClient to call the tpu port
|
/// TpuClient to call the tpu port
|
||||||
pub tpu_manager: Arc<TpuManager>,
|
pub tpu_manager: Arc<TpuManager>,
|
||||||
|
|
||||||
|
counting_semaphore: Arc<tokio::sync::Semaphore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transaction Properties
|
/// Transaction Properties
|
||||||
|
@ -58,62 +60,10 @@ impl TxSender {
|
||||||
Self {
|
Self {
|
||||||
tpu_manager,
|
tpu_manager,
|
||||||
txs_sent: Default::default(),
|
txs_sent: Default::default(),
|
||||||
|
counting_semaphore: Arc::new(tokio::sync::Semaphore::new(5)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// retry enqued_tx(s)
|
|
||||||
async fn forward_txs(
|
|
||||||
&self,
|
|
||||||
sigs_and_slots: Vec<(String, u64)>,
|
|
||||||
txs: Vec<WireTransaction>,
|
|
||||||
postgres: Option<PostgresMpscSend>,
|
|
||||||
) {
|
|
||||||
assert_eq!(sigs_and_slots.len(), txs.len());
|
|
||||||
|
|
||||||
if sigs_and_slots.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tpu_client = self.tpu_manager.clone();
|
|
||||||
let txs_sent = self.txs_sent.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let quic_response = match tpu_client.try_send_wire_transaction_batch(txs).await {
|
|
||||||
Ok(_) => {
|
|
||||||
for (sig, _) in &sigs_and_slots {
|
|
||||||
txs_sent.insert(sig.to_owned(), TxProps::default());
|
|
||||||
}
|
|
||||||
// metrics
|
|
||||||
TXS_SENT.inc_by(sigs_and_slots.len() as f64);
|
|
||||||
|
|
||||||
1
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!("{err}");
|
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(postgres) = postgres {
|
|
||||||
let forwarded_slot: u64 = tpu_client.estimated_current_slot().await;
|
|
||||||
|
|
||||||
for (sig, recent_slot) in sigs_and_slots {
|
|
||||||
postgres
|
|
||||||
.send(PostgresMsg::PostgresTx(PostgresTx {
|
|
||||||
signature: sig.clone(),
|
|
||||||
recent_slot: recent_slot as i64,
|
|
||||||
forwarded_slot: forwarded_slot as i64,
|
|
||||||
processed_slot: None,
|
|
||||||
cu_consumed: None,
|
|
||||||
cu_requested: None,
|
|
||||||
quic_response,
|
|
||||||
}))
|
|
||||||
.expect("Error writing to postgres service");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// retry and confirm transactions every 2ms (avg time to confirm tx)
|
/// retry and confirm transactions every 2ms (avg time to confirm tx)
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
self,
|
self,
|
||||||
|
@ -124,33 +74,105 @@ impl TxSender {
|
||||||
) -> JoinHandle<anyhow::Result<()>> {
|
) -> JoinHandle<anyhow::Result<()>> {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
info!(
|
info!(
|
||||||
"Batching tx(s) with batch size of {tx_batch_size} every {}ms",
|
"Batching tx(s) with batch size of {tx_batch_size} every {} ms",
|
||||||
tx_send_interval.as_millis()
|
tx_send_interval.as_millis()
|
||||||
);
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut sigs_and_slots = Vec::with_capacity(tx_batch_size);
|
let mut sigs_and_slots = Vec::with_capacity(tx_batch_size);
|
||||||
let mut txs = Vec::with_capacity(tx_batch_size);
|
let mut txs = Vec::with_capacity(tx_batch_size);
|
||||||
|
let mut maybe_permit = None;
|
||||||
|
let counting_semaphore = self.counting_semaphore.clone();
|
||||||
while txs.len() <= tx_batch_size {
|
while txs.len() <= tx_batch_size {
|
||||||
match recv.try_recv() {
|
let res = tokio::time::timeout(tx_send_interval, recv.recv()).await;
|
||||||
Ok((sig, tx, slot)) => {
|
match res {
|
||||||
sigs_and_slots.push((sig, slot));
|
Ok(value) => match value {
|
||||||
txs.push(tx);
|
Some((sig, tx, slot)) => {
|
||||||
}
|
sigs_and_slots.push((sig, slot));
|
||||||
Err(TryRecvError::Disconnected) => {
|
txs.push(tx);
|
||||||
bail!("Channel Disconnected");
|
}
|
||||||
}
|
None => {
|
||||||
|
bail!("Channel Disconnected");
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
break;
|
let res = counting_semaphore.clone().try_acquire_owned();
|
||||||
|
match res {
|
||||||
|
Ok(permit) => {
|
||||||
|
maybe_permit = Some(permit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(TryAcquireError::Closed) => {
|
||||||
|
bail!("Semaphone permit error");
|
||||||
|
}
|
||||||
|
Err(TryAcquireError::NoPermits) => {
|
||||||
|
// No permits continue to fetch transactions and batch them
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert_eq!(sigs_and_slots.len(), txs.len());
|
||||||
|
|
||||||
self.forward_txs(sigs_and_slots, txs, postgres_send.clone())
|
if sigs_and_slots.is_empty() {
|
||||||
.await;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
tokio::time::sleep(tx_send_interval).await;
|
let permit = match maybe_permit {
|
||||||
|
Some(permit) => permit,
|
||||||
|
None => {
|
||||||
|
// get the permit
|
||||||
|
counting_semaphore.acquire_owned().await.unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let postgres_send = postgres_send.clone();
|
||||||
|
let tpu_client = self.tpu_manager.clone();
|
||||||
|
let txs_sent = self.txs_sent.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let semaphore_permit = permit;
|
||||||
|
|
||||||
|
for (sig, _) in &sigs_and_slots {
|
||||||
|
txs_sent.insert(sig.to_owned(), TxProps::default());
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
"sending {} transactions by tpu size {}",
|
||||||
|
txs.len(),
|
||||||
|
txs_sent.len()
|
||||||
|
);
|
||||||
|
let quic_response = {
|
||||||
|
let _semaphore_permit = semaphore_permit;
|
||||||
|
match tpu_client.try_send_wire_transaction_batch(txs).await {
|
||||||
|
Ok(_) => {
|
||||||
|
// metrics
|
||||||
|
TXS_SENT.inc_by(sigs_and_slots.len() as f64);
|
||||||
|
1
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!("{err}");
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(postgres) = postgres_send {
|
||||||
|
let forwarded_slot: u64 = tpu_client.estimated_current_slot().await;
|
||||||
|
|
||||||
|
for (sig, recent_slot) in sigs_and_slots {
|
||||||
|
postgres
|
||||||
|
.send(PostgresMsg::PostgresTx(PostgresTx {
|
||||||
|
signature: sig.clone(),
|
||||||
|
recent_slot: recent_slot as i64,
|
||||||
|
forwarded_slot: forwarded_slot as i64,
|
||||||
|
processed_slot: None,
|
||||||
|
cu_consumed: None,
|
||||||
|
cu_requested: None,
|
||||||
|
quic_response,
|
||||||
|
}))
|
||||||
|
.expect("Error writing to postgres service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
use lite_rpc::DEFAULT_LITE_RPC_ADDR;
|
use lite_rpc::DEFAULT_LITE_RPC_ADDR;
|
||||||
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
|
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
|
||||||
|
|
||||||
|
@ -8,8 +7,8 @@ async fn blockhash() -> anyhow::Result<()> {
|
||||||
|
|
||||||
let mut prev_blockhash = lite_rpc.get_latest_blockhash().await.unwrap();
|
let mut prev_blockhash = lite_rpc.get_latest_blockhash().await.unwrap();
|
||||||
|
|
||||||
for _ in 0..5 {
|
for _ in 0..60 {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(3000)).await;
|
||||||
|
|
||||||
let blockhash = lite_rpc.get_latest_blockhash().await.unwrap();
|
let blockhash = lite_rpc.get_latest_blockhash().await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
/* JavaScript Support */
|
/* JavaScript Support */
|
||||||
|
|
Loading…
Reference in New Issue