solana-validator now verifies its genesis blockhash against the cluster entrypoint (#5589)

This commit is contained in:
Michael Vines 2019-08-21 18:16:40 -07:00 committed by GitHub
parent 5034331131
commit e2d6f01ad3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 105 additions and 15 deletions

1
Cargo.lock generated
View File

@ -3846,6 +3846,7 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.9.19 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-client 0.18.0-pre2",
"solana-core 0.18.0-pre2", "solana-core 0.18.0-pre2",
"solana-drone 0.18.0-pre2", "solana-drone 0.18.0-pre2",
"solana-logger 0.18.0-pre2", "solana-logger 0.18.0-pre2",

View File

@ -26,6 +26,7 @@ Methods
* [getBalance](#getbalance) * [getBalance](#getbalance)
* [getClusterNodes](#getclusternodes) * [getClusterNodes](#getclusternodes)
* [getEpochInfo](#getepochinfo) * [getEpochInfo](#getepochinfo)
* [getGenesisBlockhash](#getgenesisblockhash)
* [getLeaderSchedule](#getleaderschedule) * [getLeaderSchedule](#getleaderschedule)
* [getProgramAccounts](#getprogramaccounts) * [getProgramAccounts](#getprogramaccounts)
* [getRecentBlockhash](#getrecentblockhash) * [getRecentBlockhash](#getrecentblockhash)
@ -197,6 +198,25 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
{"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1} {"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1}
``` ```
---
### getGenesisBlockhash
Returns the genesis block hash
##### Parameters:
None
##### Results:
* `string` - a Hash as base-58 encoded string
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
```
--- ---
### getLeaderSchedule ### getLeaderSchedule

View File

@ -369,7 +369,7 @@ impl RpcClient {
let blockhash = blockhash.parse().map_err(|err| { let blockhash = blockhash.parse().map_err(|err| {
io::Error::new( io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
format!("GetRecentBlockhash parse failure: {:?}", err), format!("GetRecentBlockhash hash parse failure: {:?}", err),
) )
})?; })?;
Ok((blockhash, fee_calculator)) Ok((blockhash, fee_calculator))
@ -397,6 +397,33 @@ impl RpcClient {
)) ))
} }
pub fn get_genesis_blockhash(&self) -> io::Result<Hash> {
let response = self
.client
.send(&RpcRequest::GetGenesisBlockhash, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetGenesisBlockhash request failure: {:?}", err),
)
})?;
let blockhash = serde_json::from_value::<String>(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetGenesisBlockhash parse failure: {:?}", err),
)
})?;
let blockhash = blockhash.parse().map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetGenesisBlockhash hash parse failure: {:?}", err),
)
})?;
Ok(blockhash)
}
pub fn poll_balance_with_timeout( pub fn poll_balance_with_timeout(
&self, &self,
pubkey: &Pubkey, pubkey: &Pubkey,

View File

@ -9,6 +9,7 @@ pub enum RpcRequest {
GetAccountInfo, GetAccountInfo,
GetBalance, GetBalance,
GetClusterNodes, GetClusterNodes,
GetGenesisBlockhash,
GetNumBlocksSinceSignatureConfirmation, GetNumBlocksSinceSignatureConfirmation,
GetProgramAccounts, GetProgramAccounts,
GetRecentBlockhash, GetRecentBlockhash,
@ -38,6 +39,7 @@ impl RpcRequest {
RpcRequest::GetAccountInfo => "getAccountInfo", RpcRequest::GetAccountInfo => "getAccountInfo",
RpcRequest::GetBalance => "getBalance", RpcRequest::GetBalance => "getBalance",
RpcRequest::GetClusterNodes => "getClusterNodes", RpcRequest::GetClusterNodes => "getClusterNodes",
RpcRequest::GetGenesisBlockhash => "getGenesisBlockhash",
RpcRequest::GetNumBlocksSinceSignatureConfirmation => { RpcRequest::GetNumBlocksSinceSignatureConfirmation => {
"getNumBlocksSinceSignatureConfirmation" "getNumBlocksSinceSignatureConfirmation"
} }

View File

@ -14,6 +14,7 @@ use solana_drone::drone::request_airdrop_transaction;
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_sdk::account::Account; use solana_sdk::account::Account;
use solana_sdk::fee_calculator::FeeCalculator; use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Signature; use solana_sdk::signature::Signature;
use solana_sdk::transaction::{self, Transaction}; use solana_sdk::transaction::{self, Transaction};
@ -213,6 +214,7 @@ fn verify_signature(input: &str) -> Result<Signature> {
pub struct Meta { pub struct Meta {
pub request_processor: Arc<RwLock<JsonRpcRequestProcessor>>, pub request_processor: Arc<RwLock<JsonRpcRequestProcessor>>,
pub cluster_info: Arc<RwLock<ClusterInfo>>, pub cluster_info: Arc<RwLock<ClusterInfo>>,
pub genesis_blockhash: Hash,
} }
impl Metadata for Meta {} impl Metadata for Meta {}
@ -298,6 +300,9 @@ pub trait RpcSol {
#[rpc(meta, name = "getEpochInfo")] #[rpc(meta, name = "getEpochInfo")]
fn get_epoch_info(&self, _: Self::Metadata) -> Result<RpcEpochInfo>; fn get_epoch_info(&self, _: Self::Metadata) -> Result<RpcEpochInfo>;
#[rpc(meta, name = "getGenesisBlockhash")]
fn get_genesis_blockhash(&self, _: Self::Metadata) -> Result<String>;
#[rpc(meta, name = "getLeaderSchedule")] #[rpc(meta, name = "getLeaderSchedule")]
fn get_leader_schedule(&self, _: Self::Metadata) -> Result<Option<Vec<String>>>; fn get_leader_schedule(&self, _: Self::Metadata) -> Result<Option<Vec<String>>>;
@ -448,6 +453,11 @@ impl RpcSol for RpcSolImpl {
}) })
} }
fn get_genesis_blockhash(&self, meta: Self::Metadata) -> Result<String> {
debug!("get_genesis_blockhash rpc request received");
Ok(meta.genesis_blockhash.to_string())
}
fn get_leader_schedule(&self, meta: Self::Metadata) -> Result<Option<Vec<String>>> { fn get_leader_schedule(&self, meta: Self::Metadata) -> Result<Option<Vec<String>>> {
let bank = meta.request_processor.read().unwrap().bank(); let bank = meta.request_processor.read().unwrap().bank();
Ok( Ok(
@ -718,6 +728,7 @@ pub mod tests {
let meta = Meta { let meta = Meta {
request_processor, request_processor,
cluster_info, cluster_info,
genesis_blockhash: Hash::default(),
}; };
(io, meta, bank, blockhash, alice, leader_pubkey) (io, meta, bank, blockhash, alice, leader_pubkey)
} }
@ -1060,6 +1071,7 @@ pub mod tests {
cluster_info: Arc::new(RwLock::new(ClusterInfo::new_with_invalid_keypair( cluster_info: Arc::new(RwLock::new(ClusterInfo::new_with_invalid_keypair(
ContactInfo::default(), ContactInfo::default(),
))), ))),
genesis_blockhash: Hash::default(),
}; };
let req = let req =

View File

@ -12,6 +12,7 @@ use jsonrpc_http_server::{
hyper, AccessControlAllowOrigin, DomainsValidation, RequestMiddleware, RequestMiddlewareAction, hyper, AccessControlAllowOrigin, DomainsValidation, RequestMiddleware, RequestMiddlewareAction,
ServerBuilder, ServerBuilder,
}; };
use solana_sdk::hash::Hash;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
@ -91,6 +92,7 @@ impl JsonRpcService {
config: JsonRpcConfig, config: JsonRpcConfig,
bank_forks: Arc<RwLock<BankForks>>, bank_forks: Arc<RwLock<BankForks>>,
ledger_path: &Path, ledger_path: &Path,
genesis_blockhash: Hash,
validator_exit: &Arc<RwLock<Option<ValidatorExit>>>, validator_exit: &Arc<RwLock<Option<ValidatorExit>>>,
) -> Self { ) -> Self {
info!("rpc bound to {:?}", rpc_addr); info!("rpc bound to {:?}", rpc_addr);
@ -118,6 +120,7 @@ impl JsonRpcService {
ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request<hyper::Body>| Meta { ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request<hyper::Body>| Meta {
request_processor: request_processor_.clone(), request_processor: request_processor_.clone(),
cluster_info: cluster_info.clone(), cluster_info: cluster_info.clone(),
genesis_blockhash
}).threads(4) }).threads(4)
.cors(DomainsValidation::AllowOnly(vec![ .cors(DomainsValidation::AllowOnly(vec![
AccessControlAllowOrigin::Any, AccessControlAllowOrigin::Any,
@ -201,6 +204,7 @@ mod tests {
JsonRpcConfig::default(), JsonRpcConfig::default(),
bank_forks, bank_forks,
&PathBuf::from("farf"), &PathBuf::from("farf"),
Hash::default(),
&validator_exit, &validator_exit,
); );
let thread = rpc_service.thread_hdl.thread(); let thread = rpc_service.thread_hdl.thread();

View File

@ -22,6 +22,7 @@ use crate::tpu::Tpu;
use crate::tvu::{Sockets, Tvu}; use crate::tvu::{Sockets, Tvu};
use solana_metrics::datapoint_info; use solana_metrics::datapoint_info;
use solana_sdk::genesis_block::GenesisBlock; use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash;
use solana_sdk::poh_config::PohConfig; use solana_sdk::poh_config::PohConfig;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Keypair, KeypairUtil};
@ -38,6 +39,7 @@ use std::thread::Result;
pub struct ValidatorConfig { pub struct ValidatorConfig {
pub dev_sigverify_disabled: bool, pub dev_sigverify_disabled: bool,
pub dev_halt_at_slot: Option<Slot>, pub dev_halt_at_slot: Option<Slot>,
pub expected_genesis_blockhash: Option<Hash>,
pub voting_disabled: bool, pub voting_disabled: bool,
pub blockstream_unix_socket: Option<PathBuf>, pub blockstream_unix_socket: Option<PathBuf>,
pub storage_slots_per_turn: u64, pub storage_slots_per_turn: u64,
@ -54,6 +56,7 @@ impl Default for ValidatorConfig {
Self { Self {
dev_sigverify_disabled: false, dev_sigverify_disabled: false,
dev_halt_at_slot: None, dev_halt_at_slot: None,
expected_genesis_blockhash: None,
voting_disabled: false, voting_disabled: false,
blockstream_unix_socket: None, blockstream_unix_socket: None,
storage_slots_per_turn: DEFAULT_SLOTS_PER_TURN, storage_slots_per_turn: DEFAULT_SLOTS_PER_TURN,
@ -136,6 +139,7 @@ impl Validator {
info!("creating bank..."); info!("creating bank...");
let ( let (
genesis_blockhash,
bank_forks, bank_forks,
bank_forks_info, bank_forks_info,
blocktree, blocktree,
@ -144,6 +148,7 @@ impl Validator {
leader_schedule_cache, leader_schedule_cache,
poh_config, poh_config,
) = new_banks_from_blocktree( ) = new_banks_from_blocktree(
config.expected_genesis_blockhash,
ledger_path, ledger_path,
config.account_paths.clone(), config.account_paths.clone(),
config.snapshot_config.clone(), config.snapshot_config.clone(),
@ -184,6 +189,7 @@ impl Validator {
config.rpc_config.clone(), config.rpc_config.clone(),
bank_forks.clone(), bank_forks.clone(),
ledger_path, ledger_path,
genesis_blockhash,
&validator_exit, &validator_exit,
)) ))
}; };
@ -471,12 +477,14 @@ fn adjust_ulimit_nofile() {
} }
pub fn new_banks_from_blocktree( pub fn new_banks_from_blocktree(
expected_genesis_blockhash: Option<Hash>,
blocktree_path: &Path, blocktree_path: &Path,
account_paths: Option<String>, account_paths: Option<String>,
snapshot_config: Option<SnapshotConfig>, snapshot_config: Option<SnapshotConfig>,
verify_ledger: bool, verify_ledger: bool,
dev_halt_at_slot: Option<Slot>, dev_halt_at_slot: Option<Slot>,
) -> ( ) -> (
Hash,
BankForks, BankForks,
Vec<BankForksInfo>, Vec<BankForksInfo>,
Blocktree, Blocktree,
@ -486,6 +494,16 @@ pub fn new_banks_from_blocktree(
PohConfig, PohConfig,
) { ) {
let genesis_block = GenesisBlock::load(blocktree_path).expect("Failed to load genesis block"); let genesis_block = GenesisBlock::load(blocktree_path).expect("Failed to load genesis block");
let genesis_blockhash = genesis_block.hash();
if let Some(expected_genesis_blockhash) = expected_genesis_blockhash {
if genesis_blockhash != expected_genesis_blockhash {
panic!(
"Genesis blockhash mismatch: expected {} but local genesis blockhash is {}",
expected_genesis_blockhash, genesis_blockhash,
);
}
}
adjust_ulimit_nofile(); adjust_ulimit_nofile();
@ -502,6 +520,7 @@ pub fn new_banks_from_blocktree(
); );
( (
genesis_blockhash,
bank_forks, bank_forks,
bank_forks_info, bank_forks_info,
blocktree, blocktree,

View File

@ -91,6 +91,7 @@ fn test_replay() {
let tvu_addr = target1.info.tvu; let tvu_addr = target1.info.tvu;
let ( let (
_genesis_blockhash,
bank_forks, bank_forks,
_bank_forks_info, _bank_forks_info,
blocktree, blocktree,
@ -98,7 +99,7 @@ fn test_replay() {
completed_slots_receiver, completed_slots_receiver,
leader_schedule_cache, leader_schedule_cache,
_, _,
) = validator::new_banks_from_blocktree(&blocktree_path, None, None, true, None); ) = validator::new_banks_from_blocktree(None, &blocktree_path, None, None, true, None);
let working_bank = bank_forks.working_bank(); let working_bank = bank_forks.working_bank();
assert_eq!( assert_eq!(
working_bank.get_balance(&mint_keypair.pubkey()), working_bank.get_balance(&mint_keypair.pubkey()),

View File

@ -272,14 +272,6 @@ setup_validator_accounts() {
while true; do while true; do
rpc_url=$($solana_gossip get-rpc-url --entrypoint "$gossip_entrypoint") rpc_url=$($solana_gossip get-rpc-url --entrypoint "$gossip_entrypoint")
if new_genesis_block; then
# If the genesis block has changed remove the now stale ledger and start all
# over again
(
set -x
rm -rf "$ledger_dir"
)
fi
[[ -r "$identity_keypair_path" ]] || $solana_keygen new -o "$identity_keypair_path" [[ -r "$identity_keypair_path" ]] || $solana_keygen new -o "$identity_keypair_path"
[[ -r "$voting_keypair_path" ]] || $solana_keygen new -o "$voting_keypair_path" [[ -r "$voting_keypair_path" ]] || $solana_keygen new -o "$voting_keypair_path"
@ -312,7 +304,7 @@ EOF
exit $? exit $?
fi fi
secs_to_next_genesis_poll=5 secs_to_next_genesis_poll=60
while true; do while true; do
if [[ -z $pid ]] || ! kill -0 "$pid"; then if [[ -z $pid ]] || ! kill -0 "$pid"; then
[[ -z $pid ]] || wait "$pid" [[ -z $pid ]] || wait "$pid"
@ -326,9 +318,13 @@ EOF
echo "Polling for new genesis block..." echo "Polling for new genesis block..."
if new_genesis_block; then if new_genesis_block; then
echo "############## New genesis detected, restarting ##############" echo "############## New genesis detected, restarting ##############"
(
set -x
rm -rf "$ledger_dir"
)
break break
fi fi
secs_to_next_genesis_poll=5 secs_to_next_genesis_poll=60
fi fi
done done

View File

@ -14,6 +14,7 @@ clap = "2.33.0"
log = "0.4.8" log = "0.4.8"
reqwest = "0.9.19" reqwest = "0.9.19"
serde_json = "1.0.40" serde_json = "1.0.40"
solana-client = { path = "../client", version = "0.18.0-pre2" }
solana-core = { path = "../core", version = "0.18.0-pre2" } solana-core = { path = "../core", version = "0.18.0-pre2" }
solana-drone = { path = "../drone", version = "0.18.0-pre2" } solana-drone = { path = "../drone", version = "0.18.0-pre2" }
solana-logger = { path = "../logger", version = "0.18.0-pre2" } solana-logger = { path = "../logger", version = "0.18.0-pre2" }

View File

@ -1,6 +1,7 @@
use bzip2::bufread::BzDecoder; use bzip2::bufread::BzDecoder;
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg}; use clap::{crate_description, crate_name, crate_version, value_t, App, Arg};
use log::*; use log::*;
use solana_client::rpc_client::RpcClient;
use solana_core::bank_forks::SnapshotConfig; use solana_core::bank_forks::SnapshotConfig;
use solana_core::cluster_info::{Node, FULLNODE_PORT_RANGE}; use solana_core::cluster_info::{Node, FULLNODE_PORT_RANGE};
use solana_core::contact_info::ContactInfo; use solana_core::contact_info::ContactInfo;
@ -11,6 +12,7 @@ use solana_core::service::Service;
use solana_core::socketaddr; use solana_core::socketaddr;
use solana_core::validator::{Validator, ValidatorConfig}; use solana_core::validator::{Validator, ValidatorConfig};
use solana_netutil::parse_port_range; use solana_netutil::parse_port_range;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
use solana_sdk::timing::Slot; use solana_sdk::timing::Slot;
use std::fs::{self, File}; use std::fs::{self, File};
@ -87,7 +89,7 @@ fn initialize_ledger_path(
gossip_addr: &SocketAddr, gossip_addr: &SocketAddr,
ledger_path: &Path, ledger_path: &Path,
no_snapshot_fetch: bool, no_snapshot_fetch: bool,
) -> Result<(), String> { ) -> Result<Hash, String> {
let (nodes, _replicators) = discover( let (nodes, _replicators) = discover(
&entrypoint.gossip, &entrypoint.gossip,
Some(1), Some(1),
@ -110,6 +112,10 @@ fn initialize_ledger_path(
exit(1); exit(1);
}); });
let genesis_blockhash = RpcClient::new_socket(rpc_addr)
.get_genesis_blockhash()
.map_err(|err| err.to_string())?;
fs::create_dir_all(ledger_path).map_err(|err| err.to_string())?; fs::create_dir_all(ledger_path).map_err(|err| err.to_string())?;
download_archive(&rpc_addr, "genesis.tar.bz2", ledger_path, true)?; download_archive(&rpc_addr, "genesis.tar.bz2", ledger_path, true)?;
@ -119,7 +125,7 @@ fn initialize_ledger_path(
.unwrap_or_else(|err| eprintln!("Warning: Unable to fetch snapshot: {:?}", err)); .unwrap_or_else(|err| eprintln!("Warning: Unable to fetch snapshot: {:?}", err));
} }
Ok(()) Ok(genesis_blockhash)
} }
fn main() { fn main() {
@ -393,7 +399,7 @@ fn main() {
.map(PathBuf::from); .map(PathBuf::from);
if let Some(ref entrypoint_addr) = cluster_entrypoint { if let Some(ref entrypoint_addr) = cluster_entrypoint {
initialize_ledger_path( let expected_genesis_blockhash = initialize_ledger_path(
entrypoint_addr, entrypoint_addr,
&gossip_addr, &gossip_addr,
&ledger_path, &ledger_path,
@ -403,6 +409,7 @@ fn main() {
eprintln!("Failed to download ledger: {}", err); eprintln!("Failed to download ledger: {}", err);
exit(1); exit(1);
}); });
validator_config.expected_genesis_blockhash = Some(expected_genesis_blockhash);
} else { } else {
// Without a cluster entrypoint, ledger_path must already be present // Without a cluster entrypoint, ledger_path must already be present
if !ledger_path.is_dir() { if !ledger_path.is_dir() {