Skip to content

Commit

Permalink
Add support for sending signed payloads to beacon node
Browse files Browse the repository at this point in the history
  • Loading branch information
mksh committed Aug 23, 2024
1 parent f4fb8fb commit 64ef067
Show file tree
Hide file tree
Showing 11 changed files with 932 additions and 61 deletions.
690 changes: 680 additions & 10 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ env_logger = "^0.11"
hex = "0.4"
lazy_static = "1.5"
log = "^0.4"
# eth2_network_config uses native-tls, so do we
reqwest = { version = "0.11", default-features = false, features = ["native-tls"] }
getrandom = "0.2"
regex = "1.10.6"
serde = "1.0.204"
Expand All @@ -35,13 +37,15 @@ tiny-bip39 = "1.0.0"
tree_hash = "0.5.2"
tree_hash_derive = "0.5.2"
types = { git = "https://github.com/ChorusOne/lighthouse", rev = "1be5253610dc8fee3bf4b7a8dc1d01254bc5b57d"}
url = "2.5"
uuid = { version = "1.10", features = ["v4"] }

[dev-dependencies]
test-log = "^0.2"
pretty_assertions = "^1.4"
assert_cmd = "2.0"
predicates = "3.0"
httpmock = "0.7"

[[test]]
name = "e2e-tests"
Expand Down
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ You can use `eth-staking-smith` as follows to convert your address:
### Command to generate SignedBLSToExecutionChange

```
./target/debug/eth-staking-smith bls-to-execution-change --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_start_index 0 --validator_index 100 --withdrawal_credentials "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d"
./target/debug/eth-staking-smith bls-to-execution-change --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_start_index 0 --validator_index 100 --withdrawal_credentials "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d" \
--execution_address "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"
```

Expand All @@ -86,16 +86,13 @@ Note that --validator-index and --validator-start-index are two distinct paramet
### Command to send SignedBLSToExecutionChange request to Beacon node

```
curl -H "Content-Type: application/json" -d '{
"message": {
"validator_index": 100,
"from_bls_pubkey": "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d",
"to_execution_address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"
},
"signature": "0x9220e5badefdfe8abc36cae01af29b981edeb940ff88c438f72c8af876fbd6416138c85f5348c5ace92a081fa15291aa0ffb856141b871dc807f3ec2fe9c8415cac3d76579c61455ab3938bc162e139d060c8aa13fcd670febe46bf0bb579c5a"
}' http://localhost:3500/eth/v1/beacon/pool/bls_to_execution_change
./target/debug/eth-staking-smith bls-to-execution-change --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_start_index 0 --validator_index 100 --withdrawal_credentials "0x0045b91b2f60b88e7392d49ae1364b55e713d06f30e563f9f99e10994b26221d" \
--execution_address "0x71C7656EC7ab88b098defB751B7401B5f6d8976F" \
--beacon-node-uri http://beacon-node.local:5052
```

Notice `--beacon-node-uri` parameter which makes payload to be sent to beacon node

## Generating pre-signed exit message

It is possible to create pre-signed voluntary exit message for every validator that
Expand All @@ -112,6 +109,16 @@ Use `eth-staking-smith` via command line like:
Note that --validator-beacon-index and --validator-seed-index are two distinct parameter, the former being index of validator on Beacon chain, and the latter is the index of validator private key derived from the seed


### Command to send SignedBLSToExecutionChange request to Beacon node

```
./target/debug/eth-staking-smith presigned-exit-message --chain mainnet --mnemonic "entire habit bottom mention spoil clown finger wheat motion fox axis mechanic country make garment bar blind stadium sugar water scissors canyon often ketchup" --validator_seed_index 0 --validator_beacon_index 100 --epoch 300000 \
--beacon-node-uri http://beacon-node.local:5052
```

Notice `--beacon-node-uri` parameter which makes payload to be sent to beacon node


## Exporting CLI standard output into common keystores folder format

Most validator clients recognize the keystore folder format,
Expand Down
44 changes: 44 additions & 0 deletions src/beacon_node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#[derive(Debug)]
pub enum BeaconNodeError {
InvalidBeaconNodeURI,
ClientConfigurationError,
NodeCommunicationError,
Non200Response,
}

