diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 1f55af8ae..0ab7be80e 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1564,12 +1564,13 @@ impl BeaconChain { &self, randao_reveal: Signature, slot: Slot, + validator_graffiti: Option, ) -> Result, BlockProductionError> { let state = self .state_at_slot(slot - 1, StateSkipConfig::WithStateRoots) .map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?; - self.produce_block_on_state(state, slot, randao_reveal) + self.produce_block_on_state(state, slot, randao_reveal, validator_graffiti) } /// Produce a block for some `slot` upon the given `state`. @@ -1585,6 +1586,7 @@ impl BeaconChain { mut state: BeaconState, produce_at_slot: Slot, randao_reveal: Signature, + validator_graffiti: Option, ) -> Result, BlockProductionError> { metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS); let timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES); @@ -1652,6 +1654,12 @@ impl BeaconChain { } } + // Override the beacon node's graffiti with graffiti from the validator, if present. + let graffiti = match validator_graffiti { + Some(graffiti) => graffiti, + None => self.graffiti, + }; + let mut block = SignedBeaconBlock { message: BeaconBlock { slot: state.slot, @@ -1661,7 +1669,7 @@ impl BeaconChain { body: BeaconBlockBody { randao_reveal, eth1_data, - graffiti: self.graffiti, + graffiti, proposer_slashings: proposer_slashings.into(), attester_slashings: attester_slashings.into(), attestations: self diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 76993f87a..5dccde9ad 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -520,7 +520,7 @@ where let (block, state) = self .chain - .produce_block_on_state(state, slot, randao_reveal) + .produce_block_on_state(state, slot, randao_reveal, None) .expect("should produce block"); let signed_block = block.sign(sk, &state.fork, state.genesis_validators_root, &self.spec); diff --git a/beacon_node/rest_api/src/validator.rs b/beacon_node/rest_api/src/validator.rs index 7d4ab6b84..43a87a3ad 100644 --- a/beacon_node/rest_api/src/validator.rs +++ b/beacon_node/rest_api/src/validator.rs @@ -1,4 +1,6 @@ -use crate::helpers::{check_content_type_for_json, publish_beacon_block_to_network}; +use crate::helpers::{ + check_content_type_for_json, parse_hex_ssz_bytes, publish_beacon_block_to_network, +}; use crate::response_builder::ResponseBuilder; use crate::{ApiError, ApiResult, NetworkChannel, UrlQuery}; use beacon_chain::{ @@ -288,8 +290,14 @@ pub fn get_new_beacon_block( let slot = query.slot()?; let randao_reveal = query.randao_reveal()?; + let validator_graffiti = if let Some((_key, value)) = query.first_of_opt(&["graffiti"]) { + Some(parse_hex_ssz_bytes(&value)?) + } else { + None + }; + let (new_block, _state) = beacon_chain - .produce_block(randao_reveal, slot) + .produce_block(randao_reveal, slot, validator_graffiti) .map_err(|e| { error!( log, diff --git a/beacon_node/rest_api/tests/test.rs b/beacon_node/rest_api/tests/test.rs index d5bda936b..05abc1609 100644 --- a/beacon_node/rest_api/tests/test.rs +++ b/beacon_node/rest_api/tests/test.rs @@ -460,7 +460,7 @@ fn validator_block_post() { remote_node .http .validator() - .produce_block(slot, randao_reveal), + .produce_block(slot, randao_reveal, None), ) .expect("should fetch block from http api"); @@ -547,7 +547,7 @@ fn validator_block_get() { remote_node .http .validator() - .produce_block(slot, randao_reveal.clone()), + .produce_block(slot, randao_reveal.clone(), None), ) .expect("should fetch block from http api"); @@ -555,7 +555,50 @@ fn validator_block_get() { .client .beacon_chain() .expect("client should have beacon chain") - .produce_block(randao_reveal, slot) + .produce_block(randao_reveal, slot, None) + .expect("should produce block"); + + assert_eq!( + block, expected_block, + "the block returned from the API should be as expected" + ); +} + +#[test] +fn validator_block_get_with_graffiti() { + let mut env = build_env(); + + let spec = &E::default_spec(); + + let node = build_node(&mut env, testing_client_config()); + let remote_node = node.remote_node().expect("should produce remote node"); + + let beacon_chain = node + .client + .beacon_chain() + .expect("client should have beacon chain"); + + let slot = Slot::new(1); + let randao_reveal = get_randao_reveal(beacon_chain, slot, spec); + + let block = env + .runtime() + .block_on(remote_node.http.validator().produce_block( + slot, + randao_reveal.clone(), + Some(*b"test-graffiti-test-graffiti-test"), + )) + .expect("should fetch block from http api"); + + let (expected_block, _state) = node + .client + .beacon_chain() + .expect("client should have beacon chain") + .produce_block( + randao_reveal, + slot, + Some(*b"test-graffiti-test-graffiti-test"), + ) .expect("should produce block"); assert_eq!( diff --git a/common/remote_beacon_node/src/lib.rs b/common/remote_beacon_node/src/lib.rs index 4afc40303..199efefd9 100644 --- a/common/remote_beacon_node/src/lib.rs +++ b/common/remote_beacon_node/src/lib.rs @@ -11,8 +11,8 @@ use std::marker::PhantomData; use std::time::Duration; use types::{ Attestation, AttestationData, AttesterSlashing, BeaconBlock, BeaconState, CommitteeIndex, - Epoch, EthSpec, Fork, Hash256, ProposerSlashing, PublicKey, PublicKeyBytes, Signature, - SignedAggregateAndProof, SignedBeaconBlock, Slot, SubnetId, + Epoch, EthSpec, Fork, Graffiti, Hash256, ProposerSlashing, PublicKey, PublicKeyBytes, + Signature, SignedAggregateAndProof, SignedBeaconBlock, Slot, SubnetId, }; use url::Url; @@ -313,18 +313,21 @@ impl Validator { &self, slot: Slot, randao_reveal: Signature, + graffiti: Option, ) -> Result, Error> { let client = self.0.clone(); let url = self.url("block")?; - client - .json_get::>( - url, - vec![ - ("slot".into(), format!("{}", slot.as_u64())), - ("randao_reveal".into(), as_ssz_hex_string(&randao_reveal)), - ], - ) - .await + + let mut query_pairs = vec![ + ("slot".into(), format!("{}", slot.as_u64())), + ("randao_reveal".into(), as_ssz_hex_string(&randao_reveal)), + ]; + + if let Some(graffiti_bytes) = graffiti { + query_pairs.push(("graffiti".into(), as_ssz_hex_string(&graffiti_bytes))); + } + + client.json_get::>(url, query_pairs).await } /// Subscribes a list of validators to particular slots for attestation production/publication. diff --git a/validator_client/src/block_service.rs b/validator_client/src/block_service.rs index 058706965..60d1f4d55 100644 --- a/validator_client/src/block_service.rs +++ b/validator_client/src/block_service.rs @@ -7,7 +7,7 @@ use slog::{crit, debug, error, info, trace, warn}; use slot_clock::SlotClock; use std::ops::Deref; use std::sync::Arc; -use types::{EthSpec, PublicKey, Slot}; +use types::{EthSpec, Graffiti, PublicKey, Slot}; /// Builds a `BlockService`. pub struct BlockServiceBuilder { @@ -15,6 +15,7 @@ pub struct BlockServiceBuilder { slot_clock: Option>, beacon_node: Option>, context: Option>, + graffiti: Option, } impl BlockServiceBuilder { @@ -24,6 +25,7 @@ impl BlockServiceBuilder { slot_clock: None, beacon_node: None, context: None, + graffiti: None, } } @@ -47,6 +49,11 @@ impl BlockServiceBuilder { self } + pub fn graffiti(mut self, graffiti: Option) -> Self { + self.graffiti = graffiti; + self + } + pub fn build(self) -> Result, String> { Ok(BlockService { inner: Arc::new(Inner { @@ -62,6 +69,7 @@ impl BlockServiceBuilder { context: self .context .ok_or_else(|| "Cannot build BlockService without runtime_context")?, + graffiti: self.graffiti, }), }) } @@ -73,6 +81,7 @@ pub struct Inner { slot_clock: Arc, beacon_node: RemoteBeaconNode, context: RuntimeContext, + graffiti: Option, } /// Attempts to produce attestations for any block producer(s) at the start of the epoch. @@ -214,7 +223,7 @@ impl BlockService { .beacon_node .http .validator() - .produce_block(slot, randao_reveal) + .produce_block(slot, randao_reveal, self.graffiti) .await .map_err(|e| format!("Error from beacon node when producing block: {:?}", e))?; diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 3479f4b9d..ed320c24c 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -60,4 +60,12 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { node is not synced.", ), ) + // This overwrites the graffiti configured in the beacon node. + .arg( + Arg::with_name("graffiti") + .long("graffiti") + .help("Specify your custom graffiti to be included in blocks.") + .value_name("GRAFFITI") + .takes_value(true) + ) } diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 4f1b2079e..482c4ed70 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -2,6 +2,7 @@ use clap::ArgMatches; use clap_utils::{parse_optional, parse_path_with_default_in_home_dir}; use serde_derive::{Deserialize, Serialize}; use std::path::PathBuf; +use types::{Graffiti, GRAFFITI_BYTES_LEN}; pub const DEFAULT_HTTP_SERVER: &str = "http://localhost:5052/"; pub const DEFAULT_DATA_DIR: &str = ".lighthouse/validators"; @@ -27,6 +28,8 @@ pub struct Config { pub strict_lockfiles: bool, /// If true, don't scan the validators dir for new keystores. pub disable_auto_discover: bool, + /// Graffiti to be inserted everytime we create a block. + pub graffiti: Option, } impl Default for Config { @@ -45,6 +48,7 @@ impl Default for Config { allow_unsynced_beacon_node: false, strict_lockfiles: false, disable_auto_discover: false, + graffiti: None, } } } @@ -80,6 +84,26 @@ impl Config { config.secrets_dir = secrets_dir; } + if let Some(input_graffiti) = cli_args.value_of("graffiti") { + let graffiti_bytes = input_graffiti.as_bytes(); + if graffiti_bytes.len() > GRAFFITI_BYTES_LEN { + return Err(format!( + "Your graffiti is too long! {} bytes maximum!", + GRAFFITI_BYTES_LEN + )); + } else { + // Default graffiti to all 0 bytes. + let mut graffiti = Graffiti::default(); + + // Copy the provided bytes over. + // + // Panic-free because `graffiti_bytes.len()` <= `GRAFFITI_BYTES_LEN`. + graffiti[..graffiti_bytes.len()].copy_from_slice(&graffiti_bytes); + + config.graffiti = Some(graffiti); + } + } + Ok(config) } } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index e5d8428ce..220d82a66 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -216,6 +216,7 @@ impl ProductionValidatorClient { .validator_store(validator_store.clone()) .beacon_node(beacon_node.clone()) .runtime_context(context.service_context("block".into())) + .graffiti(config.graffiti) .build()?; let attestation_service = AttestationServiceBuilder::new()