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",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"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 = [
|
||||
"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 = [
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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},
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -273,6 +292,11 @@ 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 =
|
||||
|
@ -280,7 +304,6 @@ impl Geth {
|
|||
}
|
||||
GethMode::NonDev(ref mut opts) => opts.discovery = false,
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Manually sets the IPC path for the socket manually.
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
||||
|
|
|
@ -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