Remove stake-o-matic

This commit is contained in:
Michael Vines 2021-03-24 17:43:30 -07:00
parent a8ef29df27
commit b7aa366758
11 changed files with 0 additions and 4128 deletions

24
Cargo.lock generated
View File

@ -5219,30 +5219,6 @@ dependencies = [
"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]]
name = "solana-stake-program"
version = "1.7.0"

View File

@ -31,7 +31,6 @@ members = [
"log-analyzer",
"merkle-root-bench",
"merkle-tree",
"stake-o-matic",
"storage-bigtable",
"storage-proto",
"streamer",

View File

@ -95,7 +95,6 @@ else
solana-net-shaper
solana-stake-accounts
solana-stake-monitor
solana-stake-o-matic
solana-sys-tuner
solana-test-validator
solana-tokens

View File

@ -1,2 +0,0 @@
target/
solana-install/

View File

@ -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"]

View File

@ -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.

View File

@ -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

View File

@ -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>()
}
}

View File

@ -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 "$@"