stable futures fixes (#1124)

* Fix eth1 update functions

* Fix genesis and client

* Fix beacon node lib

* Return appropriate runtimes from environment

* Fix test rig

* Refactor eth1 service update
This commit is contained in:
Pawan Dhananjay
2020-05-10 15:25:06 +05:30
committed by GitHub
parent 4617db64fa
commit addde163c4
9 changed files with 223 additions and 228 deletions

View File

@@ -285,8 +285,10 @@ impl<T: EthSpec, S: Store<T>> CachingEth1Backend<T, S> {
} }
/// Starts the routine which connects to the external eth1 node and updates the caches. /// Starts the routine which connects to the external eth1 node and updates the caches.
fn start(&self, exit: tokio::sync::oneshot::Receiver<()>) { pub fn start(&self, exit: tokio::sync::oneshot::Receiver<()>) {
tokio::spawn(HttpService::auto_update(self.core.clone(), exit)); // don't need to spawn as a task is being spawned in auto_update
// TODO: check if this is correct
HttpService::auto_update(self.core.clone(), exit);
} }
/// Instantiates `self` from an existing service. /// Instantiates `self` from an existing service.

View File

@@ -113,27 +113,31 @@ where
client_genesis: ClientGenesis, client_genesis: ClientGenesis,
config: ClientConfig, config: ClientConfig,
) -> Result<Self, String> { ) -> Result<Self, String> {
let store = self let store = self.store.clone();
.store let store_migrator = self.store_migrator.take();
let chain_spec = self.chain_spec.clone();
let runtime_context = self.runtime_context.clone();
let eth_spec_instance = self.eth_spec_instance.clone();
let data_dir = config.data_dir.clone();
let disabled_forks = config.disabled_forks.clone();
let store = store
.ok_or_else(|| "beacon_chain_start_method requires a store".to_string())?; .ok_or_else(|| "beacon_chain_start_method requires a store".to_string())?;
let store_migrator = self let store_migrator = store_migrator
.store_migrator
.ok_or_else(|| "beacon_chain_start_method requires a store migrator".to_string())?; .ok_or_else(|| "beacon_chain_start_method requires a store migrator".to_string())?;
let context = self let context = runtime_context
.runtime_context
.ok_or_else(|| "beacon_chain_start_method requires a runtime context".to_string())? .ok_or_else(|| "beacon_chain_start_method requires a runtime context".to_string())?
.service_context("beacon".into()); .service_context("beacon".into());
let spec = self let spec = chain_spec
.chain_spec
.ok_or_else(|| "beacon_chain_start_method requires a chain spec".to_string())?; .ok_or_else(|| "beacon_chain_start_method requires a chain spec".to_string())?;
let builder = BeaconChainBuilder::new(self.eth_spec_instance) let builder = BeaconChainBuilder::new(eth_spec_instance)
.logger(context.log.clone()) .logger(context.log.clone())
.store(store) .store(store)
.store_migrator(store_migrator) .store_migrator(store_migrator)
.data_dir(config.data_dir) .data_dir(data_dir)
.custom_spec(spec.clone()) .custom_spec(spec.clone())
.disabled_forks(config.disabled_forks); .disabled_forks(disabled_forks);
let chain_exists = builder let chain_exists = builder
.store_contains_beacon_chain() .store_contains_beacon_chain()
@@ -286,6 +290,7 @@ where
network_chan: network_send, network_chan: network_send,
}; };
let log = context.log.clone();
let (exit_channel, listening_addr) = context.runtime_handle.enter(|| { let (exit_channel, listening_addr) = context.runtime_handle.enter(|| {
rest_api::start_server( rest_api::start_server(
&client_config.rest_api, &client_config.rest_api,
@@ -298,7 +303,7 @@ where
.create_freezer_db_path() .create_freezer_db_path()
.map_err(|_| "unable to read freezer DB dir")?, .map_err(|_| "unable to read freezer DB dir")?,
eth2_config.clone(), eth2_config.clone(),
context.log, log,
) )
.map_err(|e| format!("Failed to start HTTP API: {:?}", e)) .map_err(|e| format!("Failed to start HTTP API: {:?}", e))
})?; })?;

View File

