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:
parent
eaaa01a7d6
commit
b8fa524e8e
|
@ -1320,6 +1320,7 @@ dependencies = [
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
Loading…
Reference in New Issue