/// A trait for types that can be sent to beacon node as-is
/// without transformations
pub trait BeaconNodeExportable {
/// Export an entity as JSON
fn export(&self) -> serde_json::Value;

/// The path at beacon node where to send data
fn beacon_node_path(&self) -> String;

/// Send the JSON payload to beacon node
fn send_beacon_payload(&self, beacon_node_uri: url::Url) -> Result<(), BeaconNodeError> {
let reqwc = reqwest::blocking::Client::builder()
.build()
.map_err(|_| BeaconNodeError::ClientConfigurationError)?;
let joined_url = beacon_node_uri
.join(&self.beacon_node_path())
.map_err(|_| BeaconNodeError::InvalidBeaconNodeURI)?;
let resp = reqwc
.post(joined_url)
.header("Content-Type", "application/json")
.body(self.export().to_string())
.send();

match resp {
Ok(response) => {
let code = response.status().as_u16();
if code != 200 {
Err(BeaconNodeError::Non200Response)
} else {
Ok(())
}
}
Err(_) => Err(BeaconNodeError::NodeCommunicationError),
}
}
}
30 changes: 18 additions & 12 deletions src/bls_to_execution_change/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ use types::{
SignedRoot,
};

use crate::utils::get_withdrawal_credentials;
use crate::{beacon_node::BeaconNodeExportable, utils::get_withdrawal_credentials};

pub(crate) trait SignedBlsToExecutionChangeOperator {
fn export(&self) -> serde_json::Value;

fn validate(
self,
from_bls_withdrawal_credentials: &str,
Expand All @@ -19,18 +17,26 @@ pub(crate) trait SignedBlsToExecutionChangeOperator {
);
}

impl SignedBlsToExecutionChangeOperator for SignedBlsToExecutionChange {
impl BeaconNodeExportable for SignedBlsToExecutionChange {
fn export(&self) -> serde_json::Value {
serde_json::json!({
"message": {
"validator_index": self.message.validator_index.to_string(),
"from_bls_pubkey": self.message.from_bls_pubkey,
"to_execution_address": format!("0x{}", hex::encode(self.message.to_execution_address)),
},
"signature": self.signature.to_string()
})
serde_json::json!([
{
"message": {
"validator_index": self.message.validator_index.to_string(),
"from_bls_pubkey": self.message.from_bls_pubkey,
"to_execution_address": format!("0x{}", hex::encode(self.message.to_execution_address)),
},
"signature": self.signature.to_string()
}
])
}

fn beacon_node_path(&self) -> String {
"/eth/v1/beacon/pool/bls_to_execution_changes".to_string()
}
}

impl SignedBlsToExecutionChangeOperator for SignedBlsToExecutionChange {
fn validate(
self,
from_bls_withdrawal_credentials: &str,
Expand Down
33 changes: 22 additions & 11 deletions src/cli/bls_to_execution_change.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::bls_to_execution_change;
use crate::bls_to_execution_change::operator::SignedBlsToExecutionChangeOperator;
use crate::chain_spec::validators_root_and_spec;
use crate::{beacon_node::BeaconNodeExportable, bls_to_execution_change};
use clap::{arg, Parser};

#[derive(Clone, Parser)]
Expand All @@ -27,12 +27,12 @@ pub struct BlsToExecutionChangeSubcommandOpts {
/// and you want to generate for the 2nd validator,
/// the validator_start_index would be 1.
/// If no index specified, it will be set to 0.
#[arg(long, visible_alias = "validator_start_index")]
pub validator_start_index: u32,
#[arg(long, visible_alias = "validator_seed_index")]
pub validator_seed_index: u32,

/// On-chain beacon index of the validator.
#[arg(long, visible_alias = "validator_index")]
pub validator_index: u32,
#[arg(long, visible_alias = "validator_beacon_index")]
pub validator_beacon_index: u32,

/// BLS withdrawal credentials you used when depositing the validator.
#[arg(long, visible_alias = "bls_withdrawal_credentials")]
Expand All @@ -51,6 +51,11 @@ pub struct BlsToExecutionChangeSubcommandOpts {
/// description
#[arg(long, visible_alias = "genesis_validators_root")]
pub genesis_validators_root: Option<String>,

/// Optional beacon node URL. If set, the bls-to-execution-change message
/// will not be printed on stdout, but instead sent to beacon node
#[arg(long, visible_alias = "beacon_node_uri")]
pub beacon_node_uri: Option<url::Url>,
}

impl BlsToExecutionChangeSubcommandOpts {
Expand Down Expand Up @@ -83,8 +88,8 @@ impl BlsToExecutionChangeSubcommandOpts {
let (bls_to_execution_change, keypair) =
bls_to_execution_change::bls_execution_change_from_mnemonic(
self.mnemonic.as_bytes(),
self.validator_start_index as u64,
self.validator_index as u64,
self.validator_seed_index as u64,
self.validator_beacon_index as u64,
self.execution_address.as_str(),
);

Expand All @@ -101,10 +106,16 @@ impl BlsToExecutionChangeSubcommandOpts {
&genesis_validators_root,
);

let export = signed_bls_to_execution_change.export();
if self.beacon_node_uri.is_some() {
signed_bls_to_execution_change
.send_beacon_payload(self.beacon_node_uri.clone().unwrap())
.unwrap_or_else(|e| panic!("Failed sending beacon node payload: {:?}", e))
} else {
let export = signed_bls_to_execution_change.export();

let signed_bls_to_execution_change_json =
serde_json::to_string_pretty(&export).expect("could not parse validator export");
println!("{}", signed_bls_to_execution_change_json);
let signed_bls_to_execution_change_json =
serde_json::to_string_pretty(&export).expect("could not parse validator export");
println!("{}", signed_bls_to_execution_change_json);
}
}
}
20 changes: 16 additions & 4 deletions src/cli/presigned_exit_message.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use clap::{arg, Parser};

use crate::beacon_node::BeaconNodeExportable;
use crate::voluntary_exit::operator::SignedVoluntaryExitOperator;
use crate::{chain_spec::validators_root_and_spec, voluntary_exit};

Expand Down Expand Up @@ -47,6 +48,11 @@ pub struct PresignedExitMessageSubcommandOpts {
/// description
#[arg(long, visible_alias = "genesis_validators_root")]
pub genesis_validators_root: Option<String>,

/// Optional beacon node URL. If set, the bls-to-execution-change message
/// will not be printed on stdout, but instead sent to beacon node
#[arg(long, visible_alias = "beacon_node_uri")]
pub beacon_node_uri: Option<url::Url>,
}

impl PresignedExitMessageSubcommandOpts {
Expand Down Expand Up @@ -91,10 +97,16 @@ impl PresignedExitMessageSubcommandOpts {
&spec,
&genesis_validators_root,
);
let export = signed_voluntary_exit.export();

let presigned_exit_message_json =
serde_json::to_string_pretty(&export).expect("could not parse validator export");
println!("{}", presigned_exit_message_json);
if self.beacon_node_uri.is_some() {
signed_voluntary_exit
.send_beacon_payload(self.beacon_node_uri.clone().unwrap())
.unwrap_or_else(|e| panic!("Failed sending beacon node payload: {:?}", e))
} else {
let export = signed_voluntary_exit.export();
let presigned_exit_message_json =
serde_json::to_string_pretty(&export).expect("could not parse validator export");
println!("{}", presigned_exit_message_json);
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![forbid(unsafe_code)]
pub(crate) mod beacon_node;
pub mod bls_to_execution_change;
pub mod chain_spec;
pub mod cli;
Expand Down
28 changes: 18 additions & 10 deletions src/voluntary_exit/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@ use types::{
ChainSpec, Domain, ForkName, PublicKey, SignedRoot, SignedVoluntaryExit, VoluntaryExit,
};

pub(crate) trait SignedVoluntaryExitOperator {
fn export(&self) -> serde_json::Value;
use crate::beacon_node::BeaconNodeExportable;

pub(crate) trait SignedVoluntaryExitOperator {
fn validate(self, pubkey: &PublicKey, spec: &ChainSpec, genesis_validators_root: &Hash256);
}

impl SignedVoluntaryExitOperator for SignedVoluntaryExit {
impl BeaconNodeExportable for SignedVoluntaryExit {
fn export(&self) -> serde_json::Value {
serde_json::json!({
"message": {
"epoch": self.message.epoch.as_u64(),
"validator_index": self.message.validator_index,
},
"signature": self.signature.to_string()
})
serde_json::json!([
{
"message": {
"epoch": self.message.epoch.as_u64(),
"validator_index": self.message.validator_index,
},
"signature": self.signature.to_string()
}
])
}

fn beacon_node_path(&self) -> String {
"/eth/v1/beacon/pool/voluntary_exits".to_string()
}
}

impl SignedVoluntaryExitOperator for SignedVoluntaryExit {
fn validate(self, pubkey: &PublicKey, spec: &ChainSpec, genesis_validators_root: &Hash256) {
let fork_name = spec.fork_name_at_epoch(self.message.epoch);
let fork_version = match fork_name {
Expand Down
Loading

0 comments on commit 64ef067

Please sign in to comment.