@@ -43,7 +43,7 @@ pub fn spawn_notifier<T: BeaconChainTypes>(
let interval_duration = slot_duration; let interval_duration = slot_duration;
let speedo = Mutex::new(Speedo::default()); let speedo = Mutex::new(Speedo::default());
let interval = tokio::time::interval_at(start_instant, interval_duration); let mut interval = tokio::time::interval_at(start_instant, interval_duration);
let interval_future = async move { let interval_future = async move {
while let Some(_) = interval.next().await { while let Some(_) = interval.next().await {

View File

@@ -2,18 +2,18 @@ use crate::metrics;
use crate::{ use crate::{
block_cache::{BlockCache, Error as BlockCacheError, Eth1Block}, block_cache::{BlockCache, Error as BlockCacheError, Eth1Block},
deposit_cache::Error as DepositCacheError, deposit_cache::Error as DepositCacheError,
http::{get_block, get_block_number, get_deposit_logs_in_range}, http::{get_block, get_block_number, get_deposit_logs_in_range, Log},
inner::{DepositUpdater, Inner}, inner::{DepositUpdater, Inner},
DepositLog, DepositLog,
}; };
use futures::{future::TryFutureExt, stream, stream::TryStreamExt}; use futures::{future::TryFutureExt, stream, stream::TryStreamExt, StreamExt};
use parking_lot::{RwLock, RwLockReadGuard}; use parking_lot::{RwLock, RwLockReadGuard};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slog::{debug, error, info, trace, Logger}; use slog::{debug, error, info, trace, Logger};
use std::ops::{Range, RangeInclusive}; use std::ops::{Range, RangeInclusive};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use tokio::time::delay_for; use tokio::time::{interval_at, Duration, Instant};
const STANDARD_TIMEOUT_MILLIS: u64 = 15_000; const STANDARD_TIMEOUT_MILLIS: u64 = 15_000;
@@ -283,13 +283,22 @@ impl Service {
/// - Err(_) if there is an error. /// - Err(_) if there is an error.
/// ///
/// Emits logs for debugging and errors. /// Emits logs for debugging and errors.
pub async fn auto_update(service: Self, mut exit: tokio::sync::oneshot::Receiver<()>) { pub fn auto_update(service: Self, exit: tokio::sync::oneshot::Receiver<()>) {
let update_interval = Duration::from_millis(service.config().auto_update_interval_millis); let update_interval = Duration::from_millis(service.config().auto_update_interval_millis);
loop { let interval = interval_at(Instant::now(), update_interval);
let update_future = async {
let update_result = Service::update(service.clone()).await;
let update_future = interval.for_each(move |_| {
let _ = Service::do_update(service.clone(), update_interval);
futures::future::ready(())
});
let future = futures::future::select(update_future, exit);
tokio::task::spawn(future);
}
async fn do_update(service: Self, update_interval: Duration) -> Result<(), ()> {
let update_result = Service::update(service.clone()).await;
println!("Going on");
match update_result { match update_result {
Err(e) => error!( Err(e) => error!(
service.log, service.log,
@@ -305,16 +314,7 @@ impl Service {
"deposits" => format!("{:?}", deposit), "deposits" => format!("{:?}", deposit),
), ),
}; };
// WARNING: delay_for doesn't return an error and panics on error. Ok(())
delay_for(update_interval).await;
};
if let futures::future::Either::Right(_) =
futures::future::select(Box::pin(update_future), &mut exit).await
{
// the exit future returned end
break;
}
}
} }
/// Contacts the remote eth1 node and attempts to import deposit logs up to the configured /// Contacts the remote eth1 node and attempts to import deposit logs up to the configured
@@ -365,7 +365,7 @@ impl Service {
Vec::new() Vec::new()
}; };
let logs_imported = let logs: Vec<(Range<u64>, Vec<Log>)> =
stream::try_unfold(block_number_chunks.into_iter(), |mut chunks| async { stream::try_unfold(block_number_chunks.into_iter(), |mut chunks| async {
match chunks.next() { match chunks.next() {
Some(chunk) => { Some(chunk) => {
@@ -385,17 +385,18 @@ impl Service {
None => Ok(None), None => Ok(None),
} }
}) })
.try_fold(0, |mut sum: usize, (block_range, log_chunk)| async move { .try_collect()
let mut cache = service.deposits().write(); .await?;
let mut logs_imported = 0;
for (block_range, log_chunk) in logs.iter() {
let mut cache = service.deposits().write();
log_chunk log_chunk
.into_iter() .into_iter()
.map(|raw_log| { .map(|raw_log| {
DepositLog::from_log(&raw_log).map_err(|error| { DepositLog::from_log(&raw_log).map_err(|error| Error::FailedToParseDepositLog {
Error::FailedToParseDepositLog {
block_range: block_range.clone(), block_range: block_range.clone(),
error, error,
}
}) })
}) })
// Return early if any of the logs cannot be parsed. // Return early if any of the logs cannot be parsed.
@@ -410,7 +411,7 @@ impl Service {
.insert_log(deposit_log) .insert_log(deposit_log)
.map_err(Error::FailedToInsertDeposit)?; .map_err(Error::FailedToInsertDeposit)?;
sum += 1; logs_imported += 1;
Ok(()) Ok(())
}) })
@@ -429,10 +430,7 @@ impl Service {
&metrics::HIGHEST_PROCESSED_DEPOSIT_BLOCK, &metrics::HIGHEST_PROCESSED_DEPOSIT_BLOCK,
cache.last_processed_block.unwrap_or_else(|| 0) as i64, cache.last_processed_block.unwrap_or_else(|| 0) as i64,
); );
}
Ok(sum)
})
.await?;
if logs_imported > 0 { if logs_imported > 0 {
info!( info!(
@@ -531,9 +529,9 @@ impl Service {
// Produce a stream from the list of required block numbers and return a future that // Produce a stream from the list of required block numbers and return a future that
// consumes the it. // consumes the it.
let eth1_blocks = stream::try_unfold( let eth1_blocks: Vec<Eth1Block> = stream::try_unfold(
required_block_numbers.into_iter(), required_block_numbers.into_iter(),
|mut block_numbers| async move { |mut block_numbers| async {
match block_numbers.next() { match block_numbers.next() {
Some(block_number) => { Some(block_number) => {
match download_eth1_block(service.inner.clone(), block_number).await { match download_eth1_block(service.inner.clone(), block_number).await {
@@ -544,10 +542,12 @@ impl Service {
None => Ok(None), None => Ok(None),
} }
}, },
); )
.try_collect()
.await?;
let blocks_imported = eth1_blocks let mut blocks_imported = 0;
.try_fold(0, |sum: usize, eth1_block| async move { for eth1_block in eth1_blocks {
service service
.inner .inner
.block_cache .block_cache
@@ -569,9 +569,8 @@ impl Service {
.unwrap_or_else(|| 0) as i64, .unwrap_or_else(|| 0) as i64,
); );
Ok(sum + 1) blocks_imported += 1;
}) }
.await?;
// Prune the block cache, preventing it from growing too large. // Prune the block cache, preventing it from growing too large.
service.inner.prune_blocks(); service.inner.prune_blocks();

View File

@@ -143,17 +143,14 @@ mod eth1_cache {
eth1.ganache.evm_mine().await.expect("should mine block"); eth1.ganache.evm_mine().await.expect("should mine block");
} }
service Service::update_deposit_cache(service.clone())
.update_deposit_cache()
.await .await
.expect("should update deposit cache"); .expect("should update deposit cache");
service Service::update_block_cache(service.clone())
.update_block_cache()
.await .await
.expect("should update block cache"); .expect("should update block cache");
service Service::update_block_cache(service.clone())
.update_block_cache()
.await .await
.expect("should update cache when nothing has changed"); .expect("should update cache when nothing has changed");
@@ -205,12 +202,10 @@ mod eth1_cache {
eth1.ganache.evm_mine().await.expect("should mine block") eth1.ganache.evm_mine().await.expect("should mine block")
} }
service Service::update_deposit_cache(service.clone())
.update_deposit_cache()
.await .await
.expect("should update deposit cache"); .expect("should update deposit cache");
service Service::update_block_cache(service.clone())
.update_block_cache()
.await .await
.expect("should update block cache"); .expect("should update block cache");
@@ -251,12 +246,10 @@ mod eth1_cache {
for _ in 0..cache_len / 2 { for _ in 0..cache_len / 2 {
eth1.ganache.evm_mine().await.expect("should mine block") eth1.ganache.evm_mine().await.expect("should mine block")
} }
service Service::update_deposit_cache(service.clone())
.update_deposit_cache()
.await .await
.expect("should update deposit cache"); .expect("should update deposit cache");
service Service::update_block_cache(service.clone())
.update_block_cache()
.await .await
.expect("should update block cache"); .expect("should update block cache");
} }
@@ -295,11 +288,14 @@ mod eth1_cache {
eth1.ganache.evm_mine().await.expect("should mine block") eth1.ganache.evm_mine().await.expect("should mine block")
} }
futures::try_join!( futures::try_join!(
service.update_deposit_cache(), Service::update_deposit_cache(service.clone()),
service.update_deposit_cache() Service::update_deposit_cache(service.clone())
) )
.expect("should perform two simultaneous updates of deposit cache"); .expect("should perform two simultaneous updates of deposit cache");
futures::try_join!(service.update_block_cache(), service.update_block_cache()) futures::try_join!(
Service::update_block_cache(service.clone()),
Service::update_block_cache(service.clone())
)
.expect("should perform two simultaneous updates of block cache"); .expect("should perform two simultaneous updates of block cache");
assert!(service.block_cache_len() >= n, "should grow the cache"); assert!(service.block_cache_len() >= n, "should grow the cache");
@@ -344,13 +340,11 @@ mod deposit_tree {
.expect("should perform a deposit"); .expect("should perform a deposit");
} }
service Service::update_deposit_cache(service.clone())
.update_deposit_cache()
.await .await
.expect("should perform update"); .expect("should perform update");
service Service::update_deposit_cache(service.clone())
.update_deposit_cache()
.await .await
.expect("should perform update when nothing has changed"); .expect("should perform update when nothing has changed");
@@ -419,8 +413,8 @@ mod deposit_tree {
} }
futures::try_join!( futures::try_join!(
service.update_deposit_cache(), Service::update_deposit_cache(service.clone()),
service.update_deposit_cache() Service::update_deposit_cache(service.clone())
) )
.expect("should perform two updates concurrently"); .expect("should perform two updates concurrently");
@@ -657,8 +651,7 @@ mod fast {
eth1.ganache.evm_mine().await.expect("should mine block"); eth1.ganache.evm_mine().await.expect("should mine block");
} }
service Service::update_deposit_cache(service.clone())
.update_deposit_cache()
.await .await
.expect("should perform update"); .expect("should perform update");
@@ -725,8 +718,7 @@ mod persist {
.expect("should perform a deposit"); .expect("should perform a deposit");
} }
service Service::update_deposit_cache(service.clone())
.update_deposit_cache()
.await .await
.expect("should perform update"); .expect("should perform update");
@@ -737,8 +729,7 @@ mod persist {
let deposit_count = service.deposit_cache_len(); let deposit_count = service.deposit_cache_len();
service Service::update_block_cache(service.clone())
.update_block_cache()
.await .await
.expect("should perform update"); .expect("should perform update");

View File

@@ -94,9 +94,7 @@ impl Eth1GenesisService {
loop { loop {
// **WARNING** `delay_for` panics on error // **WARNING** `delay_for` panics on error
delay_for(update_interval).await; delay_for(update_interval).await;
let update_result = self let update_result = Service::update_deposit_cache(self.core.clone())
.core
.update_deposit_cache()
.await .await
.map_err(|e| format!("{:?}", e)); .map_err(|e| format!("{:?}", e));
@@ -134,7 +132,7 @@ impl Eth1GenesisService {
let should_update_block_cache = *sync_blocks; let should_update_block_cache = *sync_blocks;
if should_update_block_cache { if should_update_block_cache {
let update_result = self.core.update_block_cache().await; let update_result = Service::update_block_cache(self.core.clone()).await;
if let Err(e) = update_result { if let Err(e) = update_result {
error!( error!(
log, log,

View File

@@ -18,7 +18,6 @@ use beacon_chain::{
use clap::ArgMatches; use clap::ArgMatches;
use config::get_config; use config::get_config;
use environment::RuntimeContext; use environment::RuntimeContext;
use futures::{Future, IntoFuture};
use slog::{info, warn}; use slog::{info, warn};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use types::EthSpec; use types::EthSpec;
@@ -51,27 +50,26 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
/// Identical to `start_from_client_config`, however the `client_config` is generated from the /// Identical to `start_from_client_config`, however the `client_config` is generated from the
/// given `matches` and potentially configuration files on the local filesystem or other /// given `matches` and potentially configuration files on the local filesystem or other
/// configurations hosted remotely. /// configurations hosted remotely.
pub fn new_from_cli<'a, 'b>( pub async fn new_from_cli<'a, 'b>(
context: RuntimeContext<E>, context: RuntimeContext<E>,
matches: &ArgMatches<'b>, matches: &ArgMatches<'b>,
) -> impl Future<Item = Self, Error = String> + 'a { ) -> Result<Self, String> {
get_config::<E>( let client_config = get_config::<E>(
&matches, &matches,
&context.eth2_config.spec_constants, &context.eth2_config.spec_constants,
&context.eth2_config().spec, &context.eth2_config().spec,
context.log.clone(), context.log.clone(),
) )?;
.into_future() Self::new(context, client_config).await
.and_then(move |client_config| Self::new(context, client_config))
} }
/// Starts a new beacon node `Client` in the given `environment`. /// Starts a new beacon node `Client` in the given `environment`.
/// ///
/// Client behaviour is defined by the given `client_config`. /// Client behaviour is defined by the given `client_config`.
pub fn new( pub async fn new(
context: RuntimeContext<E>, context: RuntimeContext<E>,
mut client_config: ClientConfig, mut client_config: ClientConfig,
) -> impl Future<Item = Self, Error = String> { ) -> Result<Self, String> {
let http_eth2_config = context.eth2_config().clone(); let http_eth2_config = context.eth2_config().clone();
let spec = context.eth2_config().spec.clone(); let spec = context.eth2_config().spec.clone();
let client_config_1 = client_config.clone(); let client_config_1 = client_config.clone();
@@ -79,22 +77,19 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
let store_config = client_config.store.clone(); let store_config = client_config.store.clone();
let log = context.log.clone(); let log = context.log.clone();
let db_path_res = client_config.create_db_path(); let db_path = client_config.create_db_path()?;
let freezer_db_path_res = client_config.create_freezer_db_path(); let freezer_db_path_res = client_config.create_freezer_db_path();
db_path_res let builder = ClientBuilder::new(context.eth_spec_instance.clone())
.into_future()
.and_then(move |db_path| {
Ok(ClientBuilder::new(context.eth_spec_instance.clone())
.runtime_context(context) .runtime_context(context)
.chain_spec(spec) .chain_spec(spec)
.disk_store(&db_path, &freezer_db_path_res?, store_config)? .disk_store(&db_path, &freezer_db_path_res?, store_config)?
.background_migrator()?) .background_migrator()?;
})
.and_then(move |builder| builder.beacon_chain_builder(client_genesis, client_config_1)) let builder = builder
.and_then(move |builder| { .beacon_chain_builder(client_genesis, client_config_1)
let builder = if client_config.sync_eth1_chain && !client_config.dummy_eth1_backend .await?;
{ let builder = if client_config.sync_eth1_chain && !client_config.dummy_eth1_backend {
info!( info!(
log, log,
"Block production enabled"; "Block production enabled";
@@ -132,7 +127,6 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
}; };
Ok(Self(builder.build())) Ok(Self(builder.build()))
})
} }
pub fn into_inner(self) -> ProductionClient<E> { pub fn into_inner(self) -> ProductionClient<E> {

View File

@@ -75,8 +75,13 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
/// ///
/// The `Runtime` used is just the standard tokio runtime. /// The `Runtime` used is just the standard tokio runtime.
pub fn multi_threaded_tokio_runtime(mut self) -> Result<Self, String> { pub fn multi_threaded_tokio_runtime(mut self) -> Result<Self, String> {
self.runtime = self.runtime = Some(
Some(Runtime::new().map_err(|e| format!("Failed to start runtime: {:?}", e))?); RuntimeBuilder::new()
.threaded_scheduler()
.enable_all()
.build()
.map_err(|e| format!("Failed to start runtime: {:?}", e))?,
);
Ok(self) Ok(self)
} }
@@ -87,7 +92,8 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
pub fn single_thread_tokio_runtime(mut self) -> Result<Self, String> { pub fn single_thread_tokio_runtime(mut self) -> Result<Self, String> {
self.runtime = Some( self.runtime = Some(
RuntimeBuilder::new() RuntimeBuilder::new()
.core_threads(1) .basic_scheduler()
.enable_all()
.build() .build()
.map_err(|e| format!("Failed to start runtime: {:?}", e))?, .map_err(|e| format!("Failed to start runtime: {:?}", e))?,
); );

View File

@@ -29,10 +29,10 @@ impl<E: EthSpec> LocalBeaconNode<E> {
/// Starts a new, production beacon node on the tokio runtime in the given `context`. /// Starts a new, production beacon node on the tokio runtime in the given `context`.
/// ///
/// The node created is using the same types as the node we use in production. /// The node created is using the same types as the node we use in production.
pub fn production( pub async fn production(
context: RuntimeContext<E>, context: RuntimeContext<E>,
mut client_config: ClientConfig, mut client_config: ClientConfig,
) -> impl Future<Item = Self, Error = String> { ) -> Result<Self, String> {
// Creates a temporary directory that will be deleted once this `TempDir` is dropped. // Creates a temporary directory that will be deleted once this `TempDir` is dropped.
let datadir = TempDir::new("lighthouse_node_test_rig") let datadir = TempDir::new("lighthouse_node_test_rig")
.expect("should create temp directory for client datadir"); .expect("should create temp directory for client datadir");
@@ -40,7 +40,7 @@ impl<E: EthSpec> LocalBeaconNode<E> {
client_config.data_dir = datadir.path().into(); client_config.data_dir = datadir.path().into();
client_config.network.network_dir = PathBuf::from(datadir.path()).join("network"); client_config.network.network_dir = PathBuf::from(datadir.path()).join("network");
ProductionBeaconNode::new(context, client_config).map(move |client| Self { ProductionBeaconNode::new(context, client_config).await.map(move |client| Self {
client: client.into_inner(), client: client.into_inner(),
datadir, datadir,
}) })
@@ -105,43 +105,43 @@ impl<E: EthSpec> LocalValidatorClient<E> {
/// are created in a temp dir then removed when the process exits. /// are created in a temp dir then removed when the process exits.
/// ///
/// The validator created is using the same types as the node we use in production. /// The validator created is using the same types as the node we use in production.
pub fn production_with_insecure_keypairs( pub async fn production_with_insecure_keypairs(
context: RuntimeContext<E>, context: RuntimeContext<E>,
mut config: ValidatorConfig, mut config: ValidatorConfig,
keypair_indices: &[usize], keypair_indices: &[usize],
) -> impl Future<Item = Self, Error = String> { ) -> Result<Self, String> {
// Creates a temporary directory that will be deleted once this `TempDir` is dropped. // Creates a temporary directory that will be deleted once this `TempDir` is dropped.
let datadir = TempDir::new("lighthouse-beacon-node") let datadir = TempDir::new("lighthouse-beacon-node")
.expect("should create temp directory for client datadir"); .expect("should create temp directory for client datadir");
config.key_source = KeySource::InsecureKeypairs(keypair_indices.to_vec()); config.key_source = KeySource::InsecureKeypairs(keypair_indices.to_vec());
Self::new(context, config, datadir) Self::new(context, config, datadir).await
} }
/// Creates a validator client that attempts to read keys from the default data dir. /// Creates a validator client that attempts to read keys from the default data dir.
/// ///
/// - The validator created is using the same types as the node we use in production. /// - The validator created is using the same types as the node we use in production.
/// - It is recommended to use `production_with_insecure_keypairs` for testing. /// - It is recommended to use `production_with_insecure_keypairs` for testing.
pub fn production( pub async fn production(
context: RuntimeContext<E>, context: RuntimeContext<E>,
config: ValidatorConfig, config: ValidatorConfig,
) -> impl Future<Item = Self, Error = String> { ) -> Result<Self,String> {
// Creates a temporary directory that will be deleted once this `TempDir` is dropped. // Creates a temporary directory that will be deleted once this `TempDir` is dropped.
let datadir = TempDir::new("lighthouse-validator") let datadir = TempDir::new("lighthouse-validator")
.expect("should create temp directory for client datadir"); .expect("should create temp directory for client datadir");
Self::new(context, config, datadir) Self::new(context, config, datadir).await
} }
fn new( async fn new(
context: RuntimeContext<E>, context: RuntimeContext<E>,
mut config: ValidatorConfig, mut config: ValidatorConfig,
datadir: TempDir, datadir: TempDir,
) -> impl Future<Item = Self, Error = String> { ) -> Result<Self, String> {
config.data_dir = datadir.path().into(); config.data_dir = datadir.path().into();
ProductionValidatorClient::new(context, config).map(move |mut client| { ProductionValidatorClient::new(context, config).await.map(move |mut client| {
client client
.start_service() .start_service()
.expect("should start validator services"); .expect("should start validator services");