-
Notifications
You must be signed in to change notification settings - Fork 827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(net): test syncing from geth #623
Changes from 20 commits
cacb064
705ac1e
cc497fe
68f5fc7
8e464f7
588b030
9299a4e
67b1b38
0bb5a81
89d07d8
54522bd
caa053f
c7c5017
b0c9765
d50660b
ca770fd
75e4ff3
359c9b4
ebada9a
d4881bc
50dc5d2
3e56757
eea9265
eec3449
e6b4844
96bcea0
2c3f45e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
//! Helper struct for working with a clique geth instance. | ||
|
||
use enr::k256::ecdsa::SigningKey; | ||
use ethers_core::utils::{Geth, GethInstance}; | ||
use ethers_middleware::SignerMiddleware; | ||
use ethers_providers::{Provider, Ws}; | ||
use ethers_signers::{LocalWallet, Wallet}; | ||
use std::{ | ||
io::{BufRead, BufReader}, | ||
net::SocketAddr, | ||
}; | ||
|
||
/// A [`Geth`](ethers_core::utils::Geth) instance configured with Clique and a custom | ||
/// [`Genesis`](ethers_core::utils::Genesis). | ||
/// | ||
/// This holds a [`SignerMiddleware`](ethers_middleware::signer_middleware::SignerMiddleware) for | ||
/// enabling block production and creating transactions. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// # use ethers_core::utils::Geth; | ||
/// # use reth_staged_sync::test_utils::CliqueGethInstance; | ||
/// | ||
/// // this creates a funded geth | ||
/// let clique_geth = Geth::new() | ||
/// .chain_id(chain_id); | ||
/// | ||
/// // build the funded geth, generating a random signing key and enabling clique | ||
/// let (mut clique, provider) = CliqueGethInstance::new(clique_geth, None).await; | ||
/// | ||
/// // don't print logs, but drain the stderr | ||
/// clique.prevent_blocking().await; | ||
/// ``` | ||
pub struct CliqueGethInstance( | ||
/// The spawned [`GethInstance`](ethers_core::utils::GethInstance). | ||
pub GethInstance, | ||
); | ||
|
||
impl CliqueGethInstance { | ||
/// Sets up a new [`SignerMiddleware`](ethers_middleware::signer_middleware::SignerMiddleware) | ||
/// for the [`Geth`](ethers_core::utils::Geth) instance and returns the | ||
/// [`CliqueGethInstance`]. | ||
/// | ||
/// The signer is assumed to be the clique signer and the signer for any transactions sent for | ||
/// block production. | ||
/// | ||
/// This also spawns the geth instance. | ||
pub async fn new( | ||
geth: Geth, | ||
signer: Option<SigningKey>, | ||
) -> (Self, SignerMiddleware<Provider<Ws>, Wallet<SigningKey>>) { | ||
let signer = signer.unwrap_or_else(|| SigningKey::random(&mut rand::thread_rng())); | ||
|
||
let geth = geth.set_clique_private_key(signer.clone()); | ||
|
||
// spawn the geth instance | ||
let instance = geth.spawn(); | ||
|
||
// create the signer | ||
let wallet: LocalWallet = signer.clone().into(); | ||
|
||
// set up ethers provider | ||
let geth_endpoint = SocketAddr::new([127, 0, 0, 1].into(), instance.port()).to_string(); | ||
let provider = Provider::<Ws>::connect(format!("ws://{geth_endpoint}")).await.unwrap(); | ||
let provider = | ||
SignerMiddleware::new_with_provider_chain(provider, wallet.clone()).await.unwrap(); | ||
|
||
(Self(instance), provider) | ||
} | ||
|
||
/// Prints the logs of the [`Geth`](ethers_core::utils::Geth) instance in a new | ||
/// [`task`](tokio::task). | ||
#[allow(dead_code)] | ||
pub async fn print_logs(&mut self) { | ||
// take the stderr of the geth instance and print it | ||
let stderr = self.0.stderr().unwrap(); | ||
|
||
// print logs in a new task | ||
let mut err_reader = BufReader::new(stderr); | ||
|
||
tokio::spawn(async move { | ||
loop { | ||
if let (Ok(line), line_str) = { | ||
let mut buf = String::new(); | ||
(err_reader.read_line(&mut buf), buf.clone()) | ||
} { | ||
if line == 0 { | ||
break | ||
} | ||
if !line_str.is_empty() { | ||
dbg!(line_str); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
|
||
/// Prevents the [`Geth`](ethers_core::utils::Geth) instance from blocking due to the `stderr` | ||
/// filling up. | ||
pub async fn prevent_blocking(&mut self) { | ||
// take the stderr of the geth instance and print it | ||
let stderr = self.0.stderr().unwrap(); | ||
|
||
// print logs in a new task | ||
let mut err_reader = BufReader::new(stderr); | ||
|
||
tokio::spawn(async move { | ||
loop { | ||
if let (Ok(line), _line_str) = { | ||
let mut buf = String::new(); | ||
(err_reader.read_line(&mut buf), buf.clone()) | ||
} { | ||
if line == 0 { | ||
break | ||
} | ||
} | ||
} | ||
Comment on lines
+109
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is just for debugging, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't we send to /dev/null instead and not bother with this? either way let's do in a followup There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can't do it by default because |
||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
//! Helper extension traits for working with clique providers. | ||
|
||
use async_trait::async_trait; | ||
use enr::k256::ecdsa::SigningKey; | ||
use ethers_core::{ | ||
types::{transaction::eip2718::TypedTransaction, Address, Block, BlockNumber, H256}, | ||
utils::secret_key_to_address, | ||
}; | ||
use ethers_middleware::SignerMiddleware; | ||
use ethers_providers::Middleware; | ||
use ethers_signers::Signer; | ||
use reth_network::test_utils::enr_to_peer_id; | ||
use reth_primitives::PeerId; | ||
use thiserror::Error; | ||
use tracing::trace; | ||
|
||
/// An error that can occur when using the [`CliqueMiddleware`]. | ||
#[derive(Error, Debug)] | ||
pub enum CliqueError<E> { | ||
/// Error encountered when using the provider | ||
#[error(transparent)] | ||
ProviderError(#[from] E), | ||
|
||
/// No genesis block returned from the provider | ||
#[error("no genesis block returned from the provider")] | ||
NoGenesis, | ||
|
||
/// No tip block returned from the provider | ||
#[error("no tip block returned from the provider")] | ||
NoTip, | ||
|
||
/// Account was not successfully unlocked on the provider | ||
#[error("account was not successfully unlocked on the provider")] | ||
AccountNotUnlocked, | ||
|
||
/// Mining was not successfully enabled on the provider | ||
#[error("mining was not successfully enabled on the provider")] | ||
MiningNotEnabled, | ||
|
||
/// Mismatch between locally computed address and address returned from the provider | ||
#[error("local address {local} does not match remote address {remote}")] | ||
AddressMismatch { | ||
/// The locally computed address | ||
local: Address, | ||
|
||
/// The address returned from the provider | ||
remote: Address, | ||
}, | ||
} | ||
|
||
/// Error type for [`CliqueMiddleware`]. | ||
pub type CliqueMiddlewareError<M> = CliqueError<<M as Middleware>::Error>; | ||
|
||
/// Extension trait for [`Middleware`] to provide clique specific functionality. | ||
#[async_trait(?Send)] | ||
pub trait CliqueMiddleware: Send + Sync + Middleware { | ||
/// Enable mining on the clique geth instance by importing and unlocking the signer account | ||
/// derived from given private key and password. | ||
async fn enable_mining( | ||
&self, | ||
signer: SigningKey, | ||
password: String, | ||
) -> Result<(), CliqueMiddlewareError<Self>> { | ||
let our_address = secret_key_to_address(&signer); | ||
|
||
// send the private key to geth and unlock it | ||
let key_bytes = signer.to_bytes().to_vec().into(); | ||
trace!( | ||
private_key=%hex::encode(&key_bytes), | ||
"Importing private key" | ||
); | ||
|
||
let unlocked_addr = self.import_raw_key(key_bytes, password.to_string()).await?; | ||
if unlocked_addr != our_address { | ||
return Err(CliqueError::AddressMismatch { local: our_address, remote: unlocked_addr }) | ||
} | ||
|
||
let unlock_success = self.unlock_account(our_address, password.to_string(), None).await?; | ||
|
||
if !unlock_success { | ||
return Err(CliqueError::AccountNotUnlocked) | ||
} | ||
|
||
// start mining? | ||
self.start_mining(None).await?; | ||
|
||
// check that we are mining | ||
let mining = self.mining().await?; | ||
if !mining { | ||
return Err(CliqueError::MiningNotEnabled) | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Returns the chain tip of the [`Geth`](ethers_core::utils::Geth) instance by calling | ||
/// geth's `eth_getBlock`. | ||
async fn remote_tip_block(&self) -> Result<Block<H256>, CliqueMiddlewareError<Self>> { | ||
self.get_block(BlockNumber::Latest).await?.ok_or(CliqueError::NoTip) | ||
} | ||
|
||
/// Returns the genesis block of the [`Geth`](ethers_core::utils::Geth) instance by calling | ||
/// geth's `eth_getBlock`. | ||
async fn remote_genesis_block(&self) -> Result<Block<H256>, CliqueMiddlewareError<Self>> { | ||
self.get_block(BlockNumber::Earliest).await?.ok_or(CliqueError::NoGenesis) | ||
} | ||
|
||
/// Signs and sends the given unsigned transactions sequentially, signing with the private key | ||
/// used to configure the [`CliqueGethInstance`]. | ||
async fn send_requests<T: IntoIterator<Item = TypedTransaction>>( | ||
&self, | ||
txs: T, | ||
) -> Result<(), CliqueMiddlewareError<Self>> { | ||
for tx in txs { | ||
self.send_transaction(tx, None).await?; | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Returns the [`Geth`](ethers_core::utils::Geth) instance [`PeerId`](reth_primitives::PeerId) | ||
/// by calling geth's `admin_nodeInfo`. | ||
async fn peer_id(&self) -> Result<PeerId, CliqueMiddlewareError<Self>> { | ||
Ok(enr_to_peer_id(self.node_info().await?.enr)) | ||
} | ||
} | ||
|
||
impl<M: Middleware, S: Signer> CliqueMiddleware for SignerMiddleware<M, S> {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#![warn(missing_docs, unreachable_pub)] | ||
|
||
//! Common helpers for staged sync integration testing. | ||
|
||
pub mod clique; | ||
pub mod clique_middleware; | ||
|
||
pub use clique::CliqueGethInstance; | ||
pub use clique_middleware::{CliqueError, CliqueMiddleware, CliqueMiddlewareError}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//! Integration tests and test helpers for reth. | ||
#![cfg(test)] | ||
#![warn(missing_docs, unreachable_pub)] | ||
#![deny(unused_must_use, rust_2018_idioms)] | ||
#![doc(test( | ||
no_crate_inject, | ||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) | ||
))] | ||
|
||
pub mod sync; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if staged-sync is the right crate here, I guess so we can use the pipeline and test certain parts of it against a GethInstance?
perhaps it would be appropriate to move to its own crate?
wdyt @onbjerg ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefer it here, or in any
node
-named packages (ref #1079 )