mod blockhash_poller; mod transaction_builder; mod transaction_sender; use anchor_client::{ solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}, Cluster, }; use anchor_lang::prelude::Pubkey; use bytemuck::bytes_of; use client::{Client, MangoGroupContext}; use log::*; use solana_client::nonblocking::rpc_client::RpcClient; use std::{collections::HashSet, fs::File, io::Read, str::FromStr, sync::Arc, time::Duration}; use serde::Deserialize; use solana_geyser_connector_lib::FilterConfig; use solana_geyser_connector_lib::{ grpc_plugin_source, metrics, websocket_source, MetricsConfig, SourceConfig, }; #[derive(Clone, Debug, Deserialize)] pub struct Config { pub source: SourceConfig, pub metrics: MetricsConfig, pub bind_ws_addr: String, pub rpc_http_url: String, pub mango_group: String, pub keypair: Vec, } #[tokio::main] async fn main() -> anyhow::Result<()> { solana_logger::setup_with_default("info"); let args: Vec = std::env::args().collect(); if args.len() < 2 { error!("Please enter a config file path argument."); return Ok(()); } let config: Config = { let mut file = File::open(&args[1])?; let mut contents = String::new(); file.read_to_string(&mut contents)?; toml::from_str(&contents).unwrap() }; let rpc_client = Arc::new(RpcClient::new(config.rpc_http_url.clone())); let blockhash = blockhash_poller::init(rpc_client.clone()).await; let metrics_tx = metrics::start(config.metrics, "crank".into()); let rpc_url = config.rpc_http_url; let ws_url = rpc_url.replace("https", "wss"); let rpc_timeout = Duration::from_secs(10); let cluster = Cluster::Custom(rpc_url.clone(), ws_url.clone()); let client = Client::new( cluster.clone(), CommitmentConfig::processed(), &Keypair::new(), Some(rpc_timeout), 0, ); let group_pk = Pubkey::from_str(&config.mango_group).unwrap(); let group_context = Arc::new(MangoGroupContext::new_from_rpc(&client.rpc_async(), group_pk).await?); let perp_queue_pks: Vec<_> = group_context .perp_markets .iter() .map(|(_, context)| (context.address, context.market.event_queue)) .collect(); // fetch all serum/openbook markets to find their event queues let serum_market_pks: Vec<_> = group_context .serum3_markets .iter() .map(|(_, context)| context.market.serum_market_external) .collect(); let serum_market_ais = client .rpc_async() .get_multiple_accounts(serum_market_pks.as_slice()) .await?; let serum_market_ais: Vec<_> = serum_market_ais .iter() .filter_map(|maybe_ai| match maybe_ai { Some(ai) => Some(ai), None => None, }) .collect(); let serum_queue_pks: Vec<_> = serum_market_ais .iter() .enumerate() .map(|pair| { let market_state: serum_dex::state::MarketState = *bytemuck::from_bytes( &pair.1.data[5..5 + std::mem::size_of::()], ); let event_q = market_state.event_q; (serum_market_pks[pair.0], Pubkey::new(bytes_of(&event_q))) }) .collect(); let (account_write_queue_sender, slot_queue_sender, instruction_receiver) = transaction_builder::init( perp_queue_pks.clone(), serum_queue_pks.clone(), group_pk, metrics_tx.clone(), ) .expect("init transaction builder"); // TODO: throttle cranking, currently runs very fast transaction_sender::init( instruction_receiver, blockhash, rpc_client, Keypair::from_bytes(&config.keypair).expect("valid keyair in config"), ); info!( "connect: {}", config .source .grpc_sources .iter() .map(|c| c.connection_string.clone()) .collect::() ); let use_geyser = true; let all_queue_pks: HashSet = perp_queue_pks .iter() .chain(serum_queue_pks.iter()) .map(|mkt| mkt.1) .collect(); let filter_config = FilterConfig { program_ids: vec![], account_ids: all_queue_pks.iter().map(|pk| pk.to_string()).collect(), }; if use_geyser { grpc_plugin_source::process_events( &config.source, &filter_config, account_write_queue_sender, slot_queue_sender, metrics_tx.clone(), ) .await; } else { websocket_source::process_events( &config.source, account_write_queue_sender, slot_queue_sender, ) .await; } Ok(()) }