Skip to content

Commit

Permalink
feat(core/geth): enable Clique mode (#2063)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gakonst committed Jan 19, 2023
1 parent eaaa01a commit b8fa524
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
"ws",
] }
tempfile = "3.3.0"

[target.'cfg(target_family = "unix")'.dev-dependencies]
ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [
Expand Down
57 changes: 57 additions & 0 deletions ethers-core/src/utils/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,63 @@ pub struct Genesis {
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.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenesisAccount {
Expand Down
67 changes: 61 additions & 6 deletions ethers-core/src/utils/geth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use k256::ecdsa::SigningKey;

use super::{unused_port, Genesis};
use crate::types::H256;
use crate::{
types::{Bytes, H256},
utils::secret_key_to_address,
};
use std::{
env::temp_dir,
fs::{create_dir, File},
Expand Down Expand Up @@ -181,8 +186,9 @@ pub struct Geth {
data_dir: Option<PathBuf>,
chain_id: Option<u64>,
insecure_unlock: bool,
genesis: Option<Genesis>,
pub genesis: Option<Genesis>,
mode: GethMode,
pub clique_private_key: Option<SigningKey>,
}

impl Geth {
Expand All @@ -208,6 +214,11 @@ impl Geth {
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
///
/// By default, it's expected that `geth` is in `$PATH`, see also
Expand All @@ -218,6 +229,14 @@ impl Geth {
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.
#[must_use]
pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
Expand Down Expand Up @@ -273,14 +292,18 @@ impl Geth {
/// options.
#[must_use]
pub fn disable_discovery(mut self) -> Self {
self.inner_disable_discovery();
self
}

fn inner_disable_discovery(&mut self) {
match self.mode {
GethMode::Dev(_) => {
self.mode =
GethMode::NonDev(PrivateNetOptions { discovery: false, ..Default::default() })
}
GethMode::NonDev(ref mut opts) => opts.discovery = false,
}
self
}

/// Manually sets the IPC path for the socket manually.
Expand Down Expand Up @@ -318,7 +341,7 @@ impl Geth {

/// Consumes the builder and spawns `geth` with stdout redirected
/// to /dev/null.
pub fn spawn(self) -> GethInstance {
pub fn spawn(mut self) -> GethInstance {
let mut cmd =
if let Some(ref prg) = self.program { Command::new(prg) } else { Command::new(GETH) };
// geth uses stderr for its logs
Expand All @@ -337,15 +360,47 @@ impl Geth {
cmd.arg("--ws.api").arg(API);

// 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");
}

if is_clique {
self.inner_disable_discovery();
}

// Set the port for authenticated APIs
cmd.arg("--authrpc.port").arg(authrpc_port.to_string());

// 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
let temp_genesis_path = temp_dir().join("genesis.json");

Expand Down
30 changes: 30 additions & 0 deletions examples/geth_clique.rs
Original file line number Diff line number Diff line change
@@ -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(())
}

0 comments on commit b8fa524

Please sign in to comment.