Permit a null
LVH from an INVALID
response to newPayload
(#4037)
## Issue Addressed NA ## Proposed Changes As discovered in #4034, Lighthouse is not accepting `latest_valid_hash == None` in an `INVALID` response to `newPayload`. The `null`/`None` response *was* illegal at one point, however it was added in https://github.com/ethereum/execution-apis/pull/254. This PR brings Lighthouse in line with the standard and should fix the root cause of what #4034 patched around. ## Additional Info NA
This commit is contained in:
parent
2b6348781a
commit
cac3a66be4
@ -5016,26 +5016,43 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
"status" => ?status
|
"status" => ?status
|
||||||
);
|
);
|
||||||
|
|
||||||
// This implies that the terminal block was invalid. We are being explicit in
|
match latest_valid_hash {
|
||||||
// invalidating only the head block in this case.
|
// The `latest_valid_hash` is set to `None` when the EE
|
||||||
if latest_valid_hash == ExecutionBlockHash::zero() {
|
// "cannot determine the ancestor of the invalid
|
||||||
self.process_invalid_execution_payload(
|
// payload". In such a scenario we should only
|
||||||
&InvalidationOperation::InvalidateOne {
|
// invalidate the head block and nothing else.
|
||||||
block_root: head_block_root,
|
None => {
|
||||||
},
|
self.process_invalid_execution_payload(
|
||||||
)
|
&InvalidationOperation::InvalidateOne {
|
||||||
.await?;
|
block_root: head_block_root,
|
||||||
} else {
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
// An all-zeros execution block hash implies that
|
||||||
|
// the terminal block was invalid. We are being
|
||||||
|
// explicit in invalidating only the head block in
|
||||||
|
// this case.
|
||||||
|
Some(hash) if hash == ExecutionBlockHash::zero() => {
|
||||||
|
self.process_invalid_execution_payload(
|
||||||
|
&InvalidationOperation::InvalidateOne {
|
||||||
|
block_root: head_block_root,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
// The execution engine has stated that all blocks between the
|
// The execution engine has stated that all blocks between the
|
||||||
// `head_execution_block_hash` and `latest_valid_hash` are invalid.
|
// `head_execution_block_hash` and `latest_valid_hash` are invalid.
|
||||||
self.process_invalid_execution_payload(
|
Some(latest_valid_hash) => {
|
||||||
&InvalidationOperation::InvalidateMany {
|
self.process_invalid_execution_payload(
|
||||||
head_block_root,
|
&InvalidationOperation::InvalidateMany {
|
||||||
always_invalidate_head: true,
|
head_block_root,
|
||||||
latest_valid_ancestor: latest_valid_hash,
|
always_invalidate_head: true,
|
||||||
},
|
latest_valid_ancestor: latest_valid_hash,
|
||||||
)
|
},
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(BeaconChainError::ExecutionForkChoiceUpdateInvalid { status })
|
Err(BeaconChainError::ExecutionForkChoiceUpdateInvalid { status })
|
||||||
|
@ -172,21 +172,34 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>(
|
|||||||
"method" => "new_payload",
|
"method" => "new_payload",
|
||||||
);
|
);
|
||||||
|
|
||||||
// latest_valid_hash == 0 implies that this was the terminal block
|
// Only trigger payload invalidation in fork choice if the
|
||||||
// Hence, we don't need to run `BeaconChain::process_invalid_execution_payload`.
|
// `latest_valid_hash` is `Some` and non-zero.
|
||||||
if latest_valid_hash == ExecutionBlockHash::zero() {
|
//
|
||||||
return Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into());
|
// A `None` latest valid hash indicates that the EE was unable
|
||||||
|
// to determine the most recent valid ancestor. Since `block`
|
||||||
|
// has not yet been applied to fork choice, there's nothing to
|
||||||
|
// invalidate.
|
||||||
|
//
|
||||||
|
// An all-zeros payload indicates that an EIP-3675 check has
|
||||||
|
// failed regarding the validity of the terminal block. Rather
|
||||||
|
// than iterating back in the chain to find the terminal block
|
||||||
|
// and invalidating that, we simply reject this block without
|
||||||
|
// invalidating anything else.
|
||||||
|
if let Some(latest_valid_hash) =
|
||||||
|
latest_valid_hash.filter(|hash| *hash != ExecutionBlockHash::zero())
|
||||||
|
{
|
||||||
|
// This block has not yet been applied to fork choice, so the latest block that was
|
||||||
|
// imported to fork choice was the parent.
|
||||||
|
let latest_root = block.parent_root();
|
||||||
|
|
||||||
|
chain
|
||||||
|
.process_invalid_execution_payload(&InvalidationOperation::InvalidateMany {
|
||||||
|
head_block_root: latest_root,
|
||||||
|
always_invalidate_head: false,
|
||||||
|
latest_valid_ancestor: latest_valid_hash,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
// This block has not yet been applied to fork choice, so the latest block that was
|
|
||||||
// imported to fork choice was the parent.
|
|
||||||
let latest_root = block.parent_root();
|
|
||||||
chain
|
|
||||||
.process_invalid_execution_payload(&InvalidationOperation::InvalidateMany {
|
|
||||||
head_block_root: latest_root,
|
|
||||||
always_invalidate_head: false,
|
|
||||||
latest_valid_ancestor: latest_valid_hash,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into())
|
Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into())
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,9 @@ use types::ExecutionBlockHash;
|
|||||||
pub enum PayloadStatus {
|
pub enum PayloadStatus {
|
||||||
Valid,
|
Valid,
|
||||||
Invalid {
|
Invalid {
|
||||||
latest_valid_hash: ExecutionBlockHash,
|
/// The EE will provide a `None` LVH when it is unable to determine the
|
||||||
|
/// latest valid ancestor.
|
||||||
|
latest_valid_hash: Option<ExecutionBlockHash>,
|
||||||
validation_error: Option<String>,
|
validation_error: Option<String>,
|
||||||
},
|
},
|
||||||
Syncing,
|
Syncing,
|
||||||
@ -55,22 +57,10 @@ pub fn process_payload_status(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PayloadStatusV1Status::Invalid => {
|
PayloadStatusV1Status::Invalid => Ok(PayloadStatus::Invalid {
|
||||||
if let Some(latest_valid_hash) = response.latest_valid_hash {
|
latest_valid_hash: response.latest_valid_hash,
|
||||||
// The response is only valid if `latest_valid_hash` is not `null`.
|
validation_error: response.validation_error,
|
||||||
Ok(PayloadStatus::Invalid {
|
}),
|
||||||
latest_valid_hash,
|
|
||||||
validation_error: response.validation_error.clone(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(EngineError::Api {
|
|
||||||
error: ApiError::BadResponse(
|
|
||||||
"new_payload: response.status = INVALID but null latest_valid_hash"
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PayloadStatusV1Status::InvalidBlockHash => {
|
PayloadStatusV1Status::InvalidBlockHash => {
|
||||||
// In the interests of being liberal with what we accept, only raise a
|
// In the interests of being liberal with what we accept, only raise a
|
||||||
// warning here.
|
// warning here.
|
||||||
|
@ -7,7 +7,7 @@ use std::{env, fs::File};
|
|||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use unused_port::unused_tcp_port;
|
use unused_port::unused_tcp_port;
|
||||||
|
|
||||||
// const GETH_BRANCH: &str = "master";
|
const GETH_BRANCH: &str = "master";
|
||||||
const GETH_REPO_URL: &str = "https://github.com/ethereum/go-ethereum";
|
const GETH_REPO_URL: &str = "https://github.com/ethereum/go-ethereum";
|
||||||
|
|
||||||
pub fn build_result(repo_dir: &Path) -> Output {
|
pub fn build_result(repo_dir: &Path) -> Output {
|
||||||
@ -27,9 +27,7 @@ pub fn build(execution_clients_dir: &Path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the latest tag on the branch
|
// Get the latest tag on the branch
|
||||||
// TODO: Update when version is corrected
|
let last_release = build_utils::get_latest_release(&repo_dir, GETH_BRANCH).unwrap();
|
||||||
// let last_release = build_utils::get_latest_release(&repo_dir, GETH_BRANCH).unwrap();
|
|
||||||
let last_release = "v1.11.1";
|
|
||||||
build_utils::checkout(&repo_dir, dbg!(&last_release)).unwrap();
|
build_utils::checkout(&repo_dir, dbg!(&last_release)).unwrap();
|
||||||
|
|
||||||
// Build geth
|
// Build geth
|
||||||
|
@ -427,7 +427,16 @@ impl<E: GenericExecutionEngine> TestRig<E> {
|
|||||||
.notify_new_payload(&invalid_payload)
|
.notify_new_payload(&invalid_payload)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(status, PayloadStatus::InvalidBlockHash { .. }));
|
assert!(matches!(
|
||||||
|
status,
|
||||||
|
PayloadStatus::InvalidBlockHash { .. }
|
||||||
|
// Geth is returning `INVALID` with a `null` LVH to indicate it
|
||||||
|
// does not know the invalid ancestor.
|
||||||
|
| PayloadStatus::Invalid {
|
||||||
|
latest_valid_hash: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Execution Engine A:
|
* Execution Engine A:
|
||||||
|
Loading…
Reference in New Issue
Block a user