feat(core/geth): enable Clique mode (#2063)

* feat(core/geth): add method for initing Clique genesis

* feat(core/geth): add Clique mode

this is ported from https://github.com/paradigmxyz/reth/pull/623/files\#diff-99e7bcdfb85c75ffe5fb2ccfbc5fd8234fced6704c34b622fbf24289b8522515R228-R245

* feat(core/geth): disable discovery in clique mode

* examples: add Geth Clique example
This commit is contained in:
Georgios Konstantopoulos 2023-01-19 10:49:47 -08:00 committed by GitHub
parent eaaa01a7d6
commit b8fa524e8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 150 additions and 6 deletions

1
Cargo.lock generated
View File

@ -1320,6 +1320,7 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
"tempfile",
"tokio", "tokio",
] ]

View File

@ -110,6 +110,7 @@ ethers-contract = { version = "^1.0.0", default-features = false, path = "./ethe
ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [ ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [
"ws", "ws",
] } ] }
tempfile = "3.3.0"
[target.'cfg(target_family = "unix")'.dev-dependencies] [target.'cfg(target_family = "unix")'.dev-dependencies]
ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [ ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [

View File

@ -40,6 +40,63 @@ pub struct Genesis {
pub alloc: HashMap<Address, GenesisAccount>, pub alloc: HashMap<Address, GenesisAccount>,
} }
impl Genesis {
/// Creates a chain config using the given chain id.
/// and funds the given address with max coins.
///
/// Enables all hard forks up to London at genesis.
pub fn new(chain_id: u64, signer_addr: Address) -> Genesis {
// set up a clique config with an instant sealing period and short (8 block) epoch
let clique_config = CliqueConfig { period: 0, epoch: 8 };
let config = ChainConfig {
chain_id,
eip155_block: Some(0),
eip150_block: Some(0),
eip158_block: Some(0),
homestead_block: Some(0),
byzantium_block: Some(0),
constantinople_block: Some(0),
petersburg_block: Some(0),
istanbul_block: Some(0),
muir_glacier_block: Some(0),
berlin_block: Some(0),
london_block: Some(0),
clique: Some(clique_config),
..Default::default()
};
// fund account
let mut alloc = HashMap::new();
alloc.insert(
signer_addr,
GenesisAccount { balance: U256::MAX, nonce: None, code: None, storage: None },
);
// put signer address in the extra data, padded by the required amount of zeros
// Clique issue: https://github.com/ethereum/EIPs/issues/225
// Clique EIP: https://eips.ethereum.org/EIPS/eip-225
//
// The first 32 bytes are vanity data, so we will populate it with zeros
// This is followed by the signer address, which is 20 bytes
// There are 65 bytes of zeros after the signer address, which is usually populated with the
// proposer signature. Because the genesis does not have a proposer signature, it will be
// populated with zeros.
let extra_data_bytes = [&[0u8; 32][..], signer_addr.as_bytes(), &[0u8; 65][..]].concat();
let extra_data = Bytes::from(extra_data_bytes);
Genesis {
config,
alloc,
difficulty: U256::one(),
gas_limit: U64::from(5000000),
extra_data,
..Default::default()
}
}
}
/// An account in the state of the genesis block. /// An account in the state of the genesis block.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenesisAccount { pub struct GenesisAccount {

View File

@ -1,5 +1,10 @@
use k256::ecdsa::SigningKey;
use super::{unused_port, Genesis}; use super::{unused_port, Genesis};
use crate::types::H256; use crate::{
types::{Bytes, H256},
utils::secret_key_to_address,
};
use std::{ use std::{
env::temp_dir, env::temp_dir,
fs::{create_dir, File}, fs::{create_dir, File},
@ -181,8 +186,9 @@ pub struct Geth {
data_dir: Option<PathBuf>, data_dir: Option<PathBuf>,
chain_id: Option<u64>, chain_id: Option<u64>,
insecure_unlock: bool, insecure_unlock: bool,
genesis: Option<Genesis>, pub genesis: Option<Genesis>,
mode: GethMode, mode: GethMode,
pub clique_private_key: Option<SigningKey>,
} }
impl Geth { impl Geth {
@ -208,6 +214,11 @@ impl Geth {
Self::new().path(path) Self::new().path(path)
} }
/// Returns whether the node is launched in Clique consensus mode
pub fn is_clique(&self) -> bool {
self.clique_private_key.is_some()
}
/// Sets the `path` to the `geth` executable /// Sets the `path` to the `geth` executable
/// ///
/// By default, it's expected that `geth` is in `$PATH`, see also /// By default, it's expected that `geth` is in `$PATH`, see also
@ -218,6 +229,14 @@ impl Geth {
self self
} }
/// Sets the Clique Private Key to the `geth` executable, which will be later
/// loaded on the node.
#[must_use]
pub fn set_clique_private_key<T: Into<SigningKey>>(mut self, private_key: T) -> Self {
self.clique_private_key = Some(private_key.into());
self
}
/// Sets the port which will be used when the `geth-cli` instance is launched. /// Sets the port which will be used when the `geth-cli` instance is launched.
#[must_use] #[must_use]
pub fn port<T: Into<u16>>(mut self, port: T) -> Self { pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
@ -273,6 +292,11 @@ impl Geth {
/// options. /// options.
#[must_use] #[must_use]
pub fn disable_discovery(mut self) -> Self { pub fn disable_discovery(mut self) -> Self {
self.inner_disable_discovery();
self
}
fn inner_disable_discovery(&mut self) {
match self.mode { match self.mode {
GethMode::Dev(_) => { GethMode::Dev(_) => {
self.mode = self.mode =
@ -280,7 +304,6 @@ impl Geth {
} }
GethMode::NonDev(ref mut opts) => opts.discovery = false, GethMode::NonDev(ref mut opts) => opts.discovery = false,
} }
self
} }
/// Manually sets the IPC path for the socket manually. /// Manually sets the IPC path for the socket manually.
@ -318,7 +341,7 @@ impl Geth {
/// Consumes the builder and spawns `geth` with stdout redirected /// Consumes the builder and spawns `geth` with stdout redirected
/// to /dev/null. /// to /dev/null.
pub fn spawn(self) -> GethInstance { pub fn spawn(mut self) -> GethInstance {
let mut cmd = let mut cmd =
if let Some(ref prg) = self.program { Command::new(prg) } else { Command::new(GETH) }; if let Some(ref prg) = self.program { Command::new(prg) } else { Command::new(GETH) };
// geth uses stderr for its logs // geth uses stderr for its logs
@ -337,15 +360,47 @@ impl Geth {
cmd.arg("--ws.api").arg(API); cmd.arg("--ws.api").arg(API);
// pass insecure unlock flag if set // pass insecure unlock flag if set
if self.insecure_unlock { let is_clique = self.is_clique();
if self.insecure_unlock || is_clique {
cmd.arg("--allow-insecure-unlock"); cmd.arg("--allow-insecure-unlock");
} }
if is_clique {
self.inner_disable_discovery();
}
// Set the port for authenticated APIs // Set the port for authenticated APIs
cmd.arg("--authrpc.port").arg(authrpc_port.to_string()); cmd.arg("--authrpc.port").arg(authrpc_port.to_string());
// use geth init to initialize the datadir if the genesis exists // use geth init to initialize the datadir if the genesis exists
if let Some(genesis) = self.genesis { if let Some(ref mut genesis) = self.genesis {
if is_clique {
use super::CliqueConfig;
// set up a clique config with an instant sealing period and short (8 block) epoch
let clique_config = CliqueConfig { period: 0, epoch: 8 };
genesis.config.clique = Some(clique_config);
// set the extraData field
let extra_data_bytes = [
&[0u8; 32][..],
secret_key_to_address(
self.clique_private_key.as_ref().expect("is_clique == true"),
)
.as_ref(),
&[0u8; 65][..],
]
.concat();
let extra_data = Bytes::from(extra_data_bytes);
genesis.extra_data = extra_data;
}
} else if is_clique {
self.genesis = Some(Genesis::new(
self.chain_id.expect("chain id must be set in clique mode"),
secret_key_to_address(self.clique_private_key.as_ref().expect("is_clique == true")),
));
}
if let Some(ref genesis) = self.genesis {
// create a temp dir to store the genesis file // create a temp dir to store the genesis file
let temp_genesis_path = temp_dir().join("genesis.json"); let temp_genesis_path = temp_dir().join("genesis.json");

30
examples/geth_clique.rs Normal file
View File

@ -0,0 +1,30 @@
use ethers::{
core::{rand::thread_rng, utils::Geth},
signers::LocalWallet,
};
use eyre::Result;
#[tokio::main]
/// Shows how to instantiate a Geth with Clique enabled.
async fn main() -> Result<()> {
// Generate a random clique signer and set it on Geth.
let data_dir = tempfile::tempdir().expect("should be able to create temp geth datadir");
let dir_path = data_dir.into_path();
println!("Using {}", dir_path.display());
// Create a random signer
let key = LocalWallet::new(&mut thread_rng());
let clique_key = key.signer().clone();
let _geth = Geth::new()
// set the signer
.set_clique_private_key(clique_key)
// must always set the chain id here
.chain_id(199u64)
// set the datadir to a temp dir
.data_dir(dir_path)
// spawn it
.spawn();
Ok(())
}