mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-25 00:38:22 +00:00
unstable merge
This commit is contained in:
@@ -4845,43 +4845,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
// get_attestation_score(parent, parent_payload_status) where parent_payload_status
|
||||
// is determined by the head block's relationship to its parent.
|
||||
let head_weight = info.head_node.weight();
|
||||
let parent_weight = if let Ok(head_payload_status) = info.head_node.parent_payload_status()
|
||||
{
|
||||
// Post-GLOAS: use the payload-filtered weight matching how the head
|
||||
// extends from its parent.
|
||||
match head_payload_status {
|
||||
proto_array::PayloadStatus::Full => {
|
||||
info.parent_node.full_payload_weight().map_err(|()| {
|
||||
Box::new(ProposerHeadError::Error(
|
||||
Error::ProposerHeadForkChoiceError(
|
||||
fork_choice::Error::ProtoArrayError(
|
||||
proto_array::Error::InvalidNodeVariant {
|
||||
block_root: info.parent_node.root(),
|
||||
},
|
||||
),
|
||||
),
|
||||
))
|
||||
})?
|
||||
let parent_weight =
|
||||
if let (Ok(head_payload_status), Ok(parent_v29)) = (
|
||||
info.head_node.parent_payload_status(),
|
||||
info.parent_node.as_v29(),
|
||||
) {
|
||||
// Post-GLOAS: use the payload-filtered weight matching how the head
|
||||
// extends from its parent.
|
||||
match head_payload_status {
|
||||
proto_array::PayloadStatus::Full => parent_v29.full_payload_weight,
|
||||
proto_array::PayloadStatus::Empty => parent_v29.empty_payload_weight,
|
||||
proto_array::PayloadStatus::Pending => info.parent_node.weight(),
|
||||
}
|
||||
proto_array::PayloadStatus::Empty => {
|
||||
info.parent_node.empty_payload_weight().map_err(|()| {
|
||||
Box::new(ProposerHeadError::Error(
|
||||
Error::ProposerHeadForkChoiceError(
|
||||
fork_choice::Error::ProtoArrayError(
|
||||
proto_array::Error::InvalidNodeVariant {
|
||||
block_root: info.parent_node.root(),
|
||||
},
|
||||
),
|
||||
),
|
||||
))
|
||||
})?
|
||||
}
|
||||
proto_array::PayloadStatus::Pending => info.parent_node.weight(),
|
||||
}
|
||||
} else {
|
||||
// Pre-GLOAS (V17): use total weight.
|
||||
info.parent_node.weight()
|
||||
};
|
||||
} else {
|
||||
// Pre-GLOAS or fork boundary: use total weight.
|
||||
info.parent_node.weight()
|
||||
};
|
||||
|
||||
let (head_weak, parent_strong) = if fork_choice_slot == re_org_block_slot {
|
||||
(
|
||||
|
||||
@@ -2104,9 +2104,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
|
||||
let execution_status_string = node
|
||||
.execution_status()
|
||||
.ok()
|
||||
.map(|status| status.to_string())
|
||||
.unwrap_or_else(|| "n/a".to_string());
|
||||
.map_or_else(|_| "irrelevant".to_string(), |s| s.to_string());
|
||||
|
||||
ForkChoiceNode {
|
||||
slot: node.slot(),
|
||||
|
||||
@@ -182,8 +182,11 @@ impl Default for ProposerBoost {
|
||||
/// ancestors can compare children using payload-aware tie breaking.
|
||||
#[derive(Clone, PartialEq, Debug, Copy)]
|
||||
pub struct NodeDelta {
|
||||
/// Total weight change for the node. All votes contribute regardless of payload status.
|
||||
pub delta: i64,
|
||||
/// Weight change from `PayloadStatus::Empty` votes.
|
||||
pub empty_delta: i64,
|
||||
/// Weight change from `PayloadStatus::Full` votes.
|
||||
pub full_delta: i64,
|
||||
}
|
||||
|
||||
@@ -399,6 +402,7 @@ impl ProtoArray {
|
||||
|
||||
// Apply the delta to the node.
|
||||
if execution_status_is_invalid {
|
||||
// Invalid nodes always have a weight of 0.
|
||||
*node.weight_mut() = 0;
|
||||
} else {
|
||||
*node.weight_mut() = apply_delta(node.weight(), delta, node_index)?;
|
||||
@@ -1127,10 +1131,9 @@ impl ProtoArray {
|
||||
);
|
||||
let no_change = (parent.best_child(), parent.best_descendant());
|
||||
|
||||
// For V29 (GLOAS) parents, the spec's virtual tree model requires choosing
|
||||
// FULL or EMPTY direction at each node BEFORE considering concrete children.
|
||||
// Only children whose parent_payload_status matches the preferred direction
|
||||
// are eligible for best_child. This is PRIMARY, not a tiebreaker.
|
||||
// For V29 (GLOAS) parents, the spec's virtual tree model determines a preferred
|
||||
// FULL or EMPTY direction at each node. Weight is the primary selector among
|
||||
// viable children; direction matching is the tiebreaker when weights are equal.
|
||||
let child_matches_dir = child_matches_parent_payload_preference(
|
||||
parent,
|
||||
child,
|
||||
|
||||
@@ -4,7 +4,10 @@ use crate::common::{
|
||||
get_attestation_participation_flag_indices, increase_balance, initiate_validator_exit,
|
||||
slash_validator,
|
||||
};
|
||||
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
|
||||
use crate::per_block_processing::builder::{
|
||||
convert_validator_index_to_builder_index, is_builder_index,
|
||||
};
|
||||
use crate::per_block_processing::errors::{BlockProcessingError, ExitInvalid, IntoWithIndex};
|
||||
use crate::per_block_processing::verify_payload_attestation::verify_payload_attestation;
|
||||
use bls::{PublicKeyBytes, SignatureBytes};
|
||||
use ssz_types::FixedVector;
|
||||
@@ -507,7 +510,26 @@ pub fn process_exits<E: EthSpec>(
|
||||
// Verify and apply each exit in series. We iterate in series because higher-index exits may
|
||||
// become invalid due to the application of lower-index ones.
|
||||
for (i, exit) in voluntary_exits.iter().enumerate() {
|
||||
verify_exit(state, None, exit, verify_signatures, spec)
|
||||
// Exits must specify an epoch when they become valid; they are not valid before then.
|
||||
let current_epoch = state.current_epoch();
|
||||
if current_epoch < exit.message.epoch {
|
||||
return Err(BlockOperationError::invalid(ExitInvalid::FutureEpoch {
|
||||
state: current_epoch,
|
||||
exit: exit.message.epoch,
|
||||
})
|
||||
.into_with_index(i));
|
||||
}
|
||||
|
||||
// [New in Gloas:EIP7732]
|
||||
if state.fork_name_unchecked().gloas_enabled()
|
||||
&& is_builder_index(exit.message.validator_index)
|
||||
{
|
||||
process_builder_voluntary_exit(state, exit, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
continue;
|
||||
}
|
||||
|
||||
verify_exit(state, Some(current_epoch), exit, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
initiate_validator_exit(state, exit.message.validator_index as usize, spec)?;
|
||||
@@ -515,6 +537,87 @@ pub fn process_exits<E: EthSpec>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a builder voluntary exit. [New in Gloas:EIP7732]
|
||||
fn process_builder_voluntary_exit<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
signed_exit: &SignedVoluntaryExit,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockOperationError<ExitInvalid>> {
|
||||
let builder_index =
|
||||
convert_validator_index_to_builder_index(signed_exit.message.validator_index);
|
||||
|
||||
let builder = state
|
||||
.builders()?
|
||||
.get(builder_index as usize)
|
||||
.cloned()
|
||||
.ok_or(BlockOperationError::invalid(ExitInvalid::ValidatorUnknown(
|
||||
signed_exit.message.validator_index,
|
||||
)))?;
|
||||
|
||||
// Verify the builder is active
|
||||
let finalized_epoch = state.finalized_checkpoint().epoch;
|
||||
if !builder.is_active_at_finalized_epoch(finalized_epoch, spec) {
|
||||
return Err(BlockOperationError::invalid(ExitInvalid::NotActive(
|
||||
signed_exit.message.validator_index,
|
||||
)));
|
||||
}
|
||||
|
||||
// Only exit builder if it has no pending withdrawals in the queue
|
||||
let pending_balance = state.get_pending_balance_to_withdraw_for_builder(builder_index)?;
|
||||
if pending_balance != 0 {
|
||||
return Err(BlockOperationError::invalid(
|
||||
ExitInvalid::PendingWithdrawalInQueue(signed_exit.message.validator_index),
|
||||
));
|
||||
}
|
||||
|
||||
// Verify signature (using EIP-7044 domain: capella_fork_version for Deneb+)
|
||||
if verify_signatures.is_true() {
|
||||
let pubkey = builder.pubkey;
|
||||
let domain = spec.compute_domain(
|
||||
Domain::VoluntaryExit,
|
||||
spec.capella_fork_version,
|
||||
state.genesis_validators_root(),
|
||||
);
|
||||
let message = signed_exit.message.signing_root(domain);
|
||||
// TODO(gloas): use builder pubkey cache once available
|
||||
let bls_pubkey = pubkey
|
||||
.decompress()
|
||||
.map_err(|_| BlockOperationError::invalid(ExitInvalid::BadSignature))?;
|
||||
if !signed_exit.signature.verify(&bls_pubkey, message) {
|
||||
return Err(BlockOperationError::invalid(ExitInvalid::BadSignature));
|
||||
}
|
||||
}
|
||||
|
||||
// Initiate builder exit
|
||||
initiate_builder_exit(state, builder_index, spec)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initiate the exit of a builder. [New in Gloas:EIP7732]
|
||||
fn initiate_builder_exit<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
builder_index: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
let current_epoch = state.current_epoch();
|
||||
let builder = state
|
||||
.builders_mut()?
|
||||
.get_mut(builder_index as usize)
|
||||
.ok_or(BeaconStateError::UnknownBuilder(builder_index))?;
|
||||
|
||||
// Return if builder already initiated exit
|
||||
if builder.withdrawable_epoch != spec.far_future_epoch {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Set builder exit epoch
|
||||
builder.withdrawable_epoch = current_epoch.safe_add(spec.min_builder_withdrawability_delay)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates each `bls_to_execution_change` and updates the state
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully. Otherwise returns
|
||||
@@ -814,6 +917,30 @@ pub fn process_deposit_requests_post_gloas<E: EthSpec>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if there is a pending deposit for a new validator with the given pubkey.
|
||||
// TODO(gloas): cache the deposit signature validation or remove this loop entirely if possible,
|
||||
// it is `O(n * m)` where `n` is max 8192 and `m` is max 128M.
|
||||
fn is_pending_validator<E: EthSpec>(
|
||||
state: &BeaconState<E>,
|
||||
pubkey: &PublicKeyBytes,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<bool, BlockProcessingError> {
|
||||
for deposit in state.pending_deposits()?.iter() {
|
||||
if deposit.pubkey == *pubkey {
|
||||
let deposit_data = DepositData {
|
||||
pubkey: deposit.pubkey,
|
||||
withdrawal_credentials: deposit.withdrawal_credentials,
|
||||
amount: deposit.amount,
|
||||
signature: deposit.signature.clone(),
|
||||
};
|
||||
if is_valid_deposit_signature(&deposit_data, spec).is_ok() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn process_deposit_request_post_gloas<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
deposit_request: &DepositRequest,
|
||||
@@ -835,10 +962,14 @@ pub fn process_deposit_request_post_gloas<E: EthSpec>(
|
||||
let validator_index = state.get_validator_index(&deposit_request.pubkey)?;
|
||||
let is_validator = validator_index.is_some();
|
||||
|
||||
let is_builder_prefix =
|
||||
let has_builder_prefix =
|
||||
is_builder_withdrawal_credential(deposit_request.withdrawal_credentials, spec);
|
||||
|
||||
if is_builder || (is_builder_prefix && !is_validator) {
|
||||
if is_builder
|
||||
|| (has_builder_prefix
|
||||
&& !is_validator
|
||||
&& !is_pending_validator(state, &deposit_request.pubkey, spec)?)
|
||||
{
|
||||
// Apply builder deposits immediately
|
||||
apply_deposit_for_builder(
|
||||
state,
|
||||
|
||||
@@ -31,9 +31,9 @@ pub mod gloas {
|
||||
|
||||
// Fork choice constants
|
||||
pub type PayloadStatus = u8;
|
||||
pub const PAYLOAD_STATUS_PENDING: PayloadStatus = 0;
|
||||
pub const PAYLOAD_STATUS_EMPTY: PayloadStatus = 1;
|
||||
pub const PAYLOAD_STATUS_FULL: PayloadStatus = 2;
|
||||
pub const PAYLOAD_STATUS_EMPTY: PayloadStatus = 0;
|
||||
pub const PAYLOAD_STATUS_FULL: PayloadStatus = 1;
|
||||
pub const PAYLOAD_STATUS_PENDING: PayloadStatus = 2;
|
||||
|
||||
pub const ATTESTATION_TIMELINESS_INDEX: usize = 0;
|
||||
pub const PTC_TIMELINESS_INDEX: usize = 1;
|
||||
|
||||
@@ -47,6 +47,8 @@ excluded_paths = [
|
||||
"bls12-381-tests/hash_to_G2",
|
||||
"tests/.*/eip7732",
|
||||
"tests/.*/eip7805",
|
||||
# Heze fork is not implemented
|
||||
"tests/.*/heze/.*",
|
||||
# TODO(gloas): remove these ignores as Gloas consensus is implemented
|
||||
"tests/.*/gloas/fork_choice/.*",
|
||||
# Ignore MatrixEntry SSZ tests for now.
|
||||
|
||||
@@ -10,7 +10,7 @@ if [[ "$version" == "nightly" || "$version" =~ ^nightly-[0-9]+$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for cmd in unzip jq; do
|
||||
for cmd in jq; do
|
||||
if ! command -v "${cmd}" >/dev/null 2>&1; then
|
||||
echo "Error ${cmd} is not installed"
|
||||
exit 1
|
||||
@@ -48,13 +48,10 @@ if [[ "$version" == "nightly" || "$version" =~ ^nightly-[0-9]+$ ]]; then
|
||||
echo "Downloading artifact: ${name}"
|
||||
curl --progress-bar --location --show-error --retry 3 --retry-all-errors --fail \
|
||||
-H "${auth_header}" -H "Accept: application/vnd.github+json" \
|
||||
--output "${name}.zip" "${url}" || {
|
||||
--output "${name}" "${url}" || {
|
||||
echo "Failed to download ${name}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
unzip -qo "${name}.zip"
|
||||
rm -f "${name}.zip"
|
||||
done
|
||||
else
|
||||
for test in "${TESTS[@]}"; do
|
||||
|
||||
@@ -716,8 +716,13 @@ impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
|
||||
|
||||
// Check BLS setting here before SSZ deserialization, as most types require signatures
|
||||
// to be valid.
|
||||
let (operation, bls_error) = if metadata.bls_setting.unwrap_or_default().check().is_ok() {
|
||||
match O::decode(&path.join(O::filename()), fork_name, spec) {
|
||||
let operation_path = path.join(O::filename());
|
||||
let (operation, bls_error) = if !operation_path.is_file() {
|
||||
// Some test cases (e.g. builder_voluntary_exit__success) have no operation file.
|
||||
// TODO(gloas): remove this once the test vectors are fixed
|
||||
(None, None)
|
||||
} else if metadata.bls_setting.unwrap_or_default().check().is_ok() {
|
||||
match O::decode(&operation_path, fork_name, spec) {
|
||||
Ok(op) => (Some(op), None),
|
||||
Err(Error::InvalidBLSInput(error)) => (None, Some(error)),
|
||||
Err(e) => return Err(e),
|
||||
|
||||
@@ -537,11 +537,6 @@ impl<E: EthSpec + TypeName> Handler for RandomHandler<E> {
|
||||
fn handler_name(&self) -> String {
|
||||
"random".into()
|
||||
}
|
||||
|
||||
fn disabled_forks(&self) -> Vec<ForkName> {
|
||||
// TODO(gloas): remove once we have Gloas random tests
|
||||
vec![ForkName::Gloas]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Educe)]
|
||||
|
||||
Reference in New Issue
Block a user