mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-19 05:48:31 +00:00
Stable futures (#879)
* Port eth1 lib to use stable futures * Port eth1_test_rig to stable futures * Port eth1 tests to stable futures * Port genesis service to stable futures * Port genesis tests to stable futures * Port beacon_chain to stable futures * Port lcli to stable futures * Fix eth1_test_rig (#1014) * Fix lcli * Port timer to stable futures * Fix timer * Port websocket_server to stable futures * Port notifier to stable futures * Add TODOS * Update hashmap hashset to stable futures * Adds panic test to hashset delay * Port remote_beacon_node to stable futures * Fix lcli merge conflicts * Non rpc stuff compiles * protocol.rs compiles * Port websockets, timer and notifier to stable futures (#1035) * Fix lcli * Port timer to stable futures * Fix timer * Port websocket_server to stable futures * Port notifier to stable futures * Add TODOS * Port remote_beacon_node to stable futures * Partial eth2-libp2p stable future upgrade * Finished first round of fighting RPC types * Further progress towards porting eth2-libp2p adds caching to discovery * Update behaviour * RPC handler to stable futures * Update RPC to master libp2p * Network service additions * Fix the fallback transport construction (#1102) * Correct warning * Remove hashmap delay * Compiling version of eth2-libp2p * Update all crates versions * Fix conversion function and add tests (#1113) * Port validator_client to stable futures (#1114) * Add PH & MS slot clock changes * Account for genesis time * Add progress on duties refactor * Add simple is_aggregator bool to val subscription * Start work on attestation_verification.rs * Add progress on ObservedAttestations * Progress with ObservedAttestations * Fix tests * Add observed attestations to the beacon chain * Add attestation observation to processing code * Add progress on attestation verification * Add first draft of ObservedAttesters * Add more tests * Add observed attesters to beacon chain * Add observers to attestation processing * Add more attestation verification * Create ObservedAggregators map * Remove commented-out code * Add observed aggregators into chain * Add progress * Finish adding features to attestation verification * Ensure beacon chain compiles * Link attn verification into chain * Integrate new attn verification in chain * Remove old attestation processing code * Start trying to fix beacon_chain tests * Split adding into pools into two functions * Add aggregation to harness * Get test harness working again * Adjust the number of aggregators for test harness * Fix edge-case in harness * Integrate new attn processing in network * Fix compile bug in validator_client * Update validator API endpoints * Fix aggreagation in test harness * Fix enum thing * Fix attestation observation bug: * Patch failing API tests * Start adding comments to attestation verification * Remove unused attestation field * Unify "is block known" logic * Update comments * Supress fork choice errors for network processing * Add todos * Tidy * Add gossip attn tests * Disallow test harness to produce old attns * Comment out in-progress tests * Partially address pruning tests * Fix failing store test * Add aggregate tests * Add comments about which spec conditions we check * Dont re-aggregate * Split apart test harness attn production * Fix compile error in network * Make progress on commented-out test * Fix skipping attestation test * Add fork choice verification tests * Tidy attn tests, remove dead code * Remove some accidentally added code * Fix clippy lint * Rename test file * Add block tests, add cheap block proposer check * Rename block testing file * Add observed_block_producers * Tidy * Switch around block signature verification * Finish block testing * Remove gossip from signature tests * First pass of self review * Fix deviation in spec * Update test spec tags * Start moving over to hashset * Finish moving observed attesters to hashmap * Move aggregation pool over to hashmap * Make fc attn borrow again * Fix rest_api compile error * Fix missing comments * Fix monster test * Uncomment increasing slots test * Address remaining comments * Remove unsafe, use cfg test * Remove cfg test flag * Fix dodgy comment * Revert "Update hashmap hashset to stable futures" This reverts commitd432378a3c. * Revert "Adds panic test to hashset delay" This reverts commit281502396f. * Ported attestation_service * Ported duties_service * Ported fork_service * More ports * Port block_service * Minor fixes * VC compiles * Update TODOS * Borrow self where possible * Ignore aggregates that are already known. * Unify aggregator modulo logic * Fix typo in logs * Refactor validator subscription logic * Avoid reproducing selection proof * Skip HTTP call if no subscriptions * Rename DutyAndState -> DutyAndProof * Tidy logs * Print root as dbg * Fix compile errors in tests * Fix compile error in test * Re-Fix attestation and duties service * Minor fixes Co-authored-by: Paul Hauner <paul@paulhauner.com> * Network crate update to stable futures * Port account_manager to stable futures (#1121) * Port account_manager to stable futures * Run async fns in tokio environment * Port rest_api crate to stable futures (#1118) * Port rest_api lib to stable futures * Reduce tokio features * Update notifier to stable futures * Builder update * Further updates * Convert self referential async functions * 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 * Upgrade simulator to stable futures * Lighthouse compiles on stable futures * Remove println debugging statement * Update libp2p service, start rpc test upgrade * Update network crate for new libp2p * Update tokio::codec to futures_codec (#1128) * Further work towards RPC corrections * Correct http timeout and network service select * Use tokio runtime for libp2p * Revert "Update tokio::codec to futures_codec (#1128)" This reverts commite57aea924a. * Upgrade RPC libp2p tests * Upgrade secio fallback test * Upgrade gossipsub examples * Clean up RPC protocol * Test fixes (#1133) * Correct websocket timeout and run on os thread * Fix network test * Clean up PR * Correct tokio tcp move attestation service tests * Upgrade attestation service tests * Correct network test * Correct genesis test * Test corrections * Log info when block is received * Modify logs and update attester service events * Stable futures: fixes to vc, eth1 and account manager (#1142) * Add local testnet scripts * Remove whiteblock script * Rename local testnet script * Move spawns onto handle * Fix VC panic * Initial fix to block production issue * Tidy block producer fix * Tidy further * Add local testnet clean script * Run cargo fmt * Tidy duties service * Tidy fork service * Tidy ForkService * Tidy AttestationService * Tidy notifier * Ensure await is not suppressed in eth1 * Ensure await is not suppressed in account_manager * Use .ok() instead of .unwrap_or(()) * RPC decoding test for proto * Update discv5 and eth2-libp2p deps * Fix lcli double runtime issue (#1144) * Handle stream termination and dialing peer errors * Correct peer_info variant types * Remove unnecessary warnings * Handle subnet unsubscription removal and improve logigng * Add logs around ping * Upgrade discv5 and improve logging * Handle peer connection status for multiple connections * Improve network service logging * Improve logging around peer manager * Upgrade swarm poll centralise peer management * Identify clients on error * Fix `remove_peer` in sync (#1150) * remove_peer removes from all chains * Remove logs * Fix early return from loop * Improved logging, fix panic * Partially correct tests * Stable futures: Vc sync (#1149) * Improve syncing heuristic * Add comments * Use safer method for tolerance * Fix tests * Stable futures: Fix VC bug, update agg pool, add more metrics (#1151) * Expose epoch processing summary * Expose participation metrics to prometheus * Switch to f64 * Reduce precision * Change precision * Expose observed attesters metrics * Add metrics for agg/unagg attn counts * Add metrics for gossip rx * Add metrics for gossip tx * Adds ignored attns to prom * Add attestation timing * Add timer for aggregation pool sig agg * Add write lock timer for agg pool * Add more metrics to agg pool * Change map lock code * Add extra metric to agg pool * Change lock handling in agg pool * Change .write() to .read() * Add another agg pool timer * Fix for is_aggregator * Fix pruning bug Co-authored-by: pawan <pawandhananjay@gmail.com> Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
@@ -6,8 +6,8 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
web3 = "0.10.0"
|
||||
tokio = "0.1.22"
|
||||
futures = "0.1.25"
|
||||
tokio = { version = "0.2.20", features = ["time"] }
|
||||
futures = { version = "0.3.5", features = ["compat"] }
|
||||
types = { path = "../../eth2/types"}
|
||||
serde_json = "1.0"
|
||||
serde_json = "1.0.52"
|
||||
deposit_contract = { path = "../../eth2/utils/deposit_contract"}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use futures::Future;
|
||||
use futures::compat::Future01CompatExt;
|
||||
use serde_json::json;
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
@@ -98,28 +98,34 @@ impl GanacheInstance {
|
||||
}
|
||||
|
||||
/// Increase the timestamp on future blocks by `increase_by` seconds.
|
||||
pub fn increase_time(&self, increase_by: u64) -> impl Future<Item = (), Error = String> {
|
||||
pub async fn increase_time(&self, increase_by: u64) -> Result<(), String> {
|
||||
self.web3
|
||||
.transport()
|
||||
.execute("evm_increaseTime", vec![json!(increase_by)])
|
||||
.compat()
|
||||
.await
|
||||
.map(|_json_value| ())
|
||||
.map_err(|e| format!("Failed to increase time on EVM (is this ganache?): {:?}", e))
|
||||
}
|
||||
|
||||
/// Returns the current block number, as u64
|
||||
pub fn block_number(&self) -> impl Future<Item = u64, Error = String> {
|
||||
pub async fn block_number(&self) -> Result<u64, String> {
|
||||
self.web3
|
||||
.eth()
|
||||
.block_number()
|
||||
.compat()
|
||||
.await
|
||||
.map(|v| v.as_u64())
|
||||
.map_err(|e| format!("Failed to get block number: {:?}", e))
|
||||
}
|
||||
|
||||
/// Mines a single block.
|
||||
pub fn evm_mine(&self) -> impl Future<Item = (), Error = String> {
|
||||
pub async fn evm_mine(&self) -> Result<(), String> {
|
||||
self.web3
|
||||
.transport()
|
||||
.execute("evm_mine", vec![])
|
||||
.compat()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| {
|
||||
"utils should mine new block with evm_mine (only works with ganache-cli!)"
|
||||
|
||||
@@ -10,10 +10,10 @@ mod ganache;
|
||||
use deposit_contract::{
|
||||
encode_eth1_tx_data, testnet, ABI, BYTECODE, CONTRACT_DEPLOY_GAS, DEPOSIT_GAS,
|
||||
};
|
||||
use futures::{future, stream, Future, IntoFuture, Stream};
|
||||
use futures::compat::Future01CompatExt;
|
||||
use ganache::GanacheInstance;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::{runtime::Runtime, timer::Delay};
|
||||
use std::time::Duration;
|
||||
use tokio::time::delay_for;
|
||||
use types::DepositData;
|
||||
use types::{test_utils::generate_deterministic_keypair, EthSpec, Hash256, Keypair, Signature};
|
||||
use web3::contract::{Contract, Options};
|
||||
@@ -31,13 +31,14 @@ pub struct GanacheEth1Instance {
|
||||
}
|
||||
|
||||
impl GanacheEth1Instance {
|
||||
pub fn new() -> impl Future<Item = Self, Error = String> {
|
||||
GanacheInstance::new().into_future().and_then(|ganache| {
|
||||
DepositContract::deploy(ganache.web3.clone(), 0, None).map(|deposit_contract| Self {
|
||||
pub async fn new() -> Result<Self, String> {
|
||||
let ganache = GanacheInstance::new()?;
|
||||
DepositContract::deploy(ganache.web3.clone(), 0, None)
|
||||
.await
|
||||
.map(|deposit_contract| Self {
|
||||
ganache,
|
||||
deposit_contract,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn endpoint(&self) -> String {
|
||||
@@ -57,19 +58,19 @@ pub struct DepositContract {
|
||||
}
|
||||
|
||||
impl DepositContract {
|
||||
pub fn deploy(
|
||||
pub async fn deploy(
|
||||
web3: Web3<Http>,
|
||||
confirmations: usize,
|
||||
password: Option<String>,
|
||||
) -> impl Future<Item = Self, Error = String> {
|
||||
Self::deploy_bytecode(web3, confirmations, BYTECODE, ABI, password)
|
||||
) -> Result<Self, String> {
|
||||
Self::deploy_bytecode(web3, confirmations, BYTECODE, ABI, password).await
|
||||
}
|
||||
|
||||
pub fn deploy_testnet(
|
||||
pub async fn deploy_testnet(
|
||||
web3: Web3<Http>,
|
||||
confirmations: usize,
|
||||
password: Option<String>,
|
||||
) -> impl Future<Item = Self, Error = String> {
|
||||
) -> Result<Self, String> {
|
||||
Self::deploy_bytecode(
|
||||
web3,
|
||||
confirmations,
|
||||
@@ -77,35 +78,33 @@ impl DepositContract {
|
||||
testnet::ABI,
|
||||
password,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn deploy_bytecode(
|
||||
async fn deploy_bytecode(
|
||||
web3: Web3<Http>,
|
||||
confirmations: usize,
|
||||
bytecode: &[u8],
|
||||
abi: &[u8],
|
||||
password: Option<String>,
|
||||
) -> impl Future<Item = Self, Error = String> {
|
||||
let web3_1 = web3.clone();
|
||||
|
||||
deploy_deposit_contract(
|
||||
) -> Result<Self, String> {
|
||||
let address = deploy_deposit_contract(
|
||||
web3.clone(),
|
||||
confirmations,
|
||||
bytecode.to_vec(),
|
||||
abi.to_vec(),
|
||||
password,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Failed to deploy contract: {}. Is scripts/ganache_tests_node.sh running?.",
|
||||
e
|
||||
)
|
||||
})
|
||||
.and_then(move |address| {
|
||||
Contract::from_json(web3_1.eth(), address, ABI)
|
||||
.map_err(|e| format!("Failed to init contract: {:?}", e))
|
||||
})
|
||||
.map(|contract| Self { contract, web3 })
|
||||
})?;
|
||||
Contract::from_json(web3.clone().eth(), address, ABI)
|
||||
.map_err(|e| format!("Failed to init contract: {:?}", e))
|
||||
.map(move |contract| Self { contract, web3 })
|
||||
}
|
||||
|
||||
/// The deposit contract's address in `0x00ab...` format.
|
||||
@@ -136,7 +135,7 @@ impl DepositContract {
|
||||
/// Creates a random, valid deposit and submits it to the deposit contract.
|
||||
///
|
||||
/// The keypairs are created randomly and destroyed.
|
||||
pub fn deposit_random<E: EthSpec>(&self, runtime: &mut Runtime) -> Result<(), String> {
|
||||
pub async fn deposit_random<E: EthSpec>(&self) -> Result<(), String> {
|
||||
let keypair = Keypair::random();
|
||||
|
||||
let mut deposit = DepositData {
|
||||
@@ -148,21 +147,21 @@ impl DepositContract {
|
||||
|
||||
deposit.signature = deposit.create_signature(&keypair.sk, &E::default_spec());
|
||||
|
||||
self.deposit(runtime, deposit)
|
||||
self.deposit(deposit).await
|
||||
}
|
||||
|
||||
/// Perfoms a blocking deposit.
|
||||
pub fn deposit(&self, runtime: &mut Runtime, deposit_data: DepositData) -> Result<(), String> {
|
||||
runtime
|
||||
.block_on(self.deposit_async(deposit_data))
|
||||
pub async fn deposit(&self, deposit_data: DepositData) -> Result<(), String> {
|
||||
self.deposit_async(deposit_data)
|
||||
.await
|
||||
.map_err(|e| format!("Deposit failed: {:?}", e))
|
||||
}
|
||||
|
||||
pub fn deposit_deterministic_async<E: EthSpec>(
|
||||
pub async fn deposit_deterministic_async<E: EthSpec>(
|
||||
&self,
|
||||
keypair_index: usize,
|
||||
amount: u64,
|
||||
) -> impl Future<Item = (), Error = String> {
|
||||
) -> Result<(), String> {
|
||||
let keypair = generate_deterministic_keypair(keypair_index);
|
||||
|
||||
let mut deposit = DepositData {
|
||||
@@ -174,73 +173,57 @@ impl DepositContract {
|
||||
|
||||
deposit.signature = deposit.create_signature(&keypair.sk, &E::default_spec());
|
||||
|
||||
self.deposit_async(deposit)
|
||||
self.deposit_async(deposit).await
|
||||
}
|
||||
|
||||
/// Performs a non-blocking deposit.
|
||||
pub fn deposit_async(
|
||||
&self,
|
||||
deposit_data: DepositData,
|
||||
) -> impl Future<Item = (), Error = String> {
|
||||
let contract = self.contract.clone();
|
||||
let web3_1 = self.web3.clone();
|
||||
|
||||
self.web3
|
||||
pub async fn deposit_async(&self, deposit_data: DepositData) -> Result<(), String> {
|
||||
let from = self
|
||||
.web3
|
||||
.eth()
|
||||
.accounts()
|
||||
.compat()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get accounts: {:?}", e))
|
||||
.and_then(|accounts| {
|
||||
accounts
|
||||
.get(DEPOSIT_ACCOUNTS_INDEX)
|
||||
.cloned()
|
||||
.ok_or_else(|| "Insufficient accounts for deposit".to_string())
|
||||
})
|
||||
.and_then(move |from| {
|
||||
let tx_request = TransactionRequest {
|
||||
from,
|
||||
to: Some(contract.address()),
|
||||
gas: Some(U256::from(DEPOSIT_GAS)),
|
||||
gas_price: None,
|
||||
value: Some(from_gwei(deposit_data.amount)),
|
||||
// Note: the reason we use this `TransactionRequest` instead of just using the
|
||||
// function in `self.contract` is so that the `encode_eth1_tx_data` function gets used
|
||||
// during testing.
|
||||
//
|
||||
// It's important that `encode_eth1_tx_data` stays correct and does not suffer from
|
||||
// code-rot.
|
||||
data: encode_eth1_tx_data(&deposit_data).map(Into::into).ok(),
|
||||
nonce: None,
|
||||
condition: None,
|
||||
};
|
||||
})?;
|
||||
let tx_request = TransactionRequest {
|
||||
from,
|
||||
to: Some(self.contract.address()),
|
||||
gas: Some(U256::from(DEPOSIT_GAS)),
|
||||
gas_price: None,
|
||||
value: Some(from_gwei(deposit_data.amount)),
|
||||
// Note: the reason we use this `TransactionRequest` instead of just using the
|
||||
// function in `self.contract` is so that the `eth1_tx_data` function gets used
|
||||
// during testing.
|
||||
//
|
||||
// It's important that `eth1_tx_data` stays correct and does not suffer from
|
||||
// code-rot.
|
||||
data: encode_eth1_tx_data(&deposit_data).map(Into::into).ok(),
|
||||
nonce: None,
|
||||
condition: None,
|
||||
};
|
||||
|
||||
web3_1
|
||||
.eth()
|
||||
.send_transaction(tx_request)
|
||||
.map_err(|e| format!("Failed to call deposit fn: {:?}", e))
|
||||
})
|
||||
.map(|_| ())
|
||||
self.web3
|
||||
.eth()
|
||||
.send_transaction(tx_request)
|
||||
.compat()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to call deposit fn: {:?}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Peforms many deposits, each preceded by a delay.
|
||||
pub fn deposit_multiple(
|
||||
&self,
|
||||
deposits: Vec<DelayThenDeposit>,
|
||||
) -> impl Future<Item = (), Error = String> {
|
||||
let s = self.clone();
|
||||
stream::unfold(deposits.into_iter(), move |mut deposit_iter| {
|
||||
let s = s.clone();
|
||||
match deposit_iter.next() {
|
||||
Some(deposit) => Some(
|
||||
Delay::new(Instant::now() + deposit.delay)
|
||||
.map_err(|e| format!("Failed to execute delay: {:?}", e))
|
||||
.and_then(move |_| s.deposit_async(deposit.deposit))
|
||||
.map(move |yielded| (yielded, deposit_iter)),
|
||||
),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.map(|_| ())
|
||||
pub async fn deposit_multiple(&self, deposits: Vec<DelayThenDeposit>) -> Result<(), String> {
|
||||
for deposit in deposits.into_iter() {
|
||||
delay_for(deposit.delay).await;
|
||||
self.deposit_async(deposit.deposit).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,61 +243,56 @@ fn from_gwei(gwei: u64) -> U256 {
|
||||
|
||||
/// Deploys the deposit contract to the given web3 instance using the account with index
|
||||
/// `DEPLOYER_ACCOUNTS_INDEX`.
|
||||
fn deploy_deposit_contract(
|
||||
async fn deploy_deposit_contract(
|
||||
web3: Web3<Http>,
|
||||
confirmations: usize,
|
||||
bytecode: Vec<u8>,
|
||||
abi: Vec<u8>,
|
||||
password_opt: Option<String>,
|
||||
) -> impl Future<Item = Address, Error = String> {
|
||||
) -> Result<Address, String> {
|
||||
let bytecode = String::from_utf8(bytecode).expect("bytecode must be valid utf8");
|
||||
let web3_1 = web3.clone();
|
||||
|
||||
web3.eth()
|
||||
let from_address = web3
|
||||
.eth()
|
||||
.accounts()
|
||||
.compat()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get accounts: {:?}", e))
|
||||
.and_then(|accounts| {
|
||||
accounts
|
||||
.get(DEPLOYER_ACCOUNTS_INDEX)
|
||||
.cloned()
|
||||
.ok_or_else(|| "Insufficient accounts for deployer".to_string())
|
||||
})
|
||||
.and_then(move |from_address| {
|
||||
let future: Box<dyn Future<Item = Address, Error = String> + Send> =
|
||||
if let Some(password) = password_opt {
|
||||
// Unlock for only a single transaction.
|
||||
let duration = None;
|
||||
})?;
|
||||
|
||||
let future = web3_1
|
||||
.personal()
|
||||
.unlock_account(from_address, &password, duration)
|
||||
.then(move |result| match result {
|
||||
Ok(true) => Ok(from_address),
|
||||
Ok(false) => Err("Eth1 node refused to unlock account".to_string()),
|
||||
Err(e) => Err(format!("Eth1 unlock request failed: {:?}", e)),
|
||||
});
|
||||
let deploy_address = if let Some(password) = password_opt {
|
||||
let result = web3
|
||||
.personal()
|
||||
.unlock_account(from_address, &password, None)
|
||||
.compat()
|
||||
.await;
|
||||
match result {
|
||||
Ok(true) => return Ok(from_address),
|
||||
Ok(false) => return Err("Eth1 node refused to unlock account".to_string()),
|
||||
Err(e) => return Err(format!("Eth1 unlock request failed: {:?}", e)),
|
||||
};
|
||||
} else {
|
||||
from_address
|
||||
};
|
||||
|
||||
Box::new(future)
|
||||
} else {
|
||||
Box::new(future::ok(from_address))
|
||||
};
|
||||
let pending_contract = Contract::deploy(web3.eth(), &abi)
|
||||
.map_err(|e| format!("Unable to build contract deployer: {:?}", e))?
|
||||
.confirmations(confirmations)
|
||||
.options(Options {
|
||||
gas: Some(U256::from(CONTRACT_DEPLOY_GAS)),
|
||||
..Options::default()
|
||||
})
|
||||
.execute(bytecode, (), deploy_address)
|
||||
.map_err(|e| format!("Failed to execute deployment: {:?}", e))?;
|
||||
|
||||
future
|
||||
})
|
||||
.and_then(move |deploy_address| {
|
||||
Contract::deploy(web3.eth(), &abi)
|
||||
.map_err(|e| format!("Unable to build contract deployer: {:?}", e))?
|
||||
.confirmations(confirmations)
|
||||
.options(Options {
|
||||
gas: Some(U256::from(CONTRACT_DEPLOY_GAS)),
|
||||
..Options::default()
|
||||
})
|
||||
.execute(bytecode, (), deploy_address)
|
||||
.map_err(|e| format!("Failed to execute deployment: {:?}", e))
|
||||
})
|
||||
.and_then(|pending_contract| {
|
||||
pending_contract
|
||||
.map(|contract| contract.address())
|
||||
.map_err(|e| format!("Unable to resolve pending contract: {:?}", e))
|
||||
})
|
||||
pending_contract
|
||||
.compat()
|
||||
.await
|
||||
.map(|contract| contract.address())
|
||||
.map_err(|e| format!("Unable to resolve pending contract: {:?}", e))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user