Remove solana-vote-signer (#14099)

This commit is contained in:
Michael Vines
2020-12-13 19:12:20 -08:00
committed by GitHub
parent deaa27fbdb
commit cdd3e7d856
14 changed files with 1 additions and 662 deletions

19
Cargo.lock generated
View File

@ -3893,7 +3893,6 @@ dependencies = [
"solana-transaction-status", "solana-transaction-status",
"solana-version", "solana-version",
"solana-vote-program", "solana-vote-program",
"solana-vote-signer",
"solana_rbpf", "solana_rbpf",
"tempfile", "tempfile",
"thiserror", "thiserror",
@ -4048,7 +4047,6 @@ dependencies = [
"solana-transaction-status", "solana-transaction-status",
"solana-version", "solana-version",
"solana-vote-program", "solana-vote-program",
"solana-vote-signer",
"spl-token", "spl-token",
"systemstat", "systemstat",
"tempfile", "tempfile",
@ -5171,23 +5169,6 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "solana-vote-signer"
version = "1.5.0"
dependencies = [
"clap",
"jsonrpc-core",
"jsonrpc-derive",
"jsonrpc-http-server",
"log 0.4.11",
"net2",
"serde_json",
"solana-clap-utils",
"solana-metrics",
"solana-sdk",
"solana-version",
]
[[package]] [[package]]
name = "solana-watchtower" name = "solana-watchtower"
version = "1.5.0" version = "1.5.0"

View File

@ -67,7 +67,6 @@ members = [
"upload-perf", "upload-perf",
"net-utils", "net-utils",
"version", "version",
"vote-signer",
"cli", "cli",
"rayon-threadlimit", "rayon-threadlimit",
"watchtower", "watchtower",

View File

@ -44,7 +44,6 @@ solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" } solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
solana-version = { path = "../version", version = "1.5.0" } solana-version = { path = "../version", version = "1.5.0" }
solana-vote-program = { path = "../programs/vote", version = "1.5.0" } solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
solana-vote-signer = { path = "../vote-signer", version = "1.5.0" }
thiserror = "1.0.21" thiserror = "1.0.21"
tiny-bip39 = "0.7.0" tiny-bip39 = "0.7.0"
url = "2.1.1" url = "2.1.1"

View File

@ -72,7 +72,6 @@ solana-sys-tuner = { path = "../sys-tuner", version = "1.5.0" }
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" } solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
solana-version = { path = "../version", version = "1.5.0" } solana-version = { path = "../version", version = "1.5.0" }
solana-vote-program = { path = "../programs/vote", version = "1.5.0" } solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
solana-vote-signer = { path = "../vote-signer", version = "1.5.0" }
spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] } spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] }
tempfile = "3.1.0" tempfile = "3.1.0"
thiserror = "1.0" thiserror = "1.0"

View File

@ -38,7 +38,6 @@ pub mod gen_keys;
pub mod gossip_service; pub mod gossip_service;
pub mod heaviest_subtree_fork_choice; pub mod heaviest_subtree_fork_choice;
pub mod ledger_cleanup_service; pub mod ledger_cleanup_service;
pub mod local_vote_signer_service;
pub mod non_circulating_supply; pub mod non_circulating_supply;
pub mod optimistic_confirmation_verifier; pub mod optimistic_confirmation_verifier;
pub mod optimistically_confirmed_bank_tracker; pub mod optimistically_confirmed_bank_tracker;

View File

@ -1,41 +0,0 @@
//! The `local_vote_signer_service` can be started locally to sign validator votes
use solana_net_utils::PortRange;
use solana_vote_signer::rpc::VoteSignerRpcService;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::{self, Builder, JoinHandle};
pub struct LocalVoteSignerService {
thread: JoinHandle<()>,
exit: Arc<AtomicBool>,
}
impl LocalVoteSignerService {
#[allow(clippy::new_ret_no_self)]
pub fn new(port_range: PortRange) -> (Self, SocketAddr) {
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
let addr = SocketAddr::new(
ip_addr,
solana_net_utils::find_available_port_in_range(ip_addr, port_range)
.expect("Failed to find an available port for local vote signer service"),
);
let exit = Arc::new(AtomicBool::new(false));
let thread_exit = exit.clone();
let thread = Builder::new()
.name("solana-vote-signer".to_string())
.spawn(move || {
let service = VoteSignerRpcService::new(addr, &thread_exit);
service.join().unwrap();
})
.unwrap();
(Self { thread, exit }, addr)
}
pub fn join(self) -> thread::Result<()> {
self.exit.store(true, Ordering::Relaxed);
self.thread.join()
}
}

View File

@ -109,9 +109,6 @@ while [[ -n $1 ]]; do
elif [[ $1 = --rpc-faucet-address ]]; then elif [[ $1 = --rpc-faucet-address ]]; then
args+=("$1" "$2") args+=("$1" "$2")
shift 2 shift 2
elif [[ $1 = --vote-signer-address ]]; then
args+=("$1" "$2")
shift 2
elif [[ $1 = --accounts ]]; then elif [[ $1 = --accounts ]]; then
args+=("$1" "$2") args+=("$1" "$2")
shift 2 shift 2

View File

@ -149,8 +149,8 @@ fn main() {
for stream in listener.incoming() { for stream in listener.incoming() {
if stream.is_ok() { if stream.is_ok() {
info!("Tuning the system now"); info!("Tuning the system now");
#[cfg(target_os = "linux")]
{ {
#![cfg(target_os = "linux")]
// tune_poh_service_priority(peer_uid); // tune_poh_service_priority(peer_uid);
} }
} }

View File

@ -997,15 +997,6 @@ pub fn main() {
.validator(solana_net_utils::is_host_port) .validator(solana_net_utils::is_host_port)
.help("Enable the JSON RPC 'requestAirdrop' API with this faucet address."), .help("Enable the JSON RPC 'requestAirdrop' API with this faucet address."),
) )
.arg(
Arg::with_name("signer_addr")
.long("vote-signer-address")
.value_name("HOST:PORT")
.takes_value(true)
.hidden(true) // Don't document this argument to discourage its use
.validator(solana_net_utils::is_host_port)
.help("Rendezvous with the vote signer at this RPC end point"),
)
.arg( .arg(
Arg::with_name("account_paths") Arg::with_name("account_paths")
.long("accounts") .long("accounts")
@ -1609,10 +1600,6 @@ pub fn main() {
validator_config.halt_on_trusted_validators_accounts_hash_mismatch = true; validator_config.halt_on_trusted_validators_accounts_hash_mismatch = true;
} }
if matches.value_of("signer_addr").is_some() {
warn!("--vote-signer-address ignored");
}
let entrypoint_addr = matches.value_of("entrypoint").map(|entrypoint| { let entrypoint_addr = matches.value_of("entrypoint").map(|entrypoint| {
solana_net_utils::parse_host_port(entrypoint).unwrap_or_else(|e| { solana_net_utils::parse_host_port(entrypoint).unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e); eprintln!("failed to parse entrypoint address: {}", e);

View File

@ -1,2 +0,0 @@
/target/
/farf/

View File

@ -1,33 +0,0 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-vote-signer"
description = "Solana Vote Signing Service"
version = "1.5.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33"
jsonrpc-core = "15.0.0"
jsonrpc-derive = "15.0.0"
jsonrpc-http-server = "15.0.0"
log = "0.4.11"
net2 = "0.2.37"
serde_json = "1.0.56"
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
solana-metrics = { path = "../metrics", version = "1.5.0" }
solana-sdk = { path = "../sdk", version = "1.5.0" }
solana-version = { path = "../version", version = "1.5.0" }
[lib]
crate-type = ["lib"]
name = "solana_vote_signer"
[[bin]]
name = "solana-vote-signer"
path = "src/bin/main.rs"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,40 +0,0 @@
use clap::{crate_description, crate_name, App, Arg};
use solana_vote_signer::rpc::VoteSignerRpcService;
use std::error;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
pub const RPC_PORT: u16 = 8989;
fn main() -> Result<(), Box<dyn error::Error>> {
solana_metrics::set_panic_hook("vote-signer");
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_version::version!())
.arg(
Arg::with_name("port")
.long("port")
.value_name("NUM")
.takes_value(true)
.help("JSON RPC listener port"),
)
.get_matches();
let port = if let Some(p) = matches.value_of("port") {
p.to_string()
.parse()
.expect("Failed to parse JSON RPC Port")
} else {
RPC_PORT
};
let exit = Arc::new(AtomicBool::new(false));
let service = VoteSignerRpcService::new(
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port),
&exit,
);
service.join().unwrap();
Ok(())
}

View File

@ -1,8 +0,0 @@
pub mod rpc;
#[macro_use]
extern crate log;
extern crate solana_sdk;
#[cfg(test)]
#[macro_use]
extern crate serde_json;

View File

@ -1,498 +0,0 @@
//! The `rpc` module implements the Vote signing service RPC interface.
use jsonrpc_core::{Error, MetaIoHandler, Metadata, Result};
use jsonrpc_derive::rpc;
use jsonrpc_http_server::{hyper, AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, Signature, Signer};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::{self, sleep, Builder, JoinHandle};
use std::time::Duration;
pub struct VoteSignerRpcService {
thread_hdl: JoinHandle<()>,
}
impl VoteSignerRpcService {
pub fn new(rpc_addr: SocketAddr, exit: &Arc<AtomicBool>) -> Self {
let request_processor = LocalVoteSigner::default();
let exit = exit.clone();
let thread_hdl = Builder::new()
.name("solana-vote-signer-jsonrpc".to_string())
.spawn(move || {
let mut io = MetaIoHandler::default();
let rpc = VoteSignerRpcImpl;
io.extend_with(rpc.to_delegate());
let server =
ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request<hyper::Body>| Meta {
request_processor: request_processor.clone(),
}).threads(4)
.cors(DomainsValidation::AllowOnly(vec![
AccessControlAllowOrigin::Any,
]))
.start_http(&rpc_addr);
if server.is_err() {
warn!("JSON RPC service unavailable: unable to bind to RPC port {}. \nMake sure this port is not already in use by another application", rpc_addr.port());
return;
}
while !exit.load(Ordering::Relaxed) {
sleep(Duration::from_millis(100));
}
server.unwrap().close();
})
.unwrap();
Self { thread_hdl }
}
pub fn join(self) -> thread::Result<()> {
self.thread_hdl.join()
}
}
#[derive(Clone)]
pub struct Meta {
pub request_processor: LocalVoteSigner,
}
impl Metadata for Meta {}
#[rpc(server)]
pub trait VoteSignerRpc {
type Metadata;
#[rpc(meta, name = "registerNode")]
fn register(&self, _: Self::Metadata, _: Pubkey, _: Signature, _: Vec<u8>) -> Result<Pubkey>;
#[rpc(meta, name = "signVote")]
fn sign(&self, _: Self::Metadata, _: Pubkey, _: Signature, _: Vec<u8>) -> Result<Signature>;
#[rpc(meta, name = "deregisterNode")]
fn deregister(&self, _: Self::Metadata, _: Pubkey, _: Signature, _: Vec<u8>) -> Result<()>;
}
pub struct VoteSignerRpcImpl;
impl VoteSignerRpc for VoteSignerRpcImpl {
type Metadata = Meta;
fn register(
&self,
meta: Self::Metadata,
id: Pubkey,
sig: Signature,
signed_msg: Vec<u8>,
) -> Result<Pubkey> {
info!("register rpc request received: {:?}", id);
meta.request_processor.register(&id, &sig, &signed_msg)
}
fn sign(
&self,
meta: Self::Metadata,
id: Pubkey,
sig: Signature,
signed_msg: Vec<u8>,
) -> Result<Signature> {
info!("sign rpc request received: {:?}", id);
meta.request_processor.sign(&id, &sig, &signed_msg)
}
fn deregister(
&self,
meta: Self::Metadata,
id: Pubkey,
sig: Signature,
signed_msg: Vec<u8>,
) -> Result<()> {
info!("deregister rpc request received: {:?}", id);
meta.request_processor.deregister(&id, &sig, &signed_msg)
}
}
fn verify_signature(sig: &Signature, pubkey: &Pubkey, msg: &[u8]) -> Result<()> {
if sig.verify(pubkey.as_ref(), msg) {
Ok(())
} else {
Err(Error::invalid_request())
}
}
pub trait VoteSigner {
fn register(&self, pubkey: &Pubkey, sig: &Signature, signed_msg: &[u8]) -> Result<Pubkey>;
fn sign(&self, pubkey: &Pubkey, sig: &Signature, msg: &[u8]) -> Result<Signature>;
fn deregister(&self, pubkey: &Pubkey, sig: &Signature, msg: &[u8]) -> Result<()>;
}
#[derive(Clone)]
pub struct LocalVoteSigner {
nodes: Arc<RwLock<HashMap<Pubkey, Keypair>>>,
}
impl VoteSigner for LocalVoteSigner {
/// Process JSON-RPC request items sent via JSON-RPC.
fn register(&self, pubkey: &Pubkey, sig: &Signature, msg: &[u8]) -> Result<Pubkey> {
verify_signature(&sig, &pubkey, &msg)?;
{
if let Some(voting_keypair) = self.nodes.read().unwrap().get(&pubkey) {
return Ok(voting_keypair.pubkey());
}
}
let voting_keypair = Keypair::new();
let voting_pubkey = voting_keypair.pubkey();
self.nodes.write().unwrap().insert(*pubkey, voting_keypair);
Ok(voting_pubkey)
}
fn sign(&self, pubkey: &Pubkey, sig: &Signature, msg: &[u8]) -> Result<Signature> {
verify_signature(&sig, &pubkey, &msg)?;
match self.nodes.read().unwrap().get(&pubkey) {
Some(voting_keypair) => Ok(voting_keypair.sign_message(&msg)),
None => Err(Error::invalid_request()),
}
}
fn deregister(&self, pubkey: &Pubkey, sig: &Signature, msg: &[u8]) -> Result<()> {
verify_signature(&sig, &pubkey, &msg)?;
self.nodes.write().unwrap().remove(&pubkey);
Ok(())
}
}
impl Default for LocalVoteSigner {
fn default() -> Self {
LocalVoteSigner {
nodes: Arc::new(RwLock::new(HashMap::new())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use jsonrpc_core::{types::*, Response};
use solana_sdk::signature::{Keypair, Signer};
use std::mem;
fn start_rpc_handler() -> (MetaIoHandler<Meta>, Meta) {
let request_processor = LocalVoteSigner::default();
let mut io = MetaIoHandler::default();
let rpc = VoteSignerRpcImpl;
io.extend_with(rpc.to_delegate());
let meta = Meta { request_processor };
(io, meta)
}
#[test]
fn test_rpc_register_node() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = node_keypair.sign_message(msg.as_bytes());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Success(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
assert_eq!(
succ.result.as_array().unwrap().len(),
mem::size_of::<Pubkey>()
);
let _pk: Pubkey = serde_json::from_value(succ.result).unwrap();
} else {
panic!();
}
} else {
panic!();
}
}
#[test]
fn test_rpc_register_node_invalid_sig() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let msg1 = "This is a Test1";
let sig = node_keypair.sign_message(msg.as_bytes());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey, sig, msg1.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
panic!();
}
} else {
panic!();
}
}
#[test]
fn test_rpc_deregister_node() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = node_keypair.sign_message(msg.as_bytes());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "deregisterNode",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Success(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
panic!();
}
} else {
panic!();
}
}
#[test]
fn test_rpc_deregister_node_invalid_sig() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let msg1 = "This is a Test1";
let sig = node_keypair.sign_message(msg.as_bytes());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "deregisterNode",
"params": [node_pubkey, sig, msg1.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
panic!();
}
} else {
panic!();
}
}
#[test]
fn test_rpc_sign_vote() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = node_keypair.sign_message(msg.as_bytes());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta.clone());
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let vote_pubkey;
if let Response::Single(out) = result {
if let Output::Success(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
assert_eq!(
succ.result.as_array().unwrap().len(),
mem::size_of::<Pubkey>()
);
vote_pubkey = serde_json::from_value(succ.result).unwrap();
} else {
panic!();
}
} else {
panic!();
}
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "signVote",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Success(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
assert_eq!(
succ.result.as_array().unwrap().len(),
mem::size_of::<Signature>()
);
let sig: Signature = serde_json::from_value(succ.result).unwrap();
assert_eq!(verify_signature(&sig, &vote_pubkey, msg.as_bytes()), Ok(()));
} else {
panic!();
}
} else {
panic!();
}
}
#[test]
fn test_rpc_sign_vote_before_register() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = node_keypair.sign_message(msg.as_bytes());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "signVote",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
panic!();
}
} else {
panic!();
}
}
#[test]
fn test_rpc_sign_vote_after_deregister() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = node_keypair.sign_message(msg.as_bytes());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let _res = io.handle_request_sync(&req.to_string(), meta.clone());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "deregisterNode",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let _res = io.handle_request_sync(&req.to_string(), meta.clone());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "signVote",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
panic!();
}
} else {
panic!();
}
}
#[test]
fn test_rpc_sign_vote_invalid_sig() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let msg1 = "This is a Test";
let sig = node_keypair.sign_message(msg.as_bytes());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey, sig, msg.as_bytes()],
});
let _res = io.handle_request_sync(&req.to_string(), meta.clone());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "signVote",
"params": [node_pubkey, sig, msg1.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
panic!();
}
} else {
panic!();
}
}
}