Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Strict fee recipient #3363

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions beacon_node/execution_layer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,23 +450,6 @@ impl<T: EthSpec> ExecutionLayer<T> {
if let Some(preparation_data_entry) =
self.proposer_preparation_data().await.get(&proposer_index)
{
if let Some(suggested_fee_recipient) = self.inner.suggested_fee_recipient {
if preparation_data_entry.preparation_data.fee_recipient != suggested_fee_recipient
{
warn!(
self.log(),
"Inconsistent fee recipient";
"msg" => "The fee recipient returned from the Execution Engine differs \
from the suggested_fee_recipient set on the beacon node. This could \
indicate that fees are being diverted to another address. Please \
ensure that the value of suggested_fee_recipient is set correctly and \
that the Execution Engine is trusted.",
"proposer_index" => ?proposer_index,
"fee_recipient" => ?preparation_data_entry.preparation_data.fee_recipient,
"suggested_fee_recipient" => ?suggested_fee_recipient,
)
}
}
// The values provided via the API have first priority.
preparation_data_entry.preparation_data.fee_recipient
} else if let Some(address) = self.inner.suggested_fee_recipient {
Expand Down Expand Up @@ -689,6 +672,19 @@ impl<T: EthSpec> ExecutionLayer<T> {
.get_payload_v1::<T>(payload_id)
.await
.map(|full_payload| {
if full_payload.fee_recipient != suggested_fee_recipient {
error!(
self.log(),
"Inconsistent fee recipient";
"msg" => "The fee recipient returned from the Execution Engine differs \
from the suggested_fee_recipient set on the beacon node. This could \
indicate that fees are being diverted to another address. Please \
ensure that the value of suggested_fee_recipient is set correctly and \
that the Execution Engine is trusted.",
"fee_recipient" => ?full_payload.fee_recipient,
"suggested_fee_recipient" => ?suggested_fee_recipient,
);
}
if f(self, &full_payload).is_some() {
warn!(
self.log(),
Expand Down
12 changes: 11 additions & 1 deletion book/src/suggested-fee-recipient.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ coinbase and the recipient of other fees or rewards.

There is no guarantee that an execution node will use the `suggested_fee_recipient` to collect fees,
it may use any address it chooses. It is assumed that an honest execution node *will* use the
`suggested_fee_recipient`, but users should note this trust assumption.
`suggested_fee_recipient`, but users should note this trust assumption. Check out the
[strict fee recipient](#strict-fee-recipient) section for how to mitigate this assumption.

The `suggested_fee_recipient` can be provided to the VC, who will transmit it to the BN. The BN also
has a choice regarding the fee recipient it passes to the execution node, creating another
Expand Down Expand Up @@ -61,6 +62,15 @@ validators where a `suggested_fee_recipient` is not loaded from another method.
The `--suggested-fee-recipient` can be provided to the BN to act as a default value when the
validator client does not transmit a `suggested_fee_recipient` to the BN.

## Strict Fee Recipient

If the flag `--strict-fee-recipient` is set in the validator client, Lighthouse will refuse to sign any block whose
`fee_recipient` does not match the `suggested_fee_recipient` sent by this validator. This applies to both the normal
block proposal flow, as well as block proposals through the builder API. Proposals through the builder API are more likely
to have a discrepancy in `fee_recipient` so you should be aware of how your connected relay sends proposer payments before
using this flag. If this flag is used, a fee recipient mismatch in the builder API flow will result in a fallback to the
local execution engine for payload construction, where a strict fee recipient check will still be applied.

## Setting the fee recipient dynamically using the keymanager API

When the [validator client API](api-vc.md) is enabled, the
Expand Down
9 changes: 9 additions & 0 deletions consensus/types/src/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub trait ExecPayload<T: EthSpec>:
fn block_number(&self) -> u64;
fn timestamp(&self) -> u64;
fn block_hash(&self) -> ExecutionBlockHash;
fn fee_recipient(&self) -> Address;
}

impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
Expand Down Expand Up @@ -74,6 +75,10 @@ impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
fn block_hash(&self) -> ExecutionBlockHash {
self.execution_payload.block_hash
}

fn fee_recipient(&self) -> Address {
self.execution_payload.fee_recipient
}
}

impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
Expand Down Expand Up @@ -104,6 +109,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
fn block_hash(&self) -> ExecutionBlockHash {
self.execution_payload_header.block_hash
}

fn fee_recipient(&self) -> Address {
self.execution_payload_header.fee_recipient
}
}

#[derive(Debug, Clone, TestRandom, Serialize, Deserialize, Derivative)]
Expand Down
13 changes: 13 additions & 0 deletions lighthouse/tests/validator_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,3 +388,16 @@ fn no_doppelganger_protection_flag() {
.run()
.with_config(|config| assert!(!config.enable_doppelganger_protection));
}
#[test]
fn strict_fee_recipient_flag() {
CommandLineTest::new()
.flag("strict-fee-recipient", None)
.run()
.with_config(|config| assert!(config.strict_fee_recipient));
}
#[test]
fn no_strict_fee_recipient_flag() {
CommandLineTest::new()
.run()
.with_config(|config| assert!(!config.strict_fee_recipient));
}
23 changes: 23 additions & 0 deletions validator_client/src/block_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub struct BlockServiceBuilder<T, E: EthSpec> {
graffiti: Option<Graffiti>,
graffiti_file: Option<GraffitiFile>,
private_tx_proposals: bool,
strict_fee_recipient: bool,
}

impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
Expand All @@ -57,6 +58,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
graffiti: None,
graffiti_file: None,
private_tx_proposals: false,
strict_fee_recipient: false,
}
}

Expand Down Expand Up @@ -95,6 +97,11 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
self
}

pub fn strict_fee_recipient(mut self, strict_fee_recipient: bool) -> Self {
self.strict_fee_recipient = strict_fee_recipient;
self
}

pub fn build(self) -> Result<BlockService<T, E>, String> {
Ok(BlockService {
inner: Arc::new(Inner {
Expand All @@ -113,6 +120,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
graffiti: self.graffiti,
graffiti_file: self.graffiti_file,
private_tx_proposals: self.private_tx_proposals,
strict_fee_recipient: self.strict_fee_recipient,
}),
})
}
Expand All @@ -127,6 +135,7 @@ pub struct Inner<T, E: EthSpec> {
graffiti: Option<Graffiti>,
graffiti_file: Option<GraffitiFile>,
private_tx_proposals: bool,
strict_fee_recipient: bool,
}

/// Attempts to produce attestations for any block producer(s) at the start of the epoch.
Expand Down Expand Up @@ -328,6 +337,9 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
let self_ref = &self;
let proposer_index = self.validator_store.validator_index(&validator_pubkey);
let validator_pubkey_ref = &validator_pubkey;
let fee_recipient = self.validator_store.get_fee_recipient(&validator_pubkey);

let strict_fee_recipient = self.strict_fee_recipient;
// Request block from first responsive beacon node.
let block = self
.beacon_nodes
Expand Down Expand Up @@ -372,6 +384,17 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
};
drop(get_timer);

// Ensure the correctness of the execution payload's fee recipient.
if strict_fee_recipient {
if let Ok(execution_payload) = block.body().execution_payload() {
if Some(execution_payload.fee_recipient()) != fee_recipient {
return Err(BlockError::Recoverable(
"Incorrect fee recipient used by builder".to_string(),
));
}
}
}

if proposer_index != Some(block.proposer_index()) {
return Err(BlockError::Recoverable(
"Proposer index does not match block proposer. Beacon chain re-orged"
Expand Down
13 changes: 13 additions & 0 deletions validator_client/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,17 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
execution payload construction during proposals.")
.takes_value(false),
)
.arg(
Arg::with_name("strict-fee-recipient")
.long("strict-fee-recipient")
.help("If this flag is set, Lighthouse will refuse to sign any block whose \
`fee_recipient` does not match the `suggested_fee_recipient` sent by this validator. \
This applies to both the normal block proposal flow, as well as block proposals \
through the builder API. Proposals through the builder API are more likely to have a \
discrepancy in `fee_recipient` so you should be aware of how your connected relay \
sends proposer payments before using this flag. If this flag is used, a fee recipient \
mismatch in the builder API flow will result in a fallback to the local execution engine \
for payload construction, where a strict fee recipient check will still be applied.")
.takes_value(false),
)
}
8 changes: 8 additions & 0 deletions validator_client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ pub struct Config {
/// A list of custom certificates that the validator client will additionally use when
/// connecting to a beacon node over SSL/TLS.
pub beacon_nodes_tls_certs: Option<Vec<PathBuf>>,
/// Enabling this will make sure the validator client never signs a block whose `fee_recipient`
/// does not match the `suggested_fee_recipient`.
pub strict_fee_recipient: bool,
}

impl Default for Config {
Expand Down Expand Up @@ -89,6 +92,7 @@ impl Default for Config {
enable_doppelganger_protection: false,
beacon_nodes_tls_certs: None,
private_tx_proposals: false,
strict_fee_recipient: false,
}
}
}
Expand Down Expand Up @@ -300,6 +304,10 @@ impl Config {
config.private_tx_proposals = true;
}

if cli_args.is_present("strict-fee-recipient") {
config.strict_fee_recipient = true;
}

Ok(config)
}
}
Expand Down
1 change: 1 addition & 0 deletions validator_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
.graffiti(config.graffiti)
.graffiti_file(config.graffiti_file.clone())
.private_tx_proposals(config.private_tx_proposals)
.strict_fee_recipient(config.strict_fee_recipient)
.build()?;

let attestation_service = AttestationServiceBuilder::new()
Expand Down