Remove stake-o-matic
This commit is contained in:
parent
a8ef29df27
commit
b7aa366758
|
@ -5219,30 +5219,6 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "solana-stake-o-matic"
|
|
||||||
version = "1.7.0"
|
|
||||||
dependencies = [
|
|
||||||
"clap",
|
|
||||||
"log 0.4.11",
|
|
||||||
"reqwest",
|
|
||||||
"semver 0.9.0",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_yaml",
|
|
||||||
"solana-clap-utils",
|
|
||||||
"solana-cli-config",
|
|
||||||
"solana-cli-output",
|
|
||||||
"solana-client",
|
|
||||||
"solana-logger 1.7.0",
|
|
||||||
"solana-metrics",
|
|
||||||
"solana-notifier",
|
|
||||||
"solana-sdk",
|
|
||||||
"solana-stake-program",
|
|
||||||
"solana-transaction-status",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solana-stake-program"
|
name = "solana-stake-program"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
|
|
|
@ -31,7 +31,6 @@ members = [
|
||||||
"log-analyzer",
|
"log-analyzer",
|
||||||
"merkle-root-bench",
|
"merkle-root-bench",
|
||||||
"merkle-tree",
|
"merkle-tree",
|
||||||
"stake-o-matic",
|
|
||||||
"storage-bigtable",
|
"storage-bigtable",
|
||||||
"storage-proto",
|
"storage-proto",
|
||||||
"streamer",
|
"streamer",
|
||||||
|
|
|
@ -95,7 +95,6 @@ else
|
||||||
solana-net-shaper
|
solana-net-shaper
|
||||||
solana-stake-accounts
|
solana-stake-accounts
|
||||||
solana-stake-monitor
|
solana-stake-monitor
|
||||||
solana-stake-o-matic
|
|
||||||
solana-sys-tuner
|
solana-sys-tuner
|
||||||
solana-test-validator
|
solana-test-validator
|
||||||
solana-tokens
|
solana-tokens
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
target/
|
|
||||||
solana-install/
|
|
|
@ -1,33 +0,0 @@
|
||||||
[package]
|
|
||||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
|
||||||
description = "I will find you and I will stake you"
|
|
||||||
edition = "2018"
|
|
||||||
homepage = "https://solana.com/"
|
|
||||||
documentation = "https://docs.rs/"
|
|
||||||
license = "Apache-2.0"
|
|
||||||
name = "solana-stake-o-matic"
|
|
||||||
repository = "https://github.com/solana-labs/stake-o-matic"
|
|
||||||
version = "1.7.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clap = "2.33.0"
|
|
||||||
log = "0.4.11"
|
|
||||||
reqwest = { version = "0.11.2", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
|
||||||
semver = "0.9.0"
|
|
||||||
serde = { version = "1.0.122", features = ["derive"] }
|
|
||||||
serde_json = "1.0.62"
|
|
||||||
serde_yaml = "0.8.13"
|
|
||||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
|
||||||
solana-client = { path = "../client", version = "=1.7.0" }
|
|
||||||
solana-cli-config = { path = "../cli-config", version = "=1.7.0" }
|
|
||||||
solana-cli-output = { path = "../cli-output", version = "=1.7.0" }
|
|
||||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
|
||||||
solana-metrics = { path = "../metrics", version = "=1.7.0" }
|
|
||||||
solana-notifier = { path = "../notifier", version = "=1.7.0" }
|
|
||||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
|
||||||
solana-stake-program = { path = "../programs/stake", version = "=1.7.0" }
|
|
||||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.0" }
|
|
||||||
thiserror = "1.0.21"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
targets = ["x86_64-unknown-linux-gnu"]
|
|
|
@ -1,21 +0,0 @@
|
||||||
## Effortlessly Manage Cluster Stakes
|
|
||||||
The testnet and mainnet-beta clusters currently have a large population of
|
|
||||||
validators that need to be staked by a central authority.
|
|
||||||
|
|
||||||
## Staking Criteria
|
|
||||||
1. All non-delinquent validators receive 50,000 SOL stake
|
|
||||||
1. Additionally, non-deliquent validators that have produced a block in 75% of
|
|
||||||
their slots in the previous epoch receive bonus stake of 500,000 SOL
|
|
||||||
|
|
||||||
A validator that is delinquent for more than 24 hours will have all stake
|
|
||||||
removed. However stake-o-matic has no memory, so if the same validator resolves
|
|
||||||
their delinquency then they will be re-staked again
|
|
||||||
|
|
||||||
## Validator Whitelist
|
|
||||||
To be eligible for staking, a validator's identity pubkey must be added to a
|
|
||||||
YAML whitelist file.
|
|
||||||
|
|
||||||
## Stake Account Management
|
|
||||||
Stake-o-matic will split the individual validator stake accounts from a master
|
|
||||||
stake account, and must be given the authorized staker keypair for the master
|
|
||||||
stake account.
|
|
|
@ -1,248 +0,0 @@
|
||||||
use crate::retry_rpc_operation;
|
|
||||||
use log::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use solana_client::rpc_client::RpcClient;
|
|
||||||
use solana_sdk::{
|
|
||||||
clock::{Slot, DEFAULT_SLOTS_PER_EPOCH},
|
|
||||||
commitment_config::CommitmentConfig,
|
|
||||||
epoch_info::EpochInfo,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
fs::{self, File, OpenOptions},
|
|
||||||
io,
|
|
||||||
ops::Range,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
struct Entry {
|
|
||||||
slots: Range<Slot>,
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entry {
|
|
||||||
pub fn new<P: AsRef<Path>>(base_path: P, slots: Range<Slot>) -> Self {
|
|
||||||
let file_name = format!("{}-{}.json", slots.start, slots.end);
|
|
||||||
let path = base_path.as_ref().join(file_name);
|
|
||||||
Self { slots, path }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_filename<F: AsRef<Path>>(filename: F) -> Option<Range<Slot>> {
|
|
||||||
let filename = filename.as_ref();
|
|
||||||
let slot_range = filename.file_stem();
|
|
||||||
let extension = filename.extension();
|
|
||||||
extension
|
|
||||||
.zip(slot_range)
|
|
||||||
.and_then(|(extension, slot_range)| {
|
|
||||||
if extension == "json" {
|
|
||||||
slot_range.to_str()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.and_then(|slot_range| {
|
|
||||||
let mut parts = slot_range.splitn(2, '-');
|
|
||||||
let start = parts.next().and_then(|p| p.parse::<Slot>().ok());
|
|
||||||
let end = parts.next().and_then(|p| p.parse::<Slot>().ok());
|
|
||||||
start.zip(end).map(|(start, end)| start..end)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_pathbuf(path: PathBuf) -> Option<Self> {
|
|
||||||
path.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.and_then(Self::parse_filename)
|
|
||||||
.map(|slots| Self { slots, path })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path(&self) -> &Path {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CACHE_VERSION: u64 = 0;
|
|
||||||
const DEFAULT_SLOTS_PER_ENTRY: u64 = 2500;
|
|
||||||
const DEFAULT_MAX_CACHED_SLOTS: u64 = 5 * DEFAULT_SLOTS_PER_EPOCH;
|
|
||||||
const CONFIG_FILENAME: &str = "config.yaml";
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
struct Config {
|
|
||||||
version: u64,
|
|
||||||
slots_per_chunk: u64,
|
|
||||||
max_cached_slots: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
version: CACHE_VERSION,
|
|
||||||
slots_per_chunk: DEFAULT_SLOTS_PER_ENTRY,
|
|
||||||
max_cached_slots: DEFAULT_MAX_CACHED_SLOTS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ConfirmedBlockCache {
|
|
||||||
rpc_client: RpcClient,
|
|
||||||
base_path: PathBuf,
|
|
||||||
entries: RefCell<Vec<Entry>>,
|
|
||||||
config: Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfirmedBlockCache {
|
|
||||||
fn store_config<P: AsRef<Path>>(config_path: P, config: &Config) -> io::Result<()> {
|
|
||||||
let config_path = config_path.as_ref();
|
|
||||||
let file = File::create(config_path)?;
|
|
||||||
serde_yaml::to_writer(file, config).map_err(|e| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
format!(
|
|
||||||
"error: cannot store config `{}`: {:?}",
|
|
||||||
config_path.to_string_lossy(),
|
|
||||||
e,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_config<P: AsRef<Path>>(config_path: P) -> io::Result<Config> {
|
|
||||||
let config_path = config_path.as_ref();
|
|
||||||
let file = File::open(config_path)?;
|
|
||||||
serde_yaml::from_reader(file).map_err(|e| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
format!(
|
|
||||||
"error: cannot load config `{}`: {:?}",
|
|
||||||
config_path.to_string_lossy(),
|
|
||||||
e,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open<P: AsRef<Path>, U: AsRef<str>>(path: P, rpc_url: U) -> io::Result<Self> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let config_path = path.join(CONFIG_FILENAME);
|
|
||||||
let rpc_url = rpc_url.as_ref();
|
|
||||||
let rpc_client = RpcClient::new(rpc_url.to_string());
|
|
||||||
let (config, entries) = match fs::read_dir(path) {
|
|
||||||
Ok(dir_entries) => {
|
|
||||||
let config = Self::load_config(&config_path)?;
|
|
||||||
if config.version != CACHE_VERSION {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"unexpected cache version",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let current_slot = rpc_client
|
|
||||||
.get_slot()
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?;
|
|
||||||
let eviction_slot = current_slot.saturating_sub(config.max_cached_slots);
|
|
||||||
let (delete, mut entries) = dir_entries
|
|
||||||
.filter_map(|de| Entry::from_pathbuf(de.unwrap().path()))
|
|
||||||
.fold(
|
|
||||||
(Vec::new(), Vec::new()),
|
|
||||||
|(mut delete, mut entries), entry| {
|
|
||||||
if entry.slots.end < eviction_slot {
|
|
||||||
delete.push(entry);
|
|
||||||
} else {
|
|
||||||
entries.push(entry);
|
|
||||||
}
|
|
||||||
(delete, entries)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let mut evicted_ranges = Vec::new();
|
|
||||||
for d in &delete {
|
|
||||||
match std::fs::remove_file(&d.path) {
|
|
||||||
Ok(()) => evicted_ranges.push(format!("{:?}", d.slots)),
|
|
||||||
Err(e) => warn!("entry eviction for slots {:?} failed: {}", d.slots, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!("entries evicted for slots: {}", evicted_ranges.join(", "));
|
|
||||||
entries.sort_by(|l, r| l.slots.start.cmp(&r.slots.start));
|
|
||||||
Ok((config, entries))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
if err.kind() == io::ErrorKind::NotFound {
|
|
||||||
let config = Config::default();
|
|
||||||
fs::create_dir_all(path)?;
|
|
||||||
Self::store_config(config_path, &config)?;
|
|
||||||
Ok((config, Vec::new()))
|
|
||||||
} else {
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
Ok(Self {
|
|
||||||
rpc_client,
|
|
||||||
base_path: path.to_path_buf(),
|
|
||||||
entries: RefCell::new(entries),
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup(&self, start: Slot) -> Option<Entry> {
|
|
||||||
let entries = self.entries.borrow();
|
|
||||||
for i in entries.iter() {
|
|
||||||
if i.slots.start == start {
|
|
||||||
debug!("HIT: {}", start);
|
|
||||||
return Some(i.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!("MISS: {}", start);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch(&self, start: Slot, end: Slot, epoch_info: &EpochInfo) -> io::Result<Vec<Slot>> {
|
|
||||||
debug!("fetching slot range: {}..{}", start, end);
|
|
||||||
// Fingers crossed we hit the same RPC backend...
|
|
||||||
let slots = retry_rpc_operation(42, || {
|
|
||||||
self.rpc_client.get_confirmed_blocks(start, Some(end))
|
|
||||||
})
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))?;
|
|
||||||
|
|
||||||
// Only cache complete chunks
|
|
||||||
if end + self.config.slots_per_chunk < epoch_info.absolute_slot {
|
|
||||||
debug!("committing entry for slots {}..{}", start, end);
|
|
||||||
let entry = Entry::new(&self.base_path, start..end);
|
|
||||||
let file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create_new(true)
|
|
||||||
.open(entry.path())?;
|
|
||||||
serde_json::to_writer(file, &slots)?;
|
|
||||||
|
|
||||||
self.entries.borrow_mut().push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(slots)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query(&self, start: Slot, end: Slot) -> io::Result<Vec<Slot>> {
|
|
||||||
let chunk_size = self.config.slots_per_chunk;
|
|
||||||
let mut chunk_start = (start / chunk_size) * chunk_size;
|
|
||||||
let mut slots = Vec::new();
|
|
||||||
let epoch_info = self
|
|
||||||
.rpc_client
|
|
||||||
.get_epoch_info_with_commitment(CommitmentConfig::finalized())
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))?;
|
|
||||||
let last_slot = end.min(epoch_info.absolute_slot);
|
|
||||||
while chunk_start < last_slot {
|
|
||||||
let mut chunk_slots = if let Some(entry) = self.lookup(chunk_start) {
|
|
||||||
let file = File::open(entry.path())?;
|
|
||||||
serde_json::from_reader(file)?
|
|
||||||
} else {
|
|
||||||
let chunk_end = chunk_start + chunk_size - 1;
|
|
||||||
self.fetch(chunk_start, chunk_end, &epoch_info)?
|
|
||||||
};
|
|
||||||
slots.append(&mut chunk_slots);
|
|
||||||
chunk_start += chunk_size;
|
|
||||||
}
|
|
||||||
let slots = slots
|
|
||||||
.drain(..)
|
|
||||||
.skip_while(|s| *s < start)
|
|
||||||
.take_while(|s| *s <= end)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
Ok(slots)
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,196 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ClusterJson {
|
|
||||||
MainnetBeta,
|
|
||||||
Testnet,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ClusterJson {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::MainnetBeta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for ClusterJson {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Self::MainnetBeta => "mainnet.json",
|
|
||||||
Self::Testnet => "testnet.json",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_BASE_URL: &str = "https://www.validators.app/api/v1/";
|
|
||||||
const TOKEN_HTTP_HEADER_NAME: &str = "Token";
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ClientConfig {
|
|
||||||
pub base_url: String,
|
|
||||||
pub cluster: ClusterJson,
|
|
||||||
pub api_token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ClientConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
base_url: DEFAULT_BASE_URL.to_string(),
|
|
||||||
cluster: ClusterJson::default(),
|
|
||||||
api_token: String::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Endpoint {
|
|
||||||
Ping,
|
|
||||||
Validators,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Endpoint {
|
|
||||||
fn with_cluster(path: &str, cluster: &ClusterJson) -> String {
|
|
||||||
format!("{}/{}", path, cluster.as_ref())
|
|
||||||
}
|
|
||||||
pub fn path(&self, cluster: &ClusterJson) -> String {
|
|
||||||
match self {
|
|
||||||
Self::Ping => "ping.json".to_string(),
|
|
||||||
Self::Validators => Self::with_cluster("validators", cluster),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
struct PingResponse {
|
|
||||||
answer: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct ValidatorsResponseEntry {
|
|
||||||
pub account: Option<String>,
|
|
||||||
pub active_stake: Option<u64>,
|
|
||||||
pub commission: Option<u8>,
|
|
||||||
pub created_at: Option<String>,
|
|
||||||
pub data_center_concentration_score: Option<i64>,
|
|
||||||
pub data_center_host: Option<String>,
|
|
||||||
pub data_center_key: Option<String>,
|
|
||||||
pub delinquent: Option<bool>,
|
|
||||||
pub details: Option<String>,
|
|
||||||
pub keybase_id: Option<String>,
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub network: Option<String>,
|
|
||||||
pub ping_time: Option<f64>,
|
|
||||||
pub published_information_score: Option<i64>,
|
|
||||||
pub root_distance_score: Option<i64>,
|
|
||||||
pub security_report_score: Option<i64>,
|
|
||||||
pub skipped_slot_percent: Option<String>,
|
|
||||||
pub skipped_slot_score: Option<i64>,
|
|
||||||
pub skipped_slots: Option<u64>,
|
|
||||||
pub software_version: Option<String>,
|
|
||||||
pub software_version_score: Option<i64>,
|
|
||||||
pub stake_concentration_score: Option<i64>,
|
|
||||||
pub total_score: Option<i64>,
|
|
||||||
pub updated_at: Option<String>,
|
|
||||||
pub url: Option<String>,
|
|
||||||
pub vote_account: Option<String>,
|
|
||||||
pub vote_distance_score: Option<i64>,
|
|
||||||
pub www_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct ValidatorsResponse(Vec<ValidatorsResponseEntry>);
|
|
||||||
|
|
||||||
impl AsRef<Vec<ValidatorsResponseEntry>> for ValidatorsResponse {
|
|
||||||
fn as_ref(&self) -> &Vec<ValidatorsResponseEntry> {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum SortKind {
|
|
||||||
Score,
|
|
||||||
Name,
|
|
||||||
Stake,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for SortKind {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Score => write!(f, "score"),
|
|
||||||
Self::Name => write!(f, "name"),
|
|
||||||
Self::Stake => write!(f, "stake"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Limit = u32;
|
|
||||||
|
|
||||||
pub struct Client {
|
|
||||||
base_url: reqwest::Url,
|
|
||||||
cluster: ClusterJson,
|
|
||||||
api_token: String,
|
|
||||||
client: reqwest::blocking::Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn new<T: AsRef<str>>(api_token: T) -> Self {
|
|
||||||
let config = ClientConfig {
|
|
||||||
api_token: api_token.as_ref().to_string(),
|
|
||||||
..ClientConfig::default()
|
|
||||||
};
|
|
||||||
Self::new_with_config(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_config(config: ClientConfig) -> Self {
|
|
||||||
let ClientConfig {
|
|
||||||
base_url,
|
|
||||||
cluster,
|
|
||||||
api_token,
|
|
||||||
} = config;
|
|
||||||
Self {
|
|
||||||
base_url: reqwest::Url::parse(&base_url).unwrap(),
|
|
||||||
cluster,
|
|
||||||
api_token,
|
|
||||||
client: reqwest::blocking::Client::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request(
|
|
||||||
&self,
|
|
||||||
endpoint: Endpoint,
|
|
||||||
query: &HashMap<String, String>,
|
|
||||||
) -> reqwest::Result<reqwest::blocking::Response> {
|
|
||||||
let url = self.base_url.join(&endpoint.path(&self.cluster)).unwrap();
|
|
||||||
let request = self
|
|
||||||
.client
|
|
||||||
.get(url)
|
|
||||||
.header(TOKEN_HTTP_HEADER_NAME, &self.api_token)
|
|
||||||
.query(&query)
|
|
||||||
.build()?;
|
|
||||||
self.client.execute(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn ping(&self) -> reqwest::Result<()> {
|
|
||||||
let response = self.request(Endpoint::Ping, &HashMap::new())?;
|
|
||||||
response.json::<PingResponse>().map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validators(
|
|
||||||
&self,
|
|
||||||
sort: Option<SortKind>,
|
|
||||||
limit: Option<Limit>,
|
|
||||||
) -> reqwest::Result<ValidatorsResponse> {
|
|
||||||
let mut query = HashMap::new();
|
|
||||||
if let Some(sort) = sort {
|
|
||||||
query.insert("sort".into(), sort.to_string());
|
|
||||||
}
|
|
||||||
if let Some(limit) = limit {
|
|
||||||
query.insert("limit".into(), limit.to_string());
|
|
||||||
}
|
|
||||||
let response = self.request(Endpoint::Validators, &query)?;
|
|
||||||
response.json::<ValidatorsResponse>()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# Downloads and runs the latest stake-o-matic binary
|
|
||||||
#
|
|
||||||
set -e
|
|
||||||
|
|
||||||
solana_version=edge
|
|
||||||
curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/solana-install-init.sh \
|
|
||||||
| sh -s - $solana_version \
|
|
||||||
--no-modify-path \
|
|
||||||
--data-dir ./solana-install \
|
|
||||||
--config ./solana-install/config.yml
|
|
||||||
|
|
||||||
PATH="$(realpath "$PWD"/solana-install/releases/"$solana_version"*/solana-release/bin/):$PATH"
|
|
||||||
echo PATH="$PATH"
|
|
||||||
|
|
||||||
set -x
|
|
||||||
solana --version
|
|
||||||
exec solana-stake-o-matic "$@"
|
|
Loading…
Reference in New Issue