mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-04-19 03:01:06 -04:00
Compare commits
91 Commits
e2e-debugg
...
pyroscope
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
891e65d81d | ||
|
|
b59a830dce | ||
|
|
6999943c3d | ||
|
|
90064edd54 | ||
|
|
393eb1e83c | ||
|
|
d2bcf75c50 | ||
|
|
89d3a6c66f | ||
|
|
bf2485eb71 | ||
|
|
a88f60f1fa | ||
|
|
33f899506f | ||
|
|
f7ead02e6e | ||
|
|
0abf17f6cd | ||
|
|
8307ee1098 | ||
|
|
7e4a039a7f | ||
|
|
d46621eea3 | ||
|
|
8b25ffaa45 | ||
|
|
17f1b78494 | ||
|
|
5874226067 | ||
|
|
544bc3eb45 | ||
|
|
de6d9947d7 | ||
|
|
e5382d95dd | ||
|
|
6d8445d440 | ||
|
|
7b16c34af3 | ||
|
|
91577760b6 | ||
|
|
9ced048510 | ||
|
|
0439151373 | ||
|
|
5f050566f4 | ||
|
|
8a43513f5a | ||
|
|
6c8504ef71 | ||
|
|
e99f86ecc5 | ||
|
|
0c36416677 | ||
|
|
f2db0faea8 | ||
|
|
e31f59e04e | ||
|
|
83a5210549 | ||
|
|
0aa4a08b7b | ||
|
|
9c5d4a1767 | ||
|
|
dd1ede572d | ||
|
|
ec7eb97d41 | ||
|
|
0c893bd1a6 | ||
|
|
9a7cae9e5c | ||
|
|
c114bc57d9 | ||
|
|
a9aea0ba25 | ||
|
|
9f2ade53ff | ||
|
|
5b19916067 | ||
|
|
e4cbb34c2f | ||
|
|
c9a2a0dd86 | ||
|
|
fdd04a5466 | ||
|
|
a199580672 | ||
|
|
b5bdd65f64 | ||
|
|
ce4e653261 | ||
|
|
6f87a0cb95 | ||
|
|
d262c9f927 | ||
|
|
7865bd5f32 | ||
|
|
7f2593e496 | ||
|
|
55e2f0a7d2 | ||
|
|
bf11ac0d64 | ||
|
|
dfb918432f | ||
|
|
a28c6c8145 | ||
|
|
045a3cfe4e | ||
|
|
8ee28394ab | ||
|
|
b31e2ffe51 | ||
|
|
22e77add54 | ||
|
|
7f9983386e | ||
|
|
935acf6b9d | ||
|
|
63b69a63b1 | ||
|
|
cf63d112be | ||
|
|
6c045083a6 | ||
|
|
09d0338aa9 | ||
|
|
eaf3aa3e8e | ||
|
|
9c49bb484c | ||
|
|
bb0f70ad60 | ||
|
|
dc66f8872d | ||
|
|
db2bb5505c | ||
|
|
14f01bbc6c | ||
|
|
c3e74e4a5d | ||
|
|
e7ae6a004b | ||
|
|
862fb2eb4a | ||
|
|
bb80a9c832 | ||
|
|
c1b668a50a | ||
|
|
fab687d96d | ||
|
|
cf94ccbf72 | ||
|
|
75895c1e0b | ||
|
|
d1b9281677 | ||
|
|
641d90990d | ||
|
|
d2fc250f34 | ||
|
|
571c6f39aa | ||
|
|
55fe85c887 | ||
|
|
31f77567dd | ||
|
|
a7fdd11777 | ||
|
|
919bd5d6aa | ||
|
|
0476eeda57 |
@@ -1,25 +1,39 @@
|
||||
version: v1.7.0-alpha.1
|
||||
version: v1.7.0-alpha.2
|
||||
style: full
|
||||
|
||||
specrefs:
|
||||
search_root: ..
|
||||
search_root: .
|
||||
auto_standardize_names: true
|
||||
auto_add_missing_entries: true
|
||||
require_exceptions_have_fork: true
|
||||
|
||||
files:
|
||||
- configs.yml
|
||||
- constants.yml
|
||||
- containers.yml
|
||||
- dataclasses.yml
|
||||
- functions.yml
|
||||
- presets.yml
|
||||
- specrefs/configs.yml
|
||||
- specrefs/constants.yml
|
||||
- specrefs/containers.yml
|
||||
- specrefs/dataclasses.yml
|
||||
- specrefs/functions.yml
|
||||
- specrefs/presets.yml
|
||||
|
||||
exceptions:
|
||||
presets:
|
||||
# Not implemented: gloas (future fork)
|
||||
# gloas
|
||||
- BUILDER_PENDING_WITHDRAWALS_LIMIT#gloas
|
||||
- MAX_PAYLOAD_ATTESTATIONS#gloas
|
||||
- PTC_SIZE#gloas
|
||||
|
||||
constants:
|
||||
# Constants in the KZG library
|
||||
# phase0
|
||||
- BASIS_POINTS#phase0
|
||||
- ENDIANNESS#phase0
|
||||
- MAX_CONCURRENT_REQUESTS#phase0
|
||||
- UINT64_MAX#phase0
|
||||
- UINT64_MAX_SQRT#phase0
|
||||
# altair
|
||||
- PARTICIPATION_FLAG_WEIGHTS#altair
|
||||
# bellatrix
|
||||
- SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY#bellatrix
|
||||
# deneb
|
||||
- BLS_MODULUS#deneb
|
||||
- BYTES_PER_COMMITMENT#deneb
|
||||
- BYTES_PER_FIELD_ELEMENT#deneb
|
||||
@@ -33,18 +47,9 @@ exceptions:
|
||||
- PRIMITIVE_ROOT_OF_UNITY#deneb
|
||||
- RANDOM_CHALLENGE_KZG_BATCH_DOMAIN#deneb
|
||||
- RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN#fulu
|
||||
|
||||
# Not implemented
|
||||
- BASIS_POINTS#phase0
|
||||
- ENDIANNESS#phase0
|
||||
- MAX_CONCURRENT_REQUESTS#phase0
|
||||
- PARTICIPATION_FLAG_WEIGHTS#altair
|
||||
- SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY#bellatrix
|
||||
# fulu
|
||||
- UINT256_MAX#fulu
|
||||
- UINT64_MAX#phase0
|
||||
- UINT64_MAX_SQRT#phase0
|
||||
|
||||
# Not implemented: gloas (future fork)
|
||||
# gloas
|
||||
- BUILDER_PAYMENT_THRESHOLD_DENOMINATOR#gloas
|
||||
- BUILDER_PAYMENT_THRESHOLD_NUMERATOR#gloas
|
||||
- BUILDER_WITHDRAWAL_PREFIX#gloas
|
||||
@@ -61,61 +66,61 @@ exceptions:
|
||||
- PTC_TIMELINESS_INDEX#gloas
|
||||
|
||||
configs:
|
||||
# Not implemented: gloas (future fork)
|
||||
# gloas
|
||||
- AGGREGATE_DUE_BPS_GLOAS#gloas
|
||||
- ATTESTATION_DUE_BPS_GLOAS#gloas
|
||||
- CONTRIBUTION_DUE_BPS_GLOAS#gloas
|
||||
- GLOAS_FORK_EPOCH#gloas
|
||||
- GLOAS_FORK_VERSION#gloas
|
||||
- MAX_REQUEST_PAYLOADS#gloas
|
||||
- MIN_BUILDER_WITHDRAWABILITY_DELAY#gloas
|
||||
- PAYLOAD_ATTESTATION_DUE_BPS#gloas
|
||||
- SYNC_MESSAGE_DUE_BPS_GLOAS#gloas
|
||||
- MIN_BUILDER_WITHDRAWABILITY_DELAY#gloas
|
||||
|
||||
ssz_objects:
|
||||
# Not implemented
|
||||
# phase0
|
||||
- Eth1Block#phase0
|
||||
- MatrixEntry#fulu
|
||||
|
||||
# Not implemented: capella
|
||||
# capella
|
||||
- LightClientBootstrap#capella
|
||||
- LightClientFinalityUpdate#capella
|
||||
- LightClientOptimisticUpdate#capella
|
||||
- LightClientUpdate#capella
|
||||
|
||||
# Not implemented: gloas (future fork)
|
||||
# fulu
|
||||
- MatrixEntry#fulu
|
||||
# gloas
|
||||
- BeaconBlockBody#gloas
|
||||
- BeaconState#gloas
|
||||
- Builder#gloas
|
||||
- BuilderPendingPayment#gloas
|
||||
- BuilderPendingWithdrawal#gloas
|
||||
- DataColumnSidecar#gloas
|
||||
- ExecutionPayloadEnvelope#gloas
|
||||
- ExecutionPayloadBid#gloas
|
||||
- ExecutionPayloadEnvelope#gloas
|
||||
- ForkChoiceNode#gloas
|
||||
- IndexedPayloadAttestation#gloas
|
||||
- PayloadAttestation#gloas
|
||||
- PayloadAttestationData#gloas
|
||||
- PayloadAttestationMessage#gloas
|
||||
- SignedExecutionPayloadEnvelope#gloas
|
||||
- SignedExecutionPayloadBid#gloas
|
||||
- Builder#gloas
|
||||
- ProposerPreferences#gloas
|
||||
- SignedExecutionPayloadBid#gloas
|
||||
- SignedExecutionPayloadEnvelope#gloas
|
||||
- SignedProposerPreferences#gloas
|
||||
|
||||
dataclasses:
|
||||
# Not implemented
|
||||
- BlobParameters#fulu
|
||||
- ExpectedWithdrawals#capella
|
||||
- ExpectedWithdrawals#electra
|
||||
# phase0
|
||||
- LatestMessage#phase0
|
||||
- LightClientStore#altair
|
||||
- OptimisticStore#bellatrix
|
||||
- Store#phase0
|
||||
|
||||
# Not implemented: capella
|
||||
# altair
|
||||
- LightClientStore#altair
|
||||
# bellatrix
|
||||
- OptimisticStore#bellatrix
|
||||
# capella
|
||||
- ExpectedWithdrawals#capella
|
||||
- LightClientStore#capella
|
||||
|
||||
# Not implemented: gloas (future fork)
|
||||
# electra
|
||||
- ExpectedWithdrawals#electra
|
||||
# fulu
|
||||
- BlobParameters#fulu
|
||||
# gloas
|
||||
- ExpectedWithdrawals#gloas
|
||||
- LatestMessage#gloas
|
||||
- Store#gloas
|
||||
@@ -140,7 +145,6 @@ exceptions:
|
||||
- g1_lincomb#deneb
|
||||
- hash_to_bls_field#deneb
|
||||
- is_power_of_two#deneb
|
||||
- multi_exp#deneb
|
||||
- reverse_bits#deneb
|
||||
- validate_kzg_g1#deneb
|
||||
- verify_blob_kzg_proof#deneb
|
||||
@@ -175,7 +179,12 @@ exceptions:
|
||||
- verify_cell_kzg_proof_batch#fulu
|
||||
- verify_cell_kzg_proof_batch_impl#fulu
|
||||
|
||||
# Not implemented: phase0
|
||||
# phase0
|
||||
- update_proposer_boost_root#phase0
|
||||
- is_proposer_equivocation#phase0
|
||||
- record_block_timeliness#phase0
|
||||
- compute_proposer_score#phase0
|
||||
- get_attestation_score#phase0
|
||||
- calculate_committee_fraction#phase0
|
||||
- compute_fork_version#phase0
|
||||
- compute_pulled_up_tip#phase0
|
||||
@@ -221,8 +230,7 @@ exceptions:
|
||||
- validate_on_attestation#phase0
|
||||
- validate_target_epoch_against_current_time#phase0
|
||||
- xor#phase0
|
||||
|
||||
# Not implemented: altair
|
||||
# altair
|
||||
- compute_merkle_proof#altair
|
||||
- compute_sync_committee_period_at_slot#altair
|
||||
- get_contribution_and_proof#altair
|
||||
@@ -244,27 +252,29 @@ exceptions:
|
||||
- process_sync_committee_contributions#altair
|
||||
- set_or_append_list#altair
|
||||
- validate_light_client_update#altair
|
||||
|
||||
# Not implemented: bellatrix
|
||||
# bellatrix
|
||||
- get_execution_payload#bellatrix
|
||||
- is_merge_transition_block#bellatrix
|
||||
- is_optimistic_candidate_block#bellatrix
|
||||
- latest_verified_ancestor#bellatrix
|
||||
- prepare_execution_payload#bellatrix
|
||||
|
||||
# Not implemented: capella
|
||||
# capella
|
||||
- apply_withdrawals#capella
|
||||
- get_balance_after_withdrawals#capella
|
||||
- get_lc_execution_root#capella
|
||||
- get_validators_sweep_withdrawals#capella
|
||||
- is_valid_light_client_header#capella
|
||||
- prepare_execution_payload#capella
|
||||
- process_epoch#capella
|
||||
- update_next_withdrawal_index#capella
|
||||
- update_next_withdrawal_validator_index#capella
|
||||
- upgrade_lc_bootstrap_to_capella#capella
|
||||
- upgrade_lc_finality_update_to_capella#capella
|
||||
- upgrade_lc_header_to_capella#capella
|
||||
- upgrade_lc_optimistic_update_to_capella#capella
|
||||
- upgrade_lc_store_to_capella#capella
|
||||
- upgrade_lc_update_to_capella#capella
|
||||
|
||||
# Not implemented: deneb
|
||||
# deneb
|
||||
- get_lc_execution_root#deneb
|
||||
- is_valid_light_client_header#deneb
|
||||
- prepare_execution_payload#deneb
|
||||
@@ -274,33 +284,34 @@ exceptions:
|
||||
- upgrade_lc_optimistic_update_to_deneb#deneb
|
||||
- upgrade_lc_store_to_deneb#deneb
|
||||
- upgrade_lc_update_to_deneb#deneb
|
||||
|
||||
# Not implemented: electra
|
||||
# electra
|
||||
- compute_weak_subjectivity_period#electra
|
||||
- current_sync_committee_gindex_at_slot#electra
|
||||
- finalized_root_gindex_at_slot#electra
|
||||
- get_eth1_vote#electra
|
||||
- get_lc_execution_root#electra
|
||||
- get_pending_partial_withdrawals#electra
|
||||
- get_validators_sweep_withdrawals#electra
|
||||
- is_compounding_withdrawal_credential#electra
|
||||
- is_eligible_for_partial_withdrawals#electra
|
||||
- is_within_weak_subjectivity_period#electra
|
||||
- next_sync_committee_gindex_at_slot#electra
|
||||
- normalize_merkle_branch#electra
|
||||
- prepare_execution_payload#electra
|
||||
- update_pending_partial_withdrawals#electra
|
||||
- upgrade_lc_bootstrap_to_electra#electra
|
||||
- upgrade_lc_finality_update_to_electra#electra
|
||||
- upgrade_lc_header_to_electra#electra
|
||||
- upgrade_lc_optimistic_update_to_electra#electra
|
||||
- upgrade_lc_store_to_electra#electra
|
||||
- upgrade_lc_update_to_electra#electra
|
||||
|
||||
# Not implemented: fulu
|
||||
# fulu
|
||||
- compute_matrix#fulu
|
||||
- get_blob_parameters#fulu
|
||||
- get_data_column_sidecars_from_block#fulu
|
||||
- get_data_column_sidecars_from_column_sidecar#fulu
|
||||
- recover_matrix#fulu
|
||||
|
||||
# Not implemented: gloas (future fork)
|
||||
# gloas
|
||||
- compute_balance_weighted_acceptance#gloas
|
||||
- compute_balance_weighted_selection#gloas
|
||||
- compute_fork_version#gloas
|
||||
@@ -368,49 +379,42 @@ exceptions:
|
||||
- verify_execution_payload_bid_signature#gloas
|
||||
- add_builder_to_registry#gloas
|
||||
- apply_deposit_for_builder#gloas
|
||||
- apply_withdrawals#capella
|
||||
- apply_withdrawals#gloas
|
||||
- can_builder_cover_bid#gloas
|
||||
- compute_proposer_score#phase0
|
||||
- convert_builder_index_to_validator_index#gloas
|
||||
- convert_validator_index_to_builder_index#gloas
|
||||
- get_attestation_score#gloas
|
||||
- get_attestation_score#phase0
|
||||
- get_balance_after_withdrawals#capella
|
||||
- get_builder_from_deposit#gloas
|
||||
- get_builder_withdrawals#gloas
|
||||
- get_builders_sweep_withdrawals#gloas
|
||||
- get_index_for_new_builder#gloas
|
||||
- get_pending_balance_to_withdraw_for_builder#gloas
|
||||
- get_pending_partial_withdrawals#electra
|
||||
- get_proposer_preferences_signature#gloas
|
||||
- get_upcoming_proposal_slots#gloas
|
||||
- get_validators_sweep_withdrawals#capella
|
||||
- get_validators_sweep_withdrawals#electra
|
||||
- initiate_builder_exit#gloas
|
||||
- is_active_builder#gloas
|
||||
- is_builder_index#gloas
|
||||
- is_data_available#gloas
|
||||
- is_eligible_for_partial_withdrawals#electra
|
||||
- is_head_late#gloas
|
||||
- is_head_weak#gloas
|
||||
- is_parent_strong#gloas
|
||||
- is_proposer_equivocation#phase0
|
||||
- is_valid_proposal_slot#gloas
|
||||
- onboard_builders_from_pending_deposits#gloas
|
||||
- process_deposit_request#gloas
|
||||
- process_voluntary_exit#gloas
|
||||
- record_block_timeliness#gloas
|
||||
- record_block_timeliness#phase0
|
||||
- verify_data_column_sidecar_kzg_proofs#gloas
|
||||
- should_apply_proposer_boost#gloas
|
||||
- update_builder_pending_withdrawals#gloas
|
||||
- update_next_withdrawal_builder_index#gloas
|
||||
- update_next_withdrawal_index#capella
|
||||
- update_next_withdrawal_validator_index#capella
|
||||
- update_payload_expected_withdrawals#gloas
|
||||
- update_pending_partial_withdrawals#electra
|
||||
- update_proposer_boost_root#gloas
|
||||
- update_proposer_boost_root#phase0
|
||||
|
||||
presets:
|
||||
# gloas
|
||||
- BUILDER_PENDING_WITHDRAWALS_LIMIT#gloas
|
||||
- BUILDER_REGISTRY_LIMIT#gloas
|
||||
- MAX_BUILDERS_PER_WITHDRAWALS_SWEEP#gloas
|
||||
8
.github/workflows/check-specrefs.yml
vendored
8
.github/workflows/check-specrefs.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
- name: Check version consistency
|
||||
run: |
|
||||
WORKSPACE_VERSION=$(grep 'consensus_spec_version = ' WORKSPACE | sed 's/.*"\(.*\)"/\1/')
|
||||
ETHSPECIFY_VERSION=$(grep '^version:' specrefs/.ethspecify.yml | sed 's/version: //')
|
||||
ETHSPECIFY_VERSION=$(grep '^version:' .ethspecify.yml | sed 's/version: //')
|
||||
if [ "$WORKSPACE_VERSION" != "$ETHSPECIFY_VERSION" ]; then
|
||||
echo "Version mismatch between WORKSPACE and ethspecify"
|
||||
echo " WORKSPACE: $WORKSPACE_VERSION"
|
||||
echo " specrefs/.ethspecify.yml: $ETHSPECIFY_VERSION"
|
||||
echo " .ethspecify.yml: $ETHSPECIFY_VERSION"
|
||||
exit 1
|
||||
else
|
||||
echo "Versions match: $WORKSPACE_VERSION"
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
run: python3 -mpip install ethspecify
|
||||
|
||||
- name: Update spec references
|
||||
run: ethspecify process --path=specrefs
|
||||
run: ethspecify
|
||||
|
||||
- name: Check for differences
|
||||
run: |
|
||||
@@ -40,4 +40,4 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Check spec references
|
||||
run: ethspecify check --path=specrefs
|
||||
run: ethspecify check
|
||||
|
||||
16
.github/workflows/clang-format.yml
vendored
16
.github/workflows/clang-format.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Protobuf Format
|
||||
|
||||
on:
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
@@ -12,10 +12,14 @@ jobs:
|
||||
clang-format-checking:
|
||||
runs-on: ubuntu-4
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Is this step failing for you?
|
||||
- uses: actions/checkout@v6
|
||||
# Is this step failing for you?
|
||||
# Run: clang-format -i proto/**/*.proto
|
||||
# See: https://clang.llvm.org/docs/ClangFormat.html
|
||||
- uses: RafikFarhad/clang-format-github-action@v3
|
||||
with:
|
||||
sources: "proto/**/*.proto"
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y clang-format
|
||||
- name: Check protobuf formatting
|
||||
run: |
|
||||
clang-format --style=LLVM --dry-run --Werror proto/**/*.proto
|
||||
|
||||
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -2,7 +2,7 @@ name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ master, develop ]
|
||||
pull_request:
|
||||
branches: [ '*' ]
|
||||
merge_group:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -44,6 +44,3 @@ tmp
|
||||
|
||||
# spectest coverage reports
|
||||
report.txt
|
||||
|
||||
# execution client data
|
||||
execution/
|
||||
|
||||
@@ -33,9 +33,8 @@ formatters:
|
||||
generated: lax
|
||||
paths:
|
||||
- validator/web/site_data.go
|
||||
- .*_test.go
|
||||
- proto
|
||||
- tools/analyzers
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
- examples$
|
||||
|
||||
10
WORKSPACE
10
WORKSPACE
@@ -273,16 +273,16 @@ filegroup(
|
||||
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
|
||||
)
|
||||
|
||||
consensus_spec_version = "v1.7.0-alpha.1"
|
||||
consensus_spec_version = "v1.7.0-alpha.2"
|
||||
|
||||
load("@prysm//tools:download_spectests.bzl", "consensus_spec_tests")
|
||||
|
||||
consensus_spec_tests(
|
||||
name = "consensus_spec_tests",
|
||||
flavors = {
|
||||
"general": "sha256-j5R3jA7Oo4OSDMTvpMuD+8RomaCByeFSwtfkq6fL0Zg=",
|
||||
"minimal": "sha256-tdTqByoyswOS4r6OxFmo70y2BP7w1TgEok+gf4cbxB0=",
|
||||
"mainnet": "sha256-5gB4dt6SnSDKzdBc06VedId3NkgvSYyv9n9FRxWKwYI=",
|
||||
"general": "sha256-iGQsGZ1cHah+2CSod9jC3kN8Ku4n6KO0hIwfINrn/po=",
|
||||
"minimal": "sha256-TgcYt8N8sXSttdHTGvOa+exUZ1zn1UzlAMz0V7i37xc=",
|
||||
"mainnet": "sha256-LnXyiLoJtrvEvbqLDSAAqpLMdN/lXv92SAgYG8fNjCs=",
|
||||
},
|
||||
version = consensus_spec_version,
|
||||
)
|
||||
@@ -298,7 +298,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-J+43DrK1pF658kTXTwMS6zGf4KDjvas++m8w2a8swpg=",
|
||||
integrity = "sha256-Y/67Dg393PksZj5rTFNLntiJ6hNdB7Rxbu5gZE2gebY=",
|
||||
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
|
||||
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
19
api/fallback/BUILD.bazel
Normal file
19
api/fallback/BUILD.bazel
Normal file
@@ -0,0 +1,19 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"fallback.go",
|
||||
"log.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/api/fallback",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["@com_github_sirupsen_logrus//:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["fallback_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//testing/assert:go_default_library"],
|
||||
)
|
||||
66
api/fallback/fallback.go
Normal file
66
api/fallback/fallback.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package fallback
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HostProvider is the subset of connection-provider methods that EnsureReady
|
||||
// needs. Both grpc.GrpcConnectionProvider and rest.RestConnectionProvider
|
||||
// satisfy this interface.
|
||||
type HostProvider interface {
|
||||
Hosts() []string
|
||||
CurrentHost() string
|
||||
SwitchHost(index int) error
|
||||
}
|
||||
|
||||
// ReadyChecker can report whether the current endpoint is ready.
|
||||
// iface.NodeClient satisfies this implicitly.
|
||||
type ReadyChecker interface {
|
||||
IsReady(ctx context.Context) bool
|
||||
}
|
||||
|
||||
// EnsureReady iterates through the configured hosts and returns true as soon as
|
||||
// one responds as ready. It starts from the provider's current host and wraps
|
||||
// around using modular arithmetic, performing failover when a host is not ready.
|
||||
func EnsureReady(ctx context.Context, provider HostProvider, checker ReadyChecker) bool {
|
||||
hosts := provider.Hosts()
|
||||
numHosts := len(hosts)
|
||||
startingHost := provider.CurrentHost()
|
||||
var attemptedHosts []string
|
||||
|
||||
// Find current index
|
||||
currentIdx := 0
|
||||
for i, h := range hosts {
|
||||
if h == startingHost {
|
||||
currentIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := range numHosts {
|
||||
if checker.IsReady(ctx) {
|
||||
if len(attemptedHosts) > 0 {
|
||||
log.WithFields(logrus.Fields{
|
||||
"previous": startingHost,
|
||||
"current": provider.CurrentHost(),
|
||||
"tried": attemptedHosts,
|
||||
}).Info("Switched to responsive beacon node")
|
||||
}
|
||||
return true
|
||||
}
|
||||
attemptedHosts = append(attemptedHosts, provider.CurrentHost())
|
||||
|
||||
// Try next host if not the last iteration
|
||||
if i < numHosts-1 {
|
||||
nextIdx := (currentIdx + i + 1) % numHosts
|
||||
if err := provider.SwitchHost(nextIdx); err != nil {
|
||||
log.WithError(err).Error("Failed to switch host")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.WithField("tried", attemptedHosts).Warn("No responsive beacon node found")
|
||||
return false
|
||||
}
|
||||
94
api/fallback/fallback_test.go
Normal file
94
api/fallback/fallback_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package fallback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
)
|
||||
|
||||
// mockHostProvider is a minimal HostProvider for unit tests.
|
||||
type mockHostProvider struct {
|
||||
hosts []string
|
||||
hostIndex int
|
||||
}
|
||||
|
||||
func (m *mockHostProvider) Hosts() []string { return m.hosts }
|
||||
func (m *mockHostProvider) CurrentHost() string {
|
||||
return m.hosts[m.hostIndex%len(m.hosts)]
|
||||
}
|
||||
func (m *mockHostProvider) SwitchHost(index int) error { m.hostIndex = index; return nil }
|
||||
|
||||
// mockReadyChecker records per-call IsReady results in sequence.
|
||||
type mockReadyChecker struct {
|
||||
results []bool
|
||||
idx int
|
||||
}
|
||||
|
||||
func (m *mockReadyChecker) IsReady(_ context.Context) bool {
|
||||
if m.idx >= len(m.results) {
|
||||
return false
|
||||
}
|
||||
r := m.results[m.idx]
|
||||
m.idx++
|
||||
return r
|
||||
}
|
||||
|
||||
func TestEnsureReady_SingleHostReady(t *testing.T) {
|
||||
provider := &mockHostProvider{hosts: []string{"http://host1:3500"}, hostIndex: 0}
|
||||
checker := &mockReadyChecker{results: []bool{true}}
|
||||
assert.Equal(t, true, EnsureReady(t.Context(), provider, checker))
|
||||
assert.Equal(t, 0, provider.hostIndex)
|
||||
}
|
||||
|
||||
func TestEnsureReady_SingleHostNotReady(t *testing.T) {
|
||||
provider := &mockHostProvider{hosts: []string{"http://host1:3500"}, hostIndex: 0}
|
||||
checker := &mockReadyChecker{results: []bool{false}}
|
||||
assert.Equal(t, false, EnsureReady(t.Context(), provider, checker))
|
||||
}
|
||||
|
||||
func TestEnsureReady_SingleHostError(t *testing.T) {
|
||||
provider := &mockHostProvider{hosts: []string{"http://host1:3500"}, hostIndex: 0}
|
||||
checker := &mockReadyChecker{results: []bool{false}}
|
||||
assert.Equal(t, false, EnsureReady(t.Context(), provider, checker))
|
||||
}
|
||||
|
||||
func TestEnsureReady_MultipleHostsFirstReady(t *testing.T) {
|
||||
provider := &mockHostProvider{
|
||||
hosts: []string{"http://host1:3500", "http://host2:3500"},
|
||||
hostIndex: 0,
|
||||
}
|
||||
checker := &mockReadyChecker{results: []bool{true}}
|
||||
assert.Equal(t, true, EnsureReady(t.Context(), provider, checker))
|
||||
assert.Equal(t, 0, provider.hostIndex)
|
||||
}
|
||||
|
||||
func TestEnsureReady_MultipleHostsFailoverToSecond(t *testing.T) {
|
||||
provider := &mockHostProvider{
|
||||
hosts: []string{"http://host1:3500", "http://host2:3500"},
|
||||
hostIndex: 0,
|
||||
}
|
||||
checker := &mockReadyChecker{results: []bool{false, true}}
|
||||
assert.Equal(t, true, EnsureReady(t.Context(), provider, checker))
|
||||
assert.Equal(t, 1, provider.hostIndex)
|
||||
}
|
||||
|
||||
func TestEnsureReady_MultipleHostsNoneReady(t *testing.T) {
|
||||
provider := &mockHostProvider{
|
||||
hosts: []string{"http://host1:3500", "http://host2:3500", "http://host3:3500"},
|
||||
hostIndex: 0,
|
||||
}
|
||||
checker := &mockReadyChecker{results: []bool{false, false, false}}
|
||||
assert.Equal(t, false, EnsureReady(t.Context(), provider, checker))
|
||||
}
|
||||
|
||||
func TestEnsureReady_WrapAroundFromNonZeroIndex(t *testing.T) {
|
||||
provider := &mockHostProvider{
|
||||
hosts: []string{"http://host0:3500", "http://host1:3500", "http://host2:3500"},
|
||||
hostIndex: 1,
|
||||
}
|
||||
// host1 (start) fails, host2 fails, host0 succeeds
|
||||
checker := &mockReadyChecker{results: []bool{false, false, true}}
|
||||
assert.Equal(t, true, EnsureReady(t.Context(), provider, checker))
|
||||
assert.Equal(t, 0, provider.hostIndex)
|
||||
}
|
||||
9
api/fallback/log.go
Normal file
9
api/fallback/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Code generated by hack/gen-logs.sh; DO NOT EDIT.
|
||||
// This file is created and regenerated automatically. Anything added here might get removed.
|
||||
package fallback
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
// The prefix for logs from this package will be the text after the last slash in the package path.
|
||||
// If you wish to change this, you should add your desired name in the runtime/logging/logrus-prefixed-formatter/prefix-replacement.go file.
|
||||
var log = logrus.WithField("package", "api/fallback")
|
||||
@@ -3,13 +3,16 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"grpc_connection_provider.go",
|
||||
"grpcutils.go",
|
||||
"log.go",
|
||||
"mock_grpc_provider.go",
|
||||
"parameters.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/api/grpc",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//metadata:go_default_library",
|
||||
@@ -18,12 +21,17 @@ go_library(
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["grpcutils_test.go"],
|
||||
srcs = [
|
||||
"grpc_connection_provider_test.go",
|
||||
"grpcutils_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//credentials/insecure:go_default_library",
|
||||
"@org_golang_google_grpc//metadata:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
186
api/grpc/grpc_connection_provider.go
Normal file
186
api/grpc/grpc_connection_provider.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// GrpcConnectionProvider manages gRPC connections for failover support.
|
||||
// It allows switching between different beacon node endpoints when the current one becomes unavailable.
|
||||
// Only one connection is maintained at a time - when switching hosts, the old connection is closed.
|
||||
type GrpcConnectionProvider interface {
|
||||
// CurrentConn returns the currently active gRPC connection.
|
||||
// The connection is created lazily on first call.
|
||||
// Returns nil if the provider has been closed.
|
||||
CurrentConn() *grpc.ClientConn
|
||||
// CurrentHost returns the address of the currently active endpoint.
|
||||
CurrentHost() string
|
||||
// Hosts returns all configured endpoint addresses.
|
||||
Hosts() []string
|
||||
// SwitchHost switches to the endpoint at the given index.
|
||||
// The new connection is created lazily on next CurrentConn() call.
|
||||
SwitchHost(index int) error
|
||||
// ConnectionCounter returns a monotonically increasing counter that increments
|
||||
// each time SwitchHost changes the active endpoint. This allows consumers to
|
||||
// detect connection changes even when the host string returns to a previous value
|
||||
// (e.g., host0 → host1 → host0).
|
||||
ConnectionCounter() uint64
|
||||
// Close closes the current connection.
|
||||
Close()
|
||||
}
|
||||
|
||||
type grpcConnectionProvider struct {
|
||||
// Immutable after construction - no lock needed for reads
|
||||
endpoints []string
|
||||
ctx context.Context
|
||||
dialOpts []grpc.DialOption
|
||||
|
||||
// Current connection state (protected by mutex)
|
||||
currentIndex uint64
|
||||
conn *grpc.ClientConn
|
||||
connCounter uint64
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
// NewGrpcConnectionProvider creates a new connection provider that manages gRPC connections.
|
||||
// The endpoint parameter can be a comma-separated list of addresses (e.g., "host1:4000,host2:4000").
|
||||
// Only one connection is maintained at a time, created lazily on first use.
|
||||
func NewGrpcConnectionProvider(
|
||||
ctx context.Context,
|
||||
endpoint string,
|
||||
dialOpts []grpc.DialOption,
|
||||
) (GrpcConnectionProvider, error) {
|
||||
endpoints := parseEndpoints(endpoint)
|
||||
if len(endpoints) == 0 {
|
||||
return nil, errors.New("no gRPC endpoints provided")
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"endpoints": endpoints,
|
||||
"count": len(endpoints),
|
||||
}).Info("Initialized gRPC connection provider")
|
||||
|
||||
return &grpcConnectionProvider{
|
||||
endpoints: endpoints,
|
||||
ctx: ctx,
|
||||
dialOpts: dialOpts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseEndpoints splits a comma-separated endpoint string into individual endpoints.
|
||||
func parseEndpoints(endpoint string) []string {
|
||||
if endpoint == "" {
|
||||
return nil
|
||||
}
|
||||
endpoints := make([]string, 0, 1)
|
||||
for p := range strings.SplitSeq(endpoint, ",") {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
endpoints = append(endpoints, p)
|
||||
}
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func (p *grpcConnectionProvider) CurrentConn() *grpc.ClientConn {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return existing connection if available
|
||||
if p.conn != nil {
|
||||
return p.conn
|
||||
}
|
||||
|
||||
// Create connection lazily
|
||||
ep := p.endpoints[p.currentIndex]
|
||||
conn, err := grpc.DialContext(p.ctx, ep, p.dialOpts...)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("endpoint", ep).Error("Failed to create gRPC connection")
|
||||
return nil
|
||||
}
|
||||
|
||||
p.conn = conn
|
||||
log.WithField("endpoint", ep).Debug("Created gRPC connection")
|
||||
return conn
|
||||
}
|
||||
|
||||
func (p *grpcConnectionProvider) CurrentHost() string {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.endpoints[p.currentIndex]
|
||||
}
|
||||
|
||||
func (p *grpcConnectionProvider) Hosts() []string {
|
||||
// Return a copy to maintain immutability
|
||||
hosts := make([]string, len(p.endpoints))
|
||||
copy(hosts, p.endpoints)
|
||||
return hosts
|
||||
}
|
||||
|
||||
func (p *grpcConnectionProvider) SwitchHost(index int) error {
|
||||
if index < 0 || index >= len(p.endpoints) {
|
||||
return errors.Errorf("invalid host index %d, must be between 0 and %d", index, len(p.endpoints)-1)
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if uint64(index) == p.currentIndex {
|
||||
return nil // Already on this host
|
||||
}
|
||||
|
||||
oldHost := p.endpoints[p.currentIndex]
|
||||
oldConn := p.conn
|
||||
|
||||
p.conn = nil // Clear immediately - new connection created lazily
|
||||
p.currentIndex = uint64(index)
|
||||
p.connCounter++
|
||||
|
||||
// Close old connection asynchronously to avoid blocking the caller
|
||||
if oldConn != nil {
|
||||
go func() {
|
||||
if err := oldConn.Close(); err != nil {
|
||||
log.WithError(err).WithField("endpoint", oldHost).Debug("Failed to close previous connection")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"previousHost": oldHost,
|
||||
"newHost": p.endpoints[index],
|
||||
}).Debug("Switched gRPC endpoint")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *grpcConnectionProvider) ConnectionCounter() uint64 {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.connCounter
|
||||
}
|
||||
|
||||
func (p *grpcConnectionProvider) Close() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.closed {
|
||||
return
|
||||
}
|
||||
p.closed = true
|
||||
|
||||
if p.conn != nil {
|
||||
if err := p.conn.Close(); err != nil {
|
||||
log.WithError(err).WithField("endpoint", p.endpoints[p.currentIndex]).Debug("Failed to close gRPC connection")
|
||||
}
|
||||
p.conn = nil
|
||||
}
|
||||
}
|
||||
207
api/grpc/grpc_connection_provider_test.go
Normal file
207
api/grpc/grpc_connection_provider_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func TestParseEndpoints(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
{"single endpoint", "localhost:4000", []string{"localhost:4000"}},
|
||||
{"multiple endpoints", "host1:4000,host2:4000,host3:4000", []string{"host1:4000", "host2:4000", "host3:4000"}},
|
||||
{"endpoints with spaces", "host1:4000, host2:4000 , host3:4000", []string{"host1:4000", "host2:4000", "host3:4000"}},
|
||||
{"empty string", "", nil},
|
||||
{"only commas", ",,,", []string{}},
|
||||
{"trailing comma", "host1:4000,host2:4000,", []string{"host1:4000", "host2:4000"}},
|
||||
{"leading comma", ",host1:4000,host2:4000", []string{"host1:4000", "host2:4000"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseEndpoints(tt.input)
|
||||
if !reflect.DeepEqual(tt.expected, got) {
|
||||
t.Errorf("parseEndpoints(%q) = %v, want %v", tt.input, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGrpcConnectionProvider_Errors(t *testing.T) {
|
||||
t.Run("no endpoints", func(t *testing.T) {
|
||||
dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
||||
_, err := NewGrpcConnectionProvider(context.Background(), "", dialOpts)
|
||||
require.ErrorContains(t, "no gRPC endpoints provided", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGrpcConnectionProvider_LazyConnection(t *testing.T) {
|
||||
// Start only one server but configure provider with two endpoints
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
server := grpc.NewServer()
|
||||
go func() { _ = server.Serve(lis) }()
|
||||
defer server.Stop()
|
||||
|
||||
validAddr := lis.Addr().String()
|
||||
invalidAddr := "127.0.0.1:1" // Port 1 is unlikely to be listening
|
||||
|
||||
// Provider should succeed even though second endpoint is invalid (lazy connections)
|
||||
endpoint := validAddr + "," + invalidAddr
|
||||
ctx := context.Background()
|
||||
dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
||||
provider, err := NewGrpcConnectionProvider(ctx, endpoint, dialOpts)
|
||||
require.NoError(t, err, "Provider creation should succeed with lazy connections")
|
||||
defer func() { provider.Close() }()
|
||||
|
||||
// First endpoint should work
|
||||
conn := provider.CurrentConn()
|
||||
assert.NotNil(t, conn, "First connection should be created lazily")
|
||||
}
|
||||
|
||||
func TestGrpcConnectionProvider_SingleConnectionModel(t *testing.T) {
|
||||
// Create provider with 3 endpoints
|
||||
var addrs []string
|
||||
var servers []*grpc.Server
|
||||
|
||||
for range 3 {
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
server := grpc.NewServer()
|
||||
go func() { _ = server.Serve(lis) }()
|
||||
addrs = append(addrs, lis.Addr().String())
|
||||
servers = append(servers, server)
|
||||
}
|
||||
defer func() {
|
||||
for _, s := range servers {
|
||||
s.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
endpoint := strings.Join(addrs, ",")
|
||||
ctx := context.Background()
|
||||
dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
||||
provider, err := NewGrpcConnectionProvider(ctx, endpoint, dialOpts)
|
||||
require.NoError(t, err)
|
||||
defer func() { provider.Close() }()
|
||||
|
||||
// Access the internal state to verify single connection behavior
|
||||
p := provider.(*grpcConnectionProvider)
|
||||
|
||||
// Initially no connection
|
||||
p.mu.Lock()
|
||||
assert.Equal(t, (*grpc.ClientConn)(nil), p.conn, "Connection should be nil before access")
|
||||
p.mu.Unlock()
|
||||
|
||||
// Access connection - should create one
|
||||
conn0 := provider.CurrentConn()
|
||||
assert.NotNil(t, conn0)
|
||||
|
||||
p.mu.Lock()
|
||||
assert.NotNil(t, p.conn, "Connection should be created after CurrentConn()")
|
||||
firstConn := p.conn
|
||||
p.mu.Unlock()
|
||||
|
||||
// Call CurrentConn again - should return same connection
|
||||
conn0Again := provider.CurrentConn()
|
||||
assert.Equal(t, conn0, conn0Again, "Should return same connection")
|
||||
|
||||
// Switch to different host - old connection should be closed, new one created lazily
|
||||
require.NoError(t, provider.SwitchHost(1))
|
||||
|
||||
p.mu.Lock()
|
||||
assert.Equal(t, (*grpc.ClientConn)(nil), p.conn, "Connection should be nil after SwitchHost (lazy)")
|
||||
p.mu.Unlock()
|
||||
|
||||
// Get new connection
|
||||
conn1 := provider.CurrentConn()
|
||||
assert.NotNil(t, conn1)
|
||||
assert.NotEqual(t, firstConn, conn1, "Should be a different connection after switching hosts")
|
||||
}
|
||||
|
||||
// testProvider creates a provider with n test servers and returns cleanup function.
|
||||
func testProvider(t *testing.T, n int) (GrpcConnectionProvider, []string, func()) {
|
||||
var addrs []string
|
||||
var cleanups []func()
|
||||
|
||||
for range n {
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
server := grpc.NewServer()
|
||||
go func() { _ = server.Serve(lis) }()
|
||||
addrs = append(addrs, lis.Addr().String())
|
||||
cleanups = append(cleanups, server.Stop)
|
||||
}
|
||||
|
||||
endpoint := strings.Join(addrs, ",")
|
||||
|
||||
ctx := context.Background()
|
||||
dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
||||
provider, err := NewGrpcConnectionProvider(ctx, endpoint, dialOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
cleanup := func() {
|
||||
provider.Close()
|
||||
for _, c := range cleanups {
|
||||
c()
|
||||
}
|
||||
}
|
||||
return provider, addrs, cleanup
|
||||
}
|
||||
|
||||
func TestGrpcConnectionProvider(t *testing.T) {
|
||||
provider, addrs, cleanup := testProvider(t, 3)
|
||||
defer cleanup()
|
||||
|
||||
t.Run("initial state", func(t *testing.T) {
|
||||
assert.Equal(t, 3, len(provider.Hosts()))
|
||||
assert.Equal(t, addrs[0], provider.CurrentHost())
|
||||
assert.NotNil(t, provider.CurrentConn())
|
||||
})
|
||||
|
||||
t.Run("SwitchHost", func(t *testing.T) {
|
||||
require.NoError(t, provider.SwitchHost(1))
|
||||
assert.Equal(t, addrs[1], provider.CurrentHost())
|
||||
assert.NotNil(t, provider.CurrentConn()) // New connection created lazily
|
||||
require.NoError(t, provider.SwitchHost(0))
|
||||
assert.Equal(t, addrs[0], provider.CurrentHost())
|
||||
require.ErrorContains(t, "invalid host index", provider.SwitchHost(-1))
|
||||
require.ErrorContains(t, "invalid host index", provider.SwitchHost(3))
|
||||
})
|
||||
|
||||
t.Run("SwitchHost circular", func(t *testing.T) {
|
||||
// Test round-robin style switching using SwitchHost with manual index
|
||||
indices := []int{1, 2, 0, 1} // Simulate circular switching
|
||||
for i, idx := range indices {
|
||||
require.NoError(t, provider.SwitchHost(idx))
|
||||
assert.Equal(t, addrs[idx], provider.CurrentHost(), "iteration %d", i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Hosts returns copy", func(t *testing.T) {
|
||||
hosts := provider.Hosts()
|
||||
original := hosts[0]
|
||||
hosts[0] = "modified"
|
||||
assert.Equal(t, original, provider.Hosts()[0])
|
||||
})
|
||||
}
|
||||
|
||||
func TestGrpcConnectionProvider_Close(t *testing.T) {
|
||||
provider, _, cleanup := testProvider(t, 1)
|
||||
defer cleanup()
|
||||
|
||||
assert.NotNil(t, provider.CurrentConn())
|
||||
provider.Close()
|
||||
assert.Equal(t, (*grpc.ClientConn)(nil), provider.CurrentConn())
|
||||
provider.Close() // Double close is safe
|
||||
}
|
||||
27
api/grpc/mock_grpc_provider.go
Normal file
27
api/grpc/mock_grpc_provider.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package grpc
|
||||
|
||||
import "google.golang.org/grpc"
|
||||
|
||||
// MockGrpcProvider implements GrpcConnectionProvider for testing.
|
||||
type MockGrpcProvider struct {
|
||||
MockConn *grpc.ClientConn
|
||||
MockHosts []string
|
||||
CurrentIndex int
|
||||
ConnCounter uint64
|
||||
}
|
||||
|
||||
func (m *MockGrpcProvider) CurrentConn() *grpc.ClientConn { return m.MockConn }
|
||||
func (m *MockGrpcProvider) CurrentHost() string {
|
||||
if len(m.MockHosts) > 0 {
|
||||
return m.MockHosts[m.CurrentIndex]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (m *MockGrpcProvider) Hosts() []string { return m.MockHosts }
|
||||
func (m *MockGrpcProvider) SwitchHost(idx int) error {
|
||||
m.CurrentIndex = idx
|
||||
m.ConnCounter++
|
||||
return nil
|
||||
}
|
||||
func (m *MockGrpcProvider) ConnectionCounter() uint64 { return m.ConnCounter }
|
||||
func (m *MockGrpcProvider) Close() {}
|
||||
34
api/rest/BUILD.bazel
Normal file
34
api/rest/BUILD.bazel
Normal file
@@ -0,0 +1,34 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"log.go",
|
||||
"mock_rest_provider.go",
|
||||
"rest_connection_provider.go",
|
||||
"rest_handler.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/api/rest",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api:go_default_library",
|
||||
"//api/apiutil:go_default_library",
|
||||
"//api/client:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//network/httputil:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@io_opentelemetry_go_contrib_instrumentation_net_http_otelhttp//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["rest_connection_provider_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
],
|
||||
)
|
||||
9
api/rest/log.go
Normal file
9
api/rest/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Code generated by hack/gen-logs.sh; DO NOT EDIT.
|
||||
// This file is created and regenerated automatically. Anything added here might get removed.
|
||||
package rest
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
// The prefix for logs from this package will be the text after the last slash in the package path.
|
||||
// If you wish to change this, you should add your desired name in the runtime/logging/logrus-prefixed-formatter/prefix-replacement.go file.
|
||||
var log = logrus.WithField("package", "api/rest")
|
||||
46
api/rest/mock_rest_provider.go
Normal file
46
api/rest/mock_rest_provider.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// MockRestProvider implements RestConnectionProvider for testing.
|
||||
type MockRestProvider struct {
|
||||
MockClient *http.Client
|
||||
MockHandler Handler
|
||||
MockHosts []string
|
||||
HostIndex int
|
||||
}
|
||||
|
||||
func (m *MockRestProvider) HttpClient() *http.Client { return m.MockClient }
|
||||
func (m *MockRestProvider) Handler() Handler { return m.MockHandler }
|
||||
func (m *MockRestProvider) CurrentHost() string {
|
||||
if len(m.MockHosts) > 0 {
|
||||
return m.MockHosts[m.HostIndex%len(m.MockHosts)]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (m *MockRestProvider) Hosts() []string { return m.MockHosts }
|
||||
func (m *MockRestProvider) SwitchHost(index int) error { m.HostIndex = index; return nil }
|
||||
|
||||
// MockHandler implements Handler for testing.
|
||||
type MockHandler struct {
|
||||
MockHost string
|
||||
}
|
||||
|
||||
func (m *MockHandler) Get(_ context.Context, _ string, _ any) error { return nil }
|
||||
func (m *MockHandler) GetStatusCode(_ context.Context, _ string) (int, error) {
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
func (m *MockHandler) GetSSZ(_ context.Context, _ string) ([]byte, http.Header, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (m *MockHandler) Post(_ context.Context, _ string, _ map[string]string, _ *bytes.Buffer, _ any) error {
|
||||
return nil
|
||||
}
|
||||
func (m *MockHandler) PostSSZ(_ context.Context, _ string, _ map[string]string, _ *bytes.Buffer) ([]byte, http.Header, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (m *MockHandler) Host() string { return m.MockHost }
|
||||
158
api/rest/rest_connection_provider.go
Normal file
158
api/rest/rest_connection_provider.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/api/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
// RestConnectionProvider manages HTTP client configuration for REST API with failover support.
|
||||
// It allows switching between different beacon node REST endpoints when the current one becomes unavailable.
|
||||
type RestConnectionProvider interface {
|
||||
// HttpClient returns the configured HTTP client with headers, timeout, and optional tracing.
|
||||
HttpClient() *http.Client
|
||||
// Handler returns the REST handler for making API requests.
|
||||
Handler() Handler
|
||||
// CurrentHost returns the current REST API endpoint URL.
|
||||
CurrentHost() string
|
||||
// Hosts returns all configured REST API endpoint URLs.
|
||||
Hosts() []string
|
||||
// SwitchHost switches to the endpoint at the given index.
|
||||
SwitchHost(index int) error
|
||||
}
|
||||
|
||||
// RestConnectionProviderOption is a functional option for configuring the REST connection provider.
|
||||
type RestConnectionProviderOption func(*restConnectionProvider)
|
||||
|
||||
// WithHttpTimeout sets the HTTP client timeout.
|
||||
func WithHttpTimeout(timeout time.Duration) RestConnectionProviderOption {
|
||||
return func(p *restConnectionProvider) {
|
||||
p.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithHttpHeaders sets custom HTTP headers to include in all requests.
|
||||
func WithHttpHeaders(headers map[string][]string) RestConnectionProviderOption {
|
||||
return func(p *restConnectionProvider) {
|
||||
p.headers = headers
|
||||
}
|
||||
}
|
||||
|
||||
// WithTracing enables OpenTelemetry tracing for HTTP requests.
|
||||
func WithTracing() RestConnectionProviderOption {
|
||||
return func(p *restConnectionProvider) {
|
||||
p.enableTracing = true
|
||||
}
|
||||
}
|
||||
|
||||
type restConnectionProvider struct {
|
||||
endpoints []string
|
||||
httpClient *http.Client
|
||||
restHandler *handler
|
||||
currentIndex atomic.Uint64
|
||||
timeout time.Duration
|
||||
headers map[string][]string
|
||||
enableTracing bool
|
||||
}
|
||||
|
||||
// NewRestConnectionProvider creates a new REST connection provider that manages HTTP client configuration.
|
||||
// The endpoint parameter can be a comma-separated list of URLs (e.g., "http://host1:3500,http://host2:3500").
|
||||
func NewRestConnectionProvider(endpoint string, opts ...RestConnectionProviderOption) (RestConnectionProvider, error) {
|
||||
endpoints := parseEndpoints(endpoint)
|
||||
if len(endpoints) == 0 {
|
||||
return nil, errors.New("no REST API endpoints provided")
|
||||
}
|
||||
|
||||
p := &restConnectionProvider{
|
||||
endpoints: endpoints,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
}
|
||||
|
||||
// Build the HTTP transport chain
|
||||
var transport http.RoundTripper = http.DefaultTransport
|
||||
|
||||
// Add custom headers if configured
|
||||
if len(p.headers) > 0 {
|
||||
transport = client.NewCustomHeadersTransport(transport, p.headers)
|
||||
}
|
||||
|
||||
// Add tracing if enabled
|
||||
if p.enableTracing {
|
||||
transport = otelhttp.NewTransport(transport)
|
||||
}
|
||||
|
||||
p.httpClient = &http.Client{
|
||||
Timeout: p.timeout,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
// Create the REST handler with the HTTP client and initial host
|
||||
p.restHandler = newHandler(*p.httpClient, endpoints[0])
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"endpoints": endpoints,
|
||||
"count": len(endpoints),
|
||||
}).Info("Initialized REST connection provider")
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// parseEndpoints splits a comma-separated endpoint string into individual endpoints.
|
||||
func parseEndpoints(endpoint string) []string {
|
||||
if endpoint == "" {
|
||||
return nil
|
||||
}
|
||||
endpoints := make([]string, 0, 1)
|
||||
for p := range strings.SplitSeq(endpoint, ",") {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
endpoints = append(endpoints, p)
|
||||
}
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func (p *restConnectionProvider) HttpClient() *http.Client {
|
||||
return p.httpClient
|
||||
}
|
||||
|
||||
func (p *restConnectionProvider) Handler() Handler {
|
||||
return p.restHandler
|
||||
}
|
||||
|
||||
func (p *restConnectionProvider) CurrentHost() string {
|
||||
return p.endpoints[p.currentIndex.Load()]
|
||||
}
|
||||
|
||||
func (p *restConnectionProvider) Hosts() []string {
|
||||
// Return a copy to maintain immutability
|
||||
hosts := make([]string, len(p.endpoints))
|
||||
copy(hosts, p.endpoints)
|
||||
return hosts
|
||||
}
|
||||
|
||||
func (p *restConnectionProvider) SwitchHost(index int) error {
|
||||
if index < 0 || index >= len(p.endpoints) {
|
||||
return errors.Errorf("invalid host index %d, must be between 0 and %d", index, len(p.endpoints)-1)
|
||||
}
|
||||
|
||||
oldIdx := p.currentIndex.Load()
|
||||
p.currentIndex.Store(uint64(index))
|
||||
|
||||
// Update the rest handler's host
|
||||
p.restHandler.SwitchHost(p.endpoints[index])
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"previousHost": p.endpoints[oldIdx],
|
||||
"newHost": p.endpoints[index],
|
||||
}).Debug("Switched REST endpoint")
|
||||
return nil
|
||||
}
|
||||
80
api/rest/rest_connection_provider_test.go
Normal file
80
api/rest/rest_connection_provider_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestParseEndpoints(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
{"single endpoint", "http://localhost:3500", []string{"http://localhost:3500"}},
|
||||
{"multiple endpoints", "http://host1:3500,http://host2:3500,http://host3:3500", []string{"http://host1:3500", "http://host2:3500", "http://host3:3500"}},
|
||||
{"endpoints with spaces", "http://host1:3500, http://host2:3500 , http://host3:3500", []string{"http://host1:3500", "http://host2:3500", "http://host3:3500"}},
|
||||
{"empty string", "", nil},
|
||||
{"only commas", ",,,", []string{}},
|
||||
{"trailing comma", "http://host1:3500,http://host2:3500,", []string{"http://host1:3500", "http://host2:3500"}},
|
||||
{"leading comma", ",http://host1:3500,http://host2:3500", []string{"http://host1:3500", "http://host2:3500"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseEndpoints(tt.input)
|
||||
if !reflect.DeepEqual(tt.expected, got) {
|
||||
t.Errorf("parseEndpoints(%q) = %v, want %v", tt.input, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRestConnectionProvider_Errors(t *testing.T) {
|
||||
t.Run("no endpoints", func(t *testing.T) {
|
||||
_, err := NewRestConnectionProvider("")
|
||||
require.ErrorContains(t, "no REST API endpoints provided", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRestConnectionProvider(t *testing.T) {
|
||||
provider, err := NewRestConnectionProvider("http://host1:3500,http://host2:3500,http://host3:3500")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("initial state", func(t *testing.T) {
|
||||
assert.Equal(t, 3, len(provider.Hosts()))
|
||||
assert.Equal(t, "http://host1:3500", provider.CurrentHost())
|
||||
assert.NotNil(t, provider.HttpClient())
|
||||
})
|
||||
|
||||
t.Run("SwitchHost", func(t *testing.T) {
|
||||
require.NoError(t, provider.SwitchHost(1))
|
||||
assert.Equal(t, "http://host2:3500", provider.CurrentHost())
|
||||
require.NoError(t, provider.SwitchHost(0))
|
||||
assert.Equal(t, "http://host1:3500", provider.CurrentHost())
|
||||
require.ErrorContains(t, "invalid host index", provider.SwitchHost(-1))
|
||||
require.ErrorContains(t, "invalid host index", provider.SwitchHost(3))
|
||||
})
|
||||
|
||||
t.Run("Hosts returns copy", func(t *testing.T) {
|
||||
hosts := provider.Hosts()
|
||||
original := hosts[0]
|
||||
hosts[0] = "modified"
|
||||
assert.Equal(t, original, provider.Hosts()[0])
|
||||
})
|
||||
}
|
||||
|
||||
func TestRestConnectionProvider_WithOptions(t *testing.T) {
|
||||
headers := map[string][]string{"Authorization": {"Bearer token"}}
|
||||
provider, err := NewRestConnectionProvider(
|
||||
"http://localhost:3500",
|
||||
WithHttpHeaders(headers),
|
||||
WithHttpTimeout(30000000000), // 30 seconds in nanoseconds
|
||||
WithTracing(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, provider.HttpClient())
|
||||
assert.Equal(t, "http://localhost:3500", provider.CurrentHost())
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package beacon_api
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -21,37 +21,46 @@ import (
|
||||
|
||||
type reqOption func(*http.Request)
|
||||
|
||||
type RestHandler interface {
|
||||
// Handler defines the interface for making REST API requests.
|
||||
type Handler interface {
|
||||
Get(ctx context.Context, endpoint string, resp any) error
|
||||
GetStatusCode(ctx context.Context, endpoint string) (int, error)
|
||||
GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error)
|
||||
Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp any) error
|
||||
PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error)
|
||||
HttpClient() *http.Client
|
||||
Host() string
|
||||
SetHost(host string)
|
||||
}
|
||||
|
||||
type BeaconApiRestHandler struct {
|
||||
type handler struct {
|
||||
client http.Client
|
||||
host string
|
||||
reqOverrides []reqOption
|
||||
}
|
||||
|
||||
// NewBeaconApiRestHandler returns a RestHandler
|
||||
func NewBeaconApiRestHandler(client http.Client, host string) RestHandler {
|
||||
brh := &BeaconApiRestHandler{
|
||||
// newHandler returns a *handler for internal use within the rest package.
|
||||
func newHandler(client http.Client, host string) *handler {
|
||||
rh := &handler{
|
||||
client: client,
|
||||
host: host,
|
||||
}
|
||||
brh.appendAcceptOverride()
|
||||
return brh
|
||||
rh.appendAcceptOverride()
|
||||
return rh
|
||||
}
|
||||
|
||||
// NewHandler returns a Handler
|
||||
func NewHandler(client http.Client, host string) Handler {
|
||||
rh := &handler{
|
||||
client: client,
|
||||
host: host,
|
||||
}
|
||||
rh.appendAcceptOverride()
|
||||
return rh
|
||||
}
|
||||
|
||||
// appendAcceptOverride enables the Accept header to be customized at runtime via an environment variable.
|
||||
// This is specified as an env var because it is a niche option that prysm may use for performance testing or debugging
|
||||
// bug which users are unlikely to need. Using an env var keeps the set of user-facing flags cleaner.
|
||||
func (c *BeaconApiRestHandler) appendAcceptOverride() {
|
||||
func (c *handler) appendAcceptOverride() {
|
||||
if accept := os.Getenv(params.EnvNameOverrideAccept); accept != "" {
|
||||
c.reqOverrides = append(c.reqOverrides, func(req *http.Request) {
|
||||
req.Header.Set("Accept", accept)
|
||||
@@ -60,18 +69,18 @@ func (c *BeaconApiRestHandler) appendAcceptOverride() {
|
||||
}
|
||||
|
||||
// HttpClient returns the underlying HTTP client of the handler
|
||||
func (c *BeaconApiRestHandler) HttpClient() *http.Client {
|
||||
func (c *handler) HttpClient() *http.Client {
|
||||
return &c.client
|
||||
}
|
||||
|
||||
// Host returns the underlying HTTP host
|
||||
func (c *BeaconApiRestHandler) Host() string {
|
||||
func (c *handler) Host() string {
|
||||
return c.host
|
||||
}
|
||||
|
||||
// Get sends a GET request and decodes the response body as a JSON object into the passed in object.
|
||||
// If an HTTP error is returned, the body is decoded as a DefaultJsonError JSON object and returned as the first return value.
|
||||
func (c *BeaconApiRestHandler) Get(ctx context.Context, endpoint string, resp any) error {
|
||||
func (c *handler) Get(ctx context.Context, endpoint string, resp any) error {
|
||||
url := c.host + endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
@@ -94,7 +103,7 @@ func (c *BeaconApiRestHandler) Get(ctx context.Context, endpoint string, resp an
|
||||
// GetStatusCode sends a GET request and returns only the HTTP status code.
|
||||
// This is useful for endpoints like /eth/v1/node/health that communicate status via HTTP codes
|
||||
// (200 = ready, 206 = syncing, 503 = unavailable) rather than response bodies.
|
||||
func (c *BeaconApiRestHandler) GetStatusCode(ctx context.Context, endpoint string) (int, error) {
|
||||
func (c *handler) GetStatusCode(ctx context.Context, endpoint string) (int, error) {
|
||||
url := c.host + endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
@@ -113,7 +122,7 @@ func (c *BeaconApiRestHandler) GetStatusCode(ctx context.Context, endpoint strin
|
||||
return httpResp.StatusCode, nil
|
||||
}
|
||||
|
||||
func (c *BeaconApiRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) {
|
||||
func (c *handler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) {
|
||||
url := c.host + endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
@@ -168,7 +177,7 @@ func (c *BeaconApiRestHandler) GetSSZ(ctx context.Context, endpoint string) ([]b
|
||||
|
||||
// Post sends a POST request and decodes the response body as a JSON object into the passed in object.
|
||||
// If an HTTP error is returned, the body is decoded as a DefaultJsonError JSON object and returned as the first return value.
|
||||
func (c *BeaconApiRestHandler) Post(
|
||||
func (c *handler) Post(
|
||||
ctx context.Context,
|
||||
apiEndpoint string,
|
||||
headers map[string]string,
|
||||
@@ -204,7 +213,7 @@ func (c *BeaconApiRestHandler) Post(
|
||||
}
|
||||
|
||||
// PostSSZ sends a POST request and prefers an SSZ (application/octet-stream) response body.
|
||||
func (c *BeaconApiRestHandler) PostSSZ(
|
||||
func (c *handler) PostSSZ(
|
||||
ctx context.Context,
|
||||
apiEndpoint string,
|
||||
headers map[string]string,
|
||||
@@ -305,6 +314,6 @@ func decodeResp(httpResp *http.Response, resp any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *BeaconApiRestHandler) SetHost(host string) {
|
||||
func (c *handler) SwitchHost(host string) {
|
||||
c.host = host
|
||||
}
|
||||
@@ -92,7 +92,11 @@ func AcceptEncodingHeaderHandler() Middleware {
|
||||
return
|
||||
}
|
||||
|
||||
gz := gzip.NewWriter(w)
|
||||
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
gzipRW := &gzipResponseWriter{gz: gz, ResponseWriter: w}
|
||||
defer func() {
|
||||
if !gzipRW.zip {
|
||||
|
||||
@@ -9,6 +9,7 @@ go_library(
|
||||
"conversions_blob.go",
|
||||
"conversions_block.go",
|
||||
"conversions_block_execution.go",
|
||||
"conversions_gloas.go",
|
||||
"conversions_lightclient.go",
|
||||
"conversions_state.go",
|
||||
"endpoints_beacon.go",
|
||||
@@ -57,10 +58,12 @@ go_test(
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
],
|
||||
|
||||
@@ -509,17 +509,17 @@ func (s *SignedBlindedBeaconBlockFulu) SigString() string {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type ExecutionPayloadBid struct {
|
||||
ParentBlockHash string `json:"parent_block_hash"`
|
||||
ParentBlockRoot string `json:"parent_block_root"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
BuilderIndex string `json:"builder_index"`
|
||||
Slot string `json:"slot"`
|
||||
Value string `json:"value"`
|
||||
ExecutionPayment string `json:"execution_payment"`
|
||||
BlobKzgCommitmentsRoot string `json:"blob_kzg_commitments_root"`
|
||||
ParentBlockHash string `json:"parent_block_hash"`
|
||||
ParentBlockRoot string `json:"parent_block_root"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
BuilderIndex string `json:"builder_index"`
|
||||
Slot string `json:"slot"`
|
||||
Value string `json:"value"`
|
||||
ExecutionPayment string `json:"execution_payment"`
|
||||
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
|
||||
}
|
||||
|
||||
type SignedExecutionPayloadBid struct {
|
||||
@@ -577,3 +577,17 @@ func (s *SignedBeaconBlockGloas) MessageRawJson() ([]byte, error) {
|
||||
func (s *SignedBeaconBlockGloas) SigString() string {
|
||||
return s.Signature
|
||||
}
|
||||
|
||||
type ExecutionPayloadEnvelope struct {
|
||||
Payload *ExecutionPayloadDeneb `json:"payload"`
|
||||
ExecutionRequests *ExecutionRequests `json:"execution_requests"`
|
||||
BuilderIndex string `json:"builder_index"`
|
||||
BeaconBlockRoot string `json:"beacon_block_root"`
|
||||
Slot string `json:"slot"`
|
||||
StateRoot string `json:"state_root"`
|
||||
}
|
||||
|
||||
type SignedExecutionPayloadEnvelope struct {
|
||||
Message *ExecutionPayloadEnvelope `json:"message"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
@@ -2939,18 +2939,22 @@ func SignedExecutionPayloadBidFromConsensus(b *eth.SignedExecutionPayloadBid) *S
|
||||
}
|
||||
|
||||
func ExecutionPayloadBidFromConsensus(b *eth.ExecutionPayloadBid) *ExecutionPayloadBid {
|
||||
blobKzgCommitments := make([]string, len(b.BlobKzgCommitments))
|
||||
for i := range b.BlobKzgCommitments {
|
||||
blobKzgCommitments[i] = hexutil.Encode(b.BlobKzgCommitments[i])
|
||||
}
|
||||
return &ExecutionPayloadBid{
|
||||
ParentBlockHash: hexutil.Encode(b.ParentBlockHash),
|
||||
ParentBlockRoot: hexutil.Encode(b.ParentBlockRoot),
|
||||
BlockHash: hexutil.Encode(b.BlockHash),
|
||||
PrevRandao: hexutil.Encode(b.PrevRandao),
|
||||
FeeRecipient: hexutil.Encode(b.FeeRecipient),
|
||||
GasLimit: fmt.Sprintf("%d", b.GasLimit),
|
||||
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex),
|
||||
Slot: fmt.Sprintf("%d", b.Slot),
|
||||
Value: fmt.Sprintf("%d", b.Value),
|
||||
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment),
|
||||
BlobKzgCommitmentsRoot: hexutil.Encode(b.BlobKzgCommitmentsRoot),
|
||||
ParentBlockHash: hexutil.Encode(b.ParentBlockHash),
|
||||
ParentBlockRoot: hexutil.Encode(b.ParentBlockRoot),
|
||||
BlockHash: hexutil.Encode(b.BlockHash),
|
||||
PrevRandao: hexutil.Encode(b.PrevRandao),
|
||||
FeeRecipient: hexutil.Encode(b.FeeRecipient),
|
||||
GasLimit: fmt.Sprintf("%d", b.GasLimit),
|
||||
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex),
|
||||
Slot: fmt.Sprintf("%d", b.Slot),
|
||||
Value: fmt.Sprintf("%d", b.Value),
|
||||
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3187,22 +3191,30 @@ func (b *ExecutionPayloadBid) ToConsensus() (*eth.ExecutionPayloadBid, error) {
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "ExecutionPayment")
|
||||
}
|
||||
blobKzgCommitmentsRoot, err := bytesutil.DecodeHexWithLength(b.BlobKzgCommitmentsRoot, fieldparams.RootLength)
|
||||
err = slice.VerifyMaxLength(b.BlobKzgCommitments, fieldparams.MaxBlobCommitmentsPerBlock)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "BlobKzgCommitmentsRoot")
|
||||
return nil, server.NewDecodeError(err, "BlobKzgCommitments")
|
||||
}
|
||||
blobKzgCommitments := make([][]byte, len(b.BlobKzgCommitments))
|
||||
for i, commitment := range b.BlobKzgCommitments {
|
||||
kzg, err := bytesutil.DecodeHexWithLength(commitment, fieldparams.BLSPubkeyLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("BlobKzgCommitments[%d]", i))
|
||||
}
|
||||
blobKzgCommitments[i] = kzg
|
||||
}
|
||||
return ð.ExecutionPayloadBid{
|
||||
ParentBlockHash: parentBlockHash,
|
||||
ParentBlockRoot: parentBlockRoot,
|
||||
BlockHash: blockHash,
|
||||
PrevRandao: prevRandao,
|
||||
FeeRecipient: feeRecipient,
|
||||
GasLimit: gasLimit,
|
||||
BuilderIndex: primitives.BuilderIndex(builderIndex),
|
||||
Slot: primitives.Slot(slot),
|
||||
Value: primitives.Gwei(value),
|
||||
ExecutionPayment: primitives.Gwei(executionPayment),
|
||||
BlobKzgCommitmentsRoot: blobKzgCommitmentsRoot,
|
||||
ParentBlockHash: parentBlockHash,
|
||||
ParentBlockRoot: parentBlockRoot,
|
||||
BlockHash: blockHash,
|
||||
PrevRandao: prevRandao,
|
||||
FeeRecipient: feeRecipient,
|
||||
GasLimit: gasLimit,
|
||||
BuilderIndex: primitives.BuilderIndex(builderIndex),
|
||||
Slot: primitives.Slot(slot),
|
||||
Value: primitives.Gwei(value),
|
||||
ExecutionPayment: primitives.Gwei(executionPayment),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -3263,3 +3275,26 @@ func (d *PayloadAttestationData) ToConsensus() (*eth.PayloadAttestationData, err
|
||||
BlobDataAvailable: d.BlobDataAvailable,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignedExecutionPayloadEnvelopeFromConsensus converts a proto envelope to the API struct.
|
||||
func SignedExecutionPayloadEnvelopeFromConsensus(e *eth.SignedExecutionPayloadEnvelope) (*SignedExecutionPayloadEnvelope, error) {
|
||||
payload, err := ExecutionPayloadDenebFromConsensus(e.Message.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var requests *ExecutionRequests
|
||||
if e.Message.ExecutionRequests != nil {
|
||||
requests = ExecutionRequestsFromConsensus(e.Message.ExecutionRequests)
|
||||
}
|
||||
return &SignedExecutionPayloadEnvelope{
|
||||
Message: &ExecutionPayloadEnvelope{
|
||||
Payload: payload,
|
||||
ExecutionRequests: requests,
|
||||
BuilderIndex: fmt.Sprintf("%d", e.Message.BuilderIndex),
|
||||
BeaconBlockRoot: hexutil.Encode(e.Message.BeaconBlockRoot),
|
||||
Slot: fmt.Sprintf("%d", e.Message.Slot),
|
||||
StateRoot: hexutil.Encode(e.Message.StateRoot),
|
||||
},
|
||||
Signature: hexutil.Encode(e.Signature),
|
||||
}, nil
|
||||
}
|
||||
|
||||
89
api/server/structs/conversions_gloas.go
Normal file
89
api/server/structs/conversions_gloas.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
func ROExecutionPayloadBidFromConsensus(b interfaces.ROExecutionPayloadBid) *ExecutionPayloadBid {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pbh := b.ParentBlockHash()
|
||||
pbr := b.ParentBlockRoot()
|
||||
bh := b.BlockHash()
|
||||
pr := b.PrevRandao()
|
||||
fr := b.FeeRecipient()
|
||||
commitments := b.BlobKzgCommitments()
|
||||
blobKzgCommitments := make([]string, 0, len(commitments))
|
||||
for _, commitment := range commitments {
|
||||
blobKzgCommitments = append(blobKzgCommitments, hexutil.Encode(commitment))
|
||||
}
|
||||
return &ExecutionPayloadBid{
|
||||
ParentBlockHash: hexutil.Encode(pbh[:]),
|
||||
ParentBlockRoot: hexutil.Encode(pbr[:]),
|
||||
BlockHash: hexutil.Encode(bh[:]),
|
||||
PrevRandao: hexutil.Encode(pr[:]),
|
||||
FeeRecipient: hexutil.Encode(fr[:]),
|
||||
GasLimit: fmt.Sprintf("%d", b.GasLimit()),
|
||||
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex()),
|
||||
Slot: fmt.Sprintf("%d", b.Slot()),
|
||||
Value: fmt.Sprintf("%d", b.Value()),
|
||||
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment()),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
}
|
||||
}
|
||||
|
||||
func BuildersFromConsensus(builders []*ethpb.Builder) []*Builder {
|
||||
newBuilders := make([]*Builder, len(builders))
|
||||
for i, b := range builders {
|
||||
newBuilders[i] = BuilderFromConsensus(b)
|
||||
}
|
||||
return newBuilders
|
||||
}
|
||||
|
||||
func BuilderFromConsensus(b *ethpb.Builder) *Builder {
|
||||
return &Builder{
|
||||
Pubkey: hexutil.Encode(b.Pubkey),
|
||||
Version: hexutil.Encode(b.Version),
|
||||
ExecutionAddress: hexutil.Encode(b.ExecutionAddress),
|
||||
Balance: fmt.Sprintf("%d", b.Balance),
|
||||
DepositEpoch: fmt.Sprintf("%d", b.DepositEpoch),
|
||||
WithdrawableEpoch: fmt.Sprintf("%d", b.WithdrawableEpoch),
|
||||
}
|
||||
}
|
||||
|
||||
func BuilderPendingPaymentsFromConsensus(payments []*ethpb.BuilderPendingPayment) []*BuilderPendingPayment {
|
||||
newPayments := make([]*BuilderPendingPayment, len(payments))
|
||||
for i, p := range payments {
|
||||
newPayments[i] = BuilderPendingPaymentFromConsensus(p)
|
||||
}
|
||||
return newPayments
|
||||
}
|
||||
|
||||
func BuilderPendingPaymentFromConsensus(p *ethpb.BuilderPendingPayment) *BuilderPendingPayment {
|
||||
return &BuilderPendingPayment{
|
||||
Weight: fmt.Sprintf("%d", p.Weight),
|
||||
Withdrawal: BuilderPendingWithdrawalFromConsensus(p.Withdrawal),
|
||||
}
|
||||
}
|
||||
|
||||
func BuilderPendingWithdrawalsFromConsensus(withdrawals []*ethpb.BuilderPendingWithdrawal) []*BuilderPendingWithdrawal {
|
||||
newWithdrawals := make([]*BuilderPendingWithdrawal, len(withdrawals))
|
||||
for i, w := range withdrawals {
|
||||
newWithdrawals[i] = BuilderPendingWithdrawalFromConsensus(w)
|
||||
}
|
||||
return newWithdrawals
|
||||
}
|
||||
|
||||
func BuilderPendingWithdrawalFromConsensus(w *ethpb.BuilderPendingWithdrawal) *BuilderPendingWithdrawal {
|
||||
return &BuilderPendingWithdrawal{
|
||||
FeeRecipient: hexutil.Encode(w.FeeRecipient),
|
||||
Amount: fmt.Sprintf("%d", w.Amount),
|
||||
BuilderIndex: fmt.Sprintf("%d", w.BuilderIndex),
|
||||
}
|
||||
}
|
||||
@@ -972,3 +972,223 @@ func BeaconStateFuluFromConsensus(st beaconState.BeaconState) (*BeaconStateFulu,
|
||||
ProposerLookahead: lookahead,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Gloas
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func BeaconStateGloasFromConsensus(st beaconState.BeaconState) (*BeaconStateGloas, error) {
|
||||
srcBr := st.BlockRoots()
|
||||
br := make([]string, len(srcBr))
|
||||
for i, r := range srcBr {
|
||||
br[i] = hexutil.Encode(r)
|
||||
}
|
||||
srcSr := st.StateRoots()
|
||||
sr := make([]string, len(srcSr))
|
||||
for i, r := range srcSr {
|
||||
sr[i] = hexutil.Encode(r)
|
||||
}
|
||||
srcHr := st.HistoricalRoots()
|
||||
hr := make([]string, len(srcHr))
|
||||
for i, r := range srcHr {
|
||||
hr[i] = hexutil.Encode(r)
|
||||
}
|
||||
srcVotes := st.Eth1DataVotes()
|
||||
votes := make([]*Eth1Data, len(srcVotes))
|
||||
for i, e := range srcVotes {
|
||||
votes[i] = Eth1DataFromConsensus(e)
|
||||
}
|
||||
srcVals := st.Validators()
|
||||
vals := make([]*Validator, len(srcVals))
|
||||
for i, v := range srcVals {
|
||||
vals[i] = ValidatorFromConsensus(v)
|
||||
}
|
||||
srcBals := st.Balances()
|
||||
bals := make([]string, len(srcBals))
|
||||
for i, b := range srcBals {
|
||||
bals[i] = fmt.Sprintf("%d", b)
|
||||
}
|
||||
srcRm := st.RandaoMixes()
|
||||
rm := make([]string, len(srcRm))
|
||||
for i, m := range srcRm {
|
||||
rm[i] = hexutil.Encode(m)
|
||||
}
|
||||
srcSlashings := st.Slashings()
|
||||
slashings := make([]string, len(srcSlashings))
|
||||
for i, s := range srcSlashings {
|
||||
slashings[i] = fmt.Sprintf("%d", s)
|
||||
}
|
||||
srcPrevPart, err := st.PreviousEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prevPart := make([]string, len(srcPrevPart))
|
||||
for i, p := range srcPrevPart {
|
||||
prevPart[i] = fmt.Sprintf("%d", p)
|
||||
}
|
||||
srcCurrPart, err := st.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currPart := make([]string, len(srcCurrPart))
|
||||
for i, p := range srcCurrPart {
|
||||
currPart[i] = fmt.Sprintf("%d", p)
|
||||
}
|
||||
srcIs, err := st.InactivityScores()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
is := make([]string, len(srcIs))
|
||||
for i, s := range srcIs {
|
||||
is[i] = fmt.Sprintf("%d", s)
|
||||
}
|
||||
currSc, err := st.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nextSc, err := st.NextSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcHs, err := st.HistoricalSummaries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hs := make([]*HistoricalSummary, len(srcHs))
|
||||
for i, s := range srcHs {
|
||||
hs[i] = HistoricalSummaryFromConsensus(s)
|
||||
}
|
||||
nwi, err := st.NextWithdrawalIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nwvi, err := st.NextWithdrawalValidatorIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
drsi, err := st.DepositRequestsStartIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbtc, err := st.DepositBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ebtc, err := st.ExitBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eee, err := st.EarliestExitEpoch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cbtc, err := st.ConsolidationBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ece, err := st.EarliestConsolidationEpoch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pbd, err := st.PendingDeposits()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ppw, err := st.PendingPartialWithdrawals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc, err := st.PendingConsolidations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcLookahead, err := st.ProposerLookahead()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lookahead := make([]string, len(srcLookahead))
|
||||
for i, v := range srcLookahead {
|
||||
lookahead[i] = fmt.Sprintf("%d", uint64(v))
|
||||
}
|
||||
// Gloas-specific fields
|
||||
lepb, err := st.LatestExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builders, err := st.Builders()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nwbi, err := st.NextWithdrawalBuilderIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
epa, err := st.ExecutionPayloadAvailabilityVector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bpp, err := st.BuilderPendingPayments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bpw, err := st.BuilderPendingWithdrawals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lbh, err := st.LatestBlockHash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pew, err := st.PayloadExpectedWithdrawals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BeaconStateGloas{
|
||||
GenesisTime: fmt.Sprintf("%d", st.GenesisTime().Unix()),
|
||||
GenesisValidatorsRoot: hexutil.Encode(st.GenesisValidatorsRoot()),
|
||||
Slot: fmt.Sprintf("%d", st.Slot()),
|
||||
Fork: ForkFromConsensus(st.Fork()),
|
||||
LatestBlockHeader: BeaconBlockHeaderFromConsensus(st.LatestBlockHeader()),
|
||||
BlockRoots: br,
|
||||
StateRoots: sr,
|
||||
HistoricalRoots: hr,
|
||||
Eth1Data: Eth1DataFromConsensus(st.Eth1Data()),
|
||||
Eth1DataVotes: votes,
|
||||
Eth1DepositIndex: fmt.Sprintf("%d", st.Eth1DepositIndex()),
|
||||
Validators: vals,
|
||||
Balances: bals,
|
||||
RandaoMixes: rm,
|
||||
Slashings: slashings,
|
||||
PreviousEpochParticipation: prevPart,
|
||||
CurrentEpochParticipation: currPart,
|
||||
JustificationBits: hexutil.Encode(st.JustificationBits()),
|
||||
PreviousJustifiedCheckpoint: CheckpointFromConsensus(st.PreviousJustifiedCheckpoint()),
|
||||
CurrentJustifiedCheckpoint: CheckpointFromConsensus(st.CurrentJustifiedCheckpoint()),
|
||||
FinalizedCheckpoint: CheckpointFromConsensus(st.FinalizedCheckpoint()),
|
||||
InactivityScores: is,
|
||||
CurrentSyncCommittee: SyncCommitteeFromConsensus(currSc),
|
||||
NextSyncCommittee: SyncCommitteeFromConsensus(nextSc),
|
||||
NextWithdrawalIndex: fmt.Sprintf("%d", nwi),
|
||||
NextWithdrawalValidatorIndex: fmt.Sprintf("%d", nwvi),
|
||||
HistoricalSummaries: hs,
|
||||
DepositRequestsStartIndex: fmt.Sprintf("%d", drsi),
|
||||
DepositBalanceToConsume: fmt.Sprintf("%d", dbtc),
|
||||
ExitBalanceToConsume: fmt.Sprintf("%d", ebtc),
|
||||
EarliestExitEpoch: fmt.Sprintf("%d", eee),
|
||||
ConsolidationBalanceToConsume: fmt.Sprintf("%d", cbtc),
|
||||
EarliestConsolidationEpoch: fmt.Sprintf("%d", ece),
|
||||
PendingDeposits: PendingDepositsFromConsensus(pbd),
|
||||
PendingPartialWithdrawals: PendingPartialWithdrawalsFromConsensus(ppw),
|
||||
PendingConsolidations: PendingConsolidationsFromConsensus(pc),
|
||||
ProposerLookahead: lookahead,
|
||||
LatestExecutionPayloadBid: ROExecutionPayloadBidFromConsensus(lepb),
|
||||
Builders: BuildersFromConsensus(builders),
|
||||
NextWithdrawalBuilderIndex: fmt.Sprintf("%d", nwbi),
|
||||
ExecutionPayloadAvailability: hexutil.Encode(epa),
|
||||
BuilderPendingPayments: BuilderPendingPaymentsFromConsensus(bpp),
|
||||
BuilderPendingWithdrawals: BuilderPendingWithdrawalsFromConsensus(bpw),
|
||||
LatestBlockHash: hexutil.Encode(lbh[:]),
|
||||
PayloadExpectedWithdrawals: WithdrawalsFromConsensus(pew),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
@@ -355,3 +359,214 @@ func TestIndexedAttestation_ToConsensus(t *testing.T) {
|
||||
_, err := a.ToConsensus()
|
||||
require.ErrorContains(t, errNilValue.Error(), err)
|
||||
}
|
||||
|
||||
func TestROExecutionPayloadBidFromConsensus(t *testing.T) {
|
||||
t.Run("empty blobkzg commitments", func(t *testing.T) {
|
||||
bid := ð.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x03}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
|
||||
GasLimit: 100,
|
||||
BuilderIndex: 7,
|
||||
Slot: 9,
|
||||
Value: 11,
|
||||
ExecutionPayment: 22,
|
||||
BlobKzgCommitments: [][]byte{},
|
||||
}
|
||||
roBid, err := blocks.WrappedROExecutionPayloadBid(bid)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := ROExecutionPayloadBidFromConsensus(roBid)
|
||||
want := &ExecutionPayloadBid{
|
||||
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
|
||||
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
|
||||
BlockHash: hexutil.Encode(bid.BlockHash),
|
||||
PrevRandao: hexutil.Encode(bid.PrevRandao),
|
||||
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
|
||||
GasLimit: "100",
|
||||
BuilderIndex: "7",
|
||||
Slot: "9",
|
||||
Value: "11",
|
||||
ExecutionPayment: "22",
|
||||
BlobKzgCommitments: []string{},
|
||||
}
|
||||
assert.DeepEqual(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
bid := ð.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x03}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
|
||||
GasLimit: 100,
|
||||
BuilderIndex: 7,
|
||||
Slot: 9,
|
||||
Value: 11,
|
||||
ExecutionPayment: 22,
|
||||
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x06}, 48)},
|
||||
}
|
||||
roBid, err := blocks.WrappedROExecutionPayloadBid(bid)
|
||||
require.NoError(t, err)
|
||||
|
||||
var bkcs []string
|
||||
for _, commitment := range roBid.BlobKzgCommitments() {
|
||||
bkcs = append(bkcs, hexutil.Encode(commitment))
|
||||
}
|
||||
|
||||
got := ROExecutionPayloadBidFromConsensus(roBid)
|
||||
want := &ExecutionPayloadBid{
|
||||
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
|
||||
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
|
||||
BlockHash: hexutil.Encode(bid.BlockHash),
|
||||
PrevRandao: hexutil.Encode(bid.PrevRandao),
|
||||
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
|
||||
GasLimit: "100",
|
||||
BuilderIndex: "7",
|
||||
Slot: "9",
|
||||
Value: "11",
|
||||
ExecutionPayment: "22",
|
||||
BlobKzgCommitments: bkcs,
|
||||
}
|
||||
assert.DeepEqual(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderConversionsFromConsensus(t *testing.T) {
|
||||
builder := ð.Builder{
|
||||
Pubkey: bytes.Repeat([]byte{0xAA}, 48),
|
||||
Version: bytes.Repeat([]byte{0x01}, 4),
|
||||
ExecutionAddress: bytes.Repeat([]byte{0xBB}, 20),
|
||||
Balance: 42,
|
||||
DepositEpoch: 3,
|
||||
WithdrawableEpoch: 4,
|
||||
}
|
||||
wantBuilder := &Builder{
|
||||
Pubkey: hexutil.Encode(builder.Pubkey),
|
||||
Version: hexutil.Encode(builder.Version),
|
||||
ExecutionAddress: hexutil.Encode(builder.ExecutionAddress),
|
||||
Balance: "42",
|
||||
DepositEpoch: "3",
|
||||
WithdrawableEpoch: "4",
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, wantBuilder, BuilderFromConsensus(builder))
|
||||
assert.DeepEqual(t, []*Builder{wantBuilder}, BuildersFromConsensus([]*eth.Builder{builder}))
|
||||
}
|
||||
|
||||
func TestBuilderPendingPaymentConversionsFromConsensus(t *testing.T) {
|
||||
withdrawal := ð.BuilderPendingWithdrawal{
|
||||
FeeRecipient: bytes.Repeat([]byte{0x10}, 20),
|
||||
Amount: 15,
|
||||
BuilderIndex: 2,
|
||||
}
|
||||
payment := ð.BuilderPendingPayment{
|
||||
Weight: 5,
|
||||
Withdrawal: withdrawal,
|
||||
}
|
||||
wantWithdrawal := &BuilderPendingWithdrawal{
|
||||
FeeRecipient: hexutil.Encode(withdrawal.FeeRecipient),
|
||||
Amount: "15",
|
||||
BuilderIndex: "2",
|
||||
}
|
||||
wantPayment := &BuilderPendingPayment{
|
||||
Weight: "5",
|
||||
Withdrawal: wantWithdrawal,
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, wantPayment, BuilderPendingPaymentFromConsensus(payment))
|
||||
assert.DeepEqual(t, []*BuilderPendingPayment{wantPayment}, BuilderPendingPaymentsFromConsensus([]*eth.BuilderPendingPayment{payment}))
|
||||
assert.DeepEqual(t, wantWithdrawal, BuilderPendingWithdrawalFromConsensus(withdrawal))
|
||||
assert.DeepEqual(t, []*BuilderPendingWithdrawal{wantWithdrawal}, BuilderPendingWithdrawalsFromConsensus([]*eth.BuilderPendingWithdrawal{withdrawal}))
|
||||
}
|
||||
|
||||
func TestBeaconStateGloasFromConsensus(t *testing.T) {
|
||||
st, err := util.NewBeaconStateGloas(func(state *eth.BeaconStateGloas) error {
|
||||
state.GenesisTime = 123
|
||||
state.GenesisValidatorsRoot = bytes.Repeat([]byte{0x10}, 32)
|
||||
state.Slot = 5
|
||||
state.ProposerLookahead = []uint64{1, 2}
|
||||
state.LatestExecutionPayloadBid = ð.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32),
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x12}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x13}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x14}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x15}, 20),
|
||||
GasLimit: 64,
|
||||
BuilderIndex: 3,
|
||||
Slot: 5,
|
||||
Value: 99,
|
||||
ExecutionPayment: 7,
|
||||
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x16}, 48)},
|
||||
}
|
||||
state.Builders = []*eth.Builder{
|
||||
{
|
||||
Pubkey: bytes.Repeat([]byte{0x20}, 48),
|
||||
Version: bytes.Repeat([]byte{0x21}, 4),
|
||||
ExecutionAddress: bytes.Repeat([]byte{0x22}, 20),
|
||||
Balance: 88,
|
||||
DepositEpoch: 1,
|
||||
WithdrawableEpoch: 2,
|
||||
},
|
||||
}
|
||||
state.NextWithdrawalBuilderIndex = 9
|
||||
state.ExecutionPayloadAvailability = []byte{0x01, 0x02}
|
||||
state.BuilderPendingPayments = []*eth.BuilderPendingPayment{
|
||||
{
|
||||
Weight: 3,
|
||||
Withdrawal: ð.BuilderPendingWithdrawal{
|
||||
FeeRecipient: bytes.Repeat([]byte{0x23}, 20),
|
||||
Amount: 4,
|
||||
BuilderIndex: 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
state.BuilderPendingWithdrawals = []*eth.BuilderPendingWithdrawal{
|
||||
{
|
||||
FeeRecipient: bytes.Repeat([]byte{0x24}, 20),
|
||||
Amount: 6,
|
||||
BuilderIndex: 7,
|
||||
},
|
||||
}
|
||||
state.LatestBlockHash = bytes.Repeat([]byte{0x25}, 32)
|
||||
state.PayloadExpectedWithdrawals = []*enginev1.Withdrawal{
|
||||
{Index: 1, ValidatorIndex: 2, Address: bytes.Repeat([]byte{0x26}, 20), Amount: 10},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := BeaconStateGloasFromConsensus(st)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "123", got.GenesisTime)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x10}, 32)), got.GenesisValidatorsRoot)
|
||||
require.Equal(t, "5", got.Slot)
|
||||
require.DeepEqual(t, []string{"1", "2"}, got.ProposerLookahead)
|
||||
require.Equal(t, "9", got.NextWithdrawalBuilderIndex)
|
||||
require.Equal(t, hexutil.Encode([]byte{0x01, 0x02}), got.ExecutionPayloadAvailability)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x25}, 32)), got.LatestBlockHash)
|
||||
|
||||
require.NotNil(t, got.LatestExecutionPayloadBid)
|
||||
require.Equal(t, "64", got.LatestExecutionPayloadBid.GasLimit)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x11}, 32)), got.LatestExecutionPayloadBid.ParentBlockHash)
|
||||
|
||||
require.NotNil(t, got.Builders)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x20}, 48)), got.Builders[0].Pubkey)
|
||||
require.Equal(t, "88", got.Builders[0].Balance)
|
||||
|
||||
require.Equal(t, "3", got.BuilderPendingPayments[0].Weight)
|
||||
require.Equal(t, "4", got.BuilderPendingPayments[0].Withdrawal.Amount)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x23}, 20)), got.BuilderPendingPayments[0].Withdrawal.FeeRecipient)
|
||||
|
||||
require.Equal(t, "6", got.BuilderPendingWithdrawals[0].Amount)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x24}, 20)), got.BuilderPendingWithdrawals[0].FeeRecipient)
|
||||
|
||||
require.Equal(t, "1", got.PayloadExpectedWithdrawals[0].WithdrawalIndex)
|
||||
require.Equal(t, "2", got.PayloadExpectedWithdrawals[0].ValidatorIndex)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x26}, 20)), got.PayloadExpectedWithdrawals[0].ExecutionAddress)
|
||||
require.Equal(t, "10", got.PayloadExpectedWithdrawals[0].Amount)
|
||||
}
|
||||
|
||||
@@ -285,6 +285,13 @@ type GetBlobsResponse struct {
|
||||
Data []string `json:"data"` //blobs
|
||||
}
|
||||
|
||||
type GetExecutionPayloadEnvelopeResponse struct {
|
||||
Version string `json:"version"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data *SignedExecutionPayloadEnvelope `json:"data"`
|
||||
}
|
||||
|
||||
type SSZQueryRequest struct {
|
||||
Query string `json:"query"`
|
||||
IncludeProof bool `json:"include_proof,omitempty"`
|
||||
|
||||
@@ -112,3 +112,8 @@ type LightClientOptimisticUpdateEvent struct {
|
||||
Version string `json:"version"`
|
||||
Data *LightClientOptimisticUpdate `json:"data"`
|
||||
}
|
||||
|
||||
type PayloadEvent struct {
|
||||
Slot string `json:"slot"`
|
||||
BlockRoot string `json:"block_root"`
|
||||
}
|
||||
|
||||
@@ -63,6 +63,19 @@ type PeerCount struct {
|
||||
Connected string `json:"connected"`
|
||||
Disconnecting string `json:"disconnecting"`
|
||||
}
|
||||
type GetVersionV2Response struct {
|
||||
Data *VersionV2 `json:"data"`
|
||||
}
|
||||
type VersionV2 struct {
|
||||
BeaconNode *ClientVersionV1 `json:"beacon_node"`
|
||||
ExecutionClient *ClientVersionV1 `json:"execution_client,omitempty"`
|
||||
}
|
||||
type ClientVersionV1 struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
}
|
||||
|
||||
type GetVersionResponse struct {
|
||||
Data *Version `json:"data"`
|
||||
|
||||
@@ -74,6 +74,18 @@ type SyncCommitteeDuty struct {
|
||||
ValidatorSyncCommitteeIndices []string `json:"validator_sync_committee_indices"`
|
||||
}
|
||||
|
||||
type GetPTCDutiesResponse struct {
|
||||
DependentRoot string `json:"dependent_root"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Data []*PTCDuty `json:"data"`
|
||||
}
|
||||
|
||||
type PTCDuty struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
Slot string `json:"slot"`
|
||||
}
|
||||
|
||||
// ProduceBlockV3Response is a wrapper json object for the returned block from the ProduceBlockV3 endpoint
|
||||
type ProduceBlockV3Response struct {
|
||||
Version string `json:"version"`
|
||||
|
||||
@@ -262,3 +262,23 @@ type PendingConsolidation struct {
|
||||
SourceIndex string `json:"source_index"`
|
||||
TargetIndex string `json:"target_index"`
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
Version string `json:"version"`
|
||||
ExecutionAddress string `json:"execution_address"`
|
||||
Balance string `json:"balance"`
|
||||
DepositEpoch string `json:"deposit_epoch"`
|
||||
WithdrawableEpoch string `json:"withdrawable_epoch"`
|
||||
}
|
||||
|
||||
type BuilderPendingPayment struct {
|
||||
Weight string `json:"weight"`
|
||||
Withdrawal *BuilderPendingWithdrawal `json:"withdrawal"`
|
||||
}
|
||||
|
||||
type BuilderPendingWithdrawal struct {
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
Amount string `json:"amount"`
|
||||
BuilderIndex string `json:"builder_index"`
|
||||
}
|
||||
|
||||
@@ -221,3 +221,51 @@ type BeaconStateFulu struct {
|
||||
PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"`
|
||||
ProposerLookahead []string `json:"proposer_lookahead"`
|
||||
}
|
||||
|
||||
type BeaconStateGloas struct {
|
||||
GenesisTime string `json:"genesis_time"`
|
||||
GenesisValidatorsRoot string `json:"genesis_validators_root"`
|
||||
Slot string `json:"slot"`
|
||||
Fork *Fork `json:"fork"`
|
||||
LatestBlockHeader *BeaconBlockHeader `json:"latest_block_header"`
|
||||
BlockRoots []string `json:"block_roots"`
|
||||
StateRoots []string `json:"state_roots"`
|
||||
HistoricalRoots []string `json:"historical_roots"`
|
||||
Eth1Data *Eth1Data `json:"eth1_data"`
|
||||
Eth1DataVotes []*Eth1Data `json:"eth1_data_votes"`
|
||||
Eth1DepositIndex string `json:"eth1_deposit_index"`
|
||||
Validators []*Validator `json:"validators"`
|
||||
Balances []string `json:"balances"`
|
||||
RandaoMixes []string `json:"randao_mixes"`
|
||||
Slashings []string `json:"slashings"`
|
||||
PreviousEpochParticipation []string `json:"previous_epoch_participation"`
|
||||
CurrentEpochParticipation []string `json:"current_epoch_participation"`
|
||||
JustificationBits string `json:"justification_bits"`
|
||||
PreviousJustifiedCheckpoint *Checkpoint `json:"previous_justified_checkpoint"`
|
||||
CurrentJustifiedCheckpoint *Checkpoint `json:"current_justified_checkpoint"`
|
||||
FinalizedCheckpoint *Checkpoint `json:"finalized_checkpoint"`
|
||||
InactivityScores []string `json:"inactivity_scores"`
|
||||
CurrentSyncCommittee *SyncCommittee `json:"current_sync_committee"`
|
||||
NextSyncCommittee *SyncCommittee `json:"next_sync_committee"`
|
||||
NextWithdrawalIndex string `json:"next_withdrawal_index"`
|
||||
NextWithdrawalValidatorIndex string `json:"next_withdrawal_validator_index"`
|
||||
HistoricalSummaries []*HistoricalSummary `json:"historical_summaries"`
|
||||
DepositRequestsStartIndex string `json:"deposit_requests_start_index"`
|
||||
DepositBalanceToConsume string `json:"deposit_balance_to_consume"`
|
||||
ExitBalanceToConsume string `json:"exit_balance_to_consume"`
|
||||
EarliestExitEpoch string `json:"earliest_exit_epoch"`
|
||||
ConsolidationBalanceToConsume string `json:"consolidation_balance_to_consume"`
|
||||
EarliestConsolidationEpoch string `json:"earliest_consolidation_epoch"`
|
||||
PendingDeposits []*PendingDeposit `json:"pending_deposits"`
|
||||
PendingPartialWithdrawals []*PendingPartialWithdrawal `json:"pending_partial_withdrawals"`
|
||||
PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"`
|
||||
ProposerLookahead []string `json:"proposer_lookahead"`
|
||||
LatestExecutionPayloadBid *ExecutionPayloadBid `json:"latest_execution_payload_bid"`
|
||||
Builders []*Builder `json:"builders"`
|
||||
NextWithdrawalBuilderIndex string `json:"next_withdrawal_builder_index"`
|
||||
ExecutionPayloadAvailability string `json:"execution_payload_availability"`
|
||||
BuilderPendingPayments []*BuilderPendingPayment `json:"builder_pending_payments"`
|
||||
BuilderPendingWithdrawals []*BuilderPendingWithdrawal `json:"builder_pending_withdrawals"`
|
||||
LatestBlockHash string `json:"latest_block_hash"`
|
||||
PayloadExpectedWithdrawals []*Withdrawal `json:"payload_expected_withdrawals"`
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ go_library(
|
||||
"error.go",
|
||||
"execution_engine.go",
|
||||
"forkchoice_update_execution.go",
|
||||
"gloas.go",
|
||||
"head.go",
|
||||
"head_sync_committee_info.go",
|
||||
"init_sync_process_block.go",
|
||||
@@ -27,6 +28,8 @@ go_library(
|
||||
"receive_blob.go",
|
||||
"receive_block.go",
|
||||
"receive_data_column.go",
|
||||
"receive_execution_payload_envelope.go",
|
||||
"receive_payload_attestation_message.go",
|
||||
"service.go",
|
||||
"setup_forkchoice.go",
|
||||
"tracked_proposer.go",
|
||||
@@ -50,6 +53,7 @@ go_library(
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/feed:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/peerdas:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
@@ -85,6 +89,7 @@ go_library(
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/logs:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//monitoring/tracing:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
@@ -118,6 +123,7 @@ go_test(
|
||||
"error_test.go",
|
||||
"execution_engine_test.go",
|
||||
"forkchoice_update_execution_test.go",
|
||||
"gloas_test.go",
|
||||
"head_sync_committee_info_test.go",
|
||||
"head_test.go",
|
||||
"init_sync_process_block_test.go",
|
||||
@@ -130,6 +136,7 @@ go_test(
|
||||
"process_block_test.go",
|
||||
"receive_attestation_test.go",
|
||||
"receive_block_test.go",
|
||||
"receive_payload_attestation_message_test.go",
|
||||
"service_norace_test.go",
|
||||
"service_test.go",
|
||||
"setup_forkchoice_test.go",
|
||||
@@ -147,6 +154,7 @@ go_test(
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/peerdas:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
@@ -178,6 +186,7 @@ go_test(
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/payload-attribute:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//container/trie:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -46,6 +47,7 @@ type ForkchoiceFetcher interface {
|
||||
HighestReceivedBlockSlot() primitives.Slot
|
||||
ReceivedBlocksLastEpoch() (uint64, error)
|
||||
InsertNode(context.Context, state.BeaconState, consensus_blocks.ROBlock) error
|
||||
InsertPayload(interfaces.ROExecutionPayloadEnvelope) error
|
||||
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
|
||||
NewSlot(context.Context, primitives.Slot) error
|
||||
ProposerBoost() [32]byte
|
||||
@@ -113,6 +115,7 @@ type FinalizationFetcher interface {
|
||||
FinalizedBlockHash() [32]byte
|
||||
InForkchoice([32]byte) bool
|
||||
IsFinalized(ctx context.Context, blockRoot [32]byte) bool
|
||||
ParentPayloadReady(interfaces.ReadOnlyBeaconBlock) bool
|
||||
}
|
||||
|
||||
// OptimisticModeFetcher retrieves information about optimistic status of the node.
|
||||
@@ -402,6 +405,32 @@ func (s *Service) InForkchoice(root [32]byte) bool {
|
||||
return s.cfg.ForkChoiceStore.HasNode(root)
|
||||
}
|
||||
|
||||
// ParentPayloadReady returns true if the block's parent payload is available
|
||||
// in forkchoice. For pre-Gloas blocks or blocks building on empty, this always
|
||||
// returns true. For blocks building on full, it checks that the full node
|
||||
// exists.
|
||||
func (s *Service) ParentPayloadReady(blk interfaces.ReadOnlyBeaconBlock) bool {
|
||||
if blk.Version() < version.Gloas {
|
||||
return true
|
||||
}
|
||||
parentRoot := blk.ParentRoot()
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
blockHash, err := s.cfg.ForkChoiceStore.BlockHash(parentRoot)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bid, err := blk.Body().SignedExecutionPayloadBid()
|
||||
if err != nil || bid == nil || bid.Message == nil {
|
||||
return false
|
||||
}
|
||||
parentBlockHash := [32]byte(bid.Message.ParentBlockHash)
|
||||
if parentBlockHash != blockHash {
|
||||
return true // builds on empty, no full node needed
|
||||
}
|
||||
return s.cfg.ForkChoiceStore.HasFullNode(parentRoot)
|
||||
}
|
||||
|
||||
// IsOptimisticForRoot takes the root as argument instead of the current head
|
||||
// and returns true if it is optimistic.
|
||||
func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, error) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/forkchoice"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
@@ -55,6 +56,13 @@ func (s *Service) InsertNode(ctx context.Context, st state.BeaconState, block co
|
||||
return s.cfg.ForkChoiceStore.InsertNode(ctx, st, block)
|
||||
}
|
||||
|
||||
// InsertPayload is a wrapper for payload insertion which is self locked
|
||||
func (s *Service) InsertPayload(pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
return s.cfg.ForkChoiceStore.InsertPayload(pe)
|
||||
}
|
||||
|
||||
// ForkChoiceDump returns the corresponding value from forkchoice
|
||||
func (s *Service) ForkChoiceDump(ctx context.Context) (*forkchoice.Dump, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
@@ -128,6 +136,13 @@ func (s *Service) hashForGenesisBlock(ctx context.Context, root [32]byte) ([]byt
|
||||
return bytesutil.SafeCopyBytes(header.BlockHash()), nil
|
||||
}
|
||||
|
||||
// CanonicalNodeAtSlot wraps the corresponding method in forkchoice
|
||||
func (s *Service) CanonicalNodeAtSlot(slot primitives.Slot) ([32]byte, bool) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.CanonicalNodeAtSlot(slot)
|
||||
}
|
||||
|
||||
// DependentRoot wraps the corresponding method in forkchoice
|
||||
func (s *Service) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
|
||||
@@ -620,6 +620,121 @@ func TestService_IsFinalized(t *testing.T) {
|
||||
require.Equal(t, false, c.IsFinalized(ctx, [32]byte{'c'}))
|
||||
}
|
||||
|
||||
func TestParentPayloadReady(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, tr := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
fcs := tr.fcs
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
parentBlockHash := [32]byte{10}
|
||||
zeroHash := params.BeaconConfig().ZeroHash
|
||||
|
||||
// Insert parent node into forkchoice.
|
||||
st, parentROBlock, err := prepareGloasForkchoiceState(ctx, 1, parentRoot, zeroHash, parentBlockHash, zeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, parentROBlock))
|
||||
|
||||
t.Run("pre-Gloas always true", func(t *testing.T) {
|
||||
blk := util.HydrateSignedBeaconBlockDeneb(ðpb.SignedBeaconBlockDeneb{
|
||||
Block: ðpb.BeaconBlockDeneb{ParentRoot: parentRoot[:]},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
|
||||
t.Run("parent not in forkchoice", func(t *testing.T) {
|
||||
unknownParent := [32]byte{99}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: []byte{20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: unknownParent[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
|
||||
t.Run("builds on empty", func(t *testing.T) {
|
||||
differentHash := [32]byte{99}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: []byte{20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
ParentBlockHash: differentHash[:],
|
||||
},
|
||||
})
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
|
||||
t.Run("builds on full without payload", func(t *testing.T) {
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: []byte{20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
|
||||
t.Run("builds on full with payload", func(t *testing.T) {
|
||||
pe, err := blocks.WrappedROExecutionPayloadEnvelope(ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: parentRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertPayload(pe))
|
||||
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: []byte{20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_hashForGenesisRoot(t *testing.T) {
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
ctx := t.Context()
|
||||
|
||||
@@ -101,11 +101,16 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *fcuConfig) (*
|
||||
if len(lastValidHash) == 0 {
|
||||
lastValidHash = defaultLatestValidHash
|
||||
}
|
||||
invalidRoots, err := s.cfg.ForkChoiceStore.SetOptimisticToInvalid(ctx, headRoot, headBlk.ParentRoot(), bytesutil.ToBytes32(lastValidHash))
|
||||
// this call has guaranteed to have the `headRoot` with its payload in forkchoice.
|
||||
invalidRoots, err := s.cfg.ForkChoiceStore.SetOptimisticToInvalid(ctx, headRoot, headBlk.ParentRoot(), bytesutil.ToBytes32(headPayload.ParentHash()), bytesutil.ToBytes32(lastValidHash))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not set head root to invalid")
|
||||
return nil, nil
|
||||
}
|
||||
// TODO: Gloas, we should not include the head root in this call
|
||||
if len(invalidRoots) == 0 || invalidRoots[0] != headRoot {
|
||||
invalidRoots = append([][32]byte{headRoot}, invalidRoots...)
|
||||
}
|
||||
if err := s.removeInvalidBlockAndState(ctx, invalidRoots); err != nil {
|
||||
log.WithError(err).Error("Could not remove invalid block and state")
|
||||
return nil, nil
|
||||
@@ -228,6 +233,9 @@ func (s *Service) notifyNewPayload(ctx context.Context, stVersion int, header in
|
||||
if stVersion < version.Bellatrix {
|
||||
return true, nil
|
||||
}
|
||||
if blk.Version() >= version.Gloas {
|
||||
return false, nil
|
||||
}
|
||||
body := blk.Block().Body()
|
||||
enabled, err := blocks.IsExecutionEnabledUsingHeader(header, body)
|
||||
if err != nil {
|
||||
@@ -290,10 +298,10 @@ func (s *Service) notifyNewPayload(ctx context.Context, stVersion int, header in
|
||||
return false, errors.WithMessage(ErrUndefinedExecutionEngineError, err.Error())
|
||||
}
|
||||
|
||||
// reportInvalidBlock deals with the event that an invalid block was detected by the execution layer
|
||||
func (s *Service) pruneInvalidBlock(ctx context.Context, root, parentRoot, lvh [32]byte) error {
|
||||
// pruneInvalidBlock deals with the event that an invalid block was detected by the execution layer
|
||||
func (s *Service) pruneInvalidBlock(ctx context.Context, root, parentRoot, parentHash [32]byte, lvh [32]byte) error {
|
||||
newPayloadInvalidNodeCount.Inc()
|
||||
invalidRoots, err := s.cfg.ForkChoiceStore.SetOptimisticToInvalid(ctx, root, parentRoot, lvh)
|
||||
invalidRoots, err := s.cfg.ForkChoiceStore.SetOptimisticToInvalid(ctx, root, parentRoot, parentHash, lvh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -429,9 +429,9 @@ func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) {
|
||||
|
||||
// Insert Attestations to D, F and G so that they have higher weight than D
|
||||
// Ensure G is head
|
||||
fcs.ProcessAttestation(ctx, []uint64{0}, brd, 1)
|
||||
fcs.ProcessAttestation(ctx, []uint64{1}, brf, 1)
|
||||
fcs.ProcessAttestation(ctx, []uint64{2}, brg, 1)
|
||||
fcs.ProcessAttestation(ctx, []uint64{0}, brd, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.ProcessAttestation(ctx, []uint64{1}, brf, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.ProcessAttestation(ctx, []uint64{2}, brg, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.SetBalancesByRooter(service.cfg.StateGen.ActiveNonSlashedBalancesByRoot)
|
||||
jc := &forkchoicetypes.Checkpoint{Epoch: 0, Root: bra}
|
||||
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, jc))
|
||||
@@ -465,9 +465,9 @@ func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, brd, headRoot)
|
||||
|
||||
// Ensure F and G where removed but their parent E wasn't
|
||||
require.Equal(t, false, fcs.HasNode(brf))
|
||||
require.Equal(t, false, fcs.HasNode(brg))
|
||||
// Ensure F and G's full nodes were removed but their empty (consensus) nodes remain, as does E
|
||||
require.Equal(t, true, fcs.HasNode(brf))
|
||||
require.Equal(t, true, fcs.HasNode(brg))
|
||||
require.Equal(t, true, fcs.HasNode(bre))
|
||||
}
|
||||
|
||||
@@ -703,14 +703,13 @@ func Test_reportInvalidBlock(t *testing.T) {
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, root))
|
||||
|
||||
require.NoError(t, fcs.SetOptimisticToValid(ctx, [32]byte{'A'}))
|
||||
err = service.pruneInvalidBlock(ctx, [32]byte{'D'}, [32]byte{'C'}, [32]byte{'a'})
|
||||
err = service.pruneInvalidBlock(ctx, [32]byte{'D'}, [32]byte{'C'}, [32]byte{'c'}, [32]byte{'a'})
|
||||
require.Equal(t, IsInvalidBlock(err), true)
|
||||
require.Equal(t, InvalidBlockLVH(err), [32]byte{'a'})
|
||||
invalidRoots := InvalidAncestorRoots(err)
|
||||
require.Equal(t, 3, len(invalidRoots))
|
||||
require.Equal(t, 2, len(invalidRoots))
|
||||
require.Equal(t, [32]byte{'D'}, invalidRoots[0])
|
||||
require.Equal(t, [32]byte{'C'}, invalidRoots[1])
|
||||
require.Equal(t, [32]byte{'B'}, invalidRoots[2])
|
||||
}
|
||||
|
||||
func Test_GetPayloadAttribute(t *testing.T) {
|
||||
@@ -785,7 +784,7 @@ func Test_GetPayloadAttributeV2(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_GetPayloadAttributeV3(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
testCases := []struct {
|
||||
name string
|
||||
st bstate.BeaconState
|
||||
}{
|
||||
|
||||
@@ -56,7 +56,7 @@ type fcuConfig struct {
|
||||
// sendFCU handles the logic to notify the engine of a forckhoice update
|
||||
// when processing an incoming block during regular sync. It
|
||||
// always updates the shuffling caches and handles epoch transitions .
|
||||
func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
func (s *Service) sendFCU(cfg *postBlockProcessConfig) {
|
||||
if cfg.postState.Version() < version.Fulu {
|
||||
// update the caches to compute the right proposer index
|
||||
// this function is called under a forkchoice lock which we need to release.
|
||||
@@ -64,7 +64,8 @@ func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
s.updateCachesPostBlockProcessing(cfg)
|
||||
s.ForkChoicer().Lock()
|
||||
}
|
||||
if err := s.getFCUArgs(cfg, fcuArgs); err != nil {
|
||||
fcuArgs, err := s.getFCUArgs(cfg)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get forkchoice update argument")
|
||||
return
|
||||
}
|
||||
|
||||
45
beacon-chain/blockchain/gloas.go
Normal file
45
beacon-chain/blockchain/gloas.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// getLookupParentRoot returns the root that serves as key to generate the parent state for the passed beacon block.
|
||||
// if it is based on empty or it is pre-Gloas, it is the parent root of the block, otherwise if it is based on full it is
|
||||
// the parent hash.
|
||||
// The caller of this function should not hold a lock on forkchoice.
|
||||
func (s *Service) getLookupParentRoot(b consensus_blocks.ROBlock) ([32]byte, error) {
|
||||
bl := b.Block()
|
||||
parentRoot := bl.ParentRoot()
|
||||
if b.Version() < version.Gloas {
|
||||
return parentRoot, nil
|
||||
}
|
||||
parentSlot, err := s.cfg.ForkChoiceStore.Slot(parentRoot)
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "failed to get slot for parent root")
|
||||
}
|
||||
|
||||
if slots.ToEpoch(parentSlot) < params.BeaconConfig().GloasForkEpoch {
|
||||
return parentRoot, nil
|
||||
}
|
||||
blockHash, err := s.cfg.ForkChoiceStore.BlockHash(parentRoot)
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "failed to get block hash for parent root")
|
||||
}
|
||||
bid, err := bl.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "failed to get signed execution payload bid from block body")
|
||||
}
|
||||
if bid == nil || bid.Message == nil || len(bid.Message.ParentBlockHash) != 32 {
|
||||
return [32]byte{}, errors.New("invalid signed execution payload bid message")
|
||||
}
|
||||
parentHash := [32]byte(bid.Message.ParentBlockHash)
|
||||
if blockHash == parentHash {
|
||||
return parentHash, nil
|
||||
}
|
||||
return parentRoot, nil
|
||||
}
|
||||
648
beacon-chain/blockchain/gloas_test.go
Normal file
648
beacon-chain/blockchain/gloas_test.go
Normal file
@@ -0,0 +1,648 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/execution"
|
||||
mockExecution "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
payloadattribute "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attribute"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
)
|
||||
|
||||
func prepareGloasForkchoiceState(
|
||||
_ context.Context,
|
||||
slot primitives.Slot,
|
||||
blockRoot [32]byte,
|
||||
parentRoot [32]byte,
|
||||
blockHash [32]byte,
|
||||
parentBlockHash [32]byte,
|
||||
justifiedEpoch primitives.Epoch,
|
||||
finalizedEpoch primitives.Epoch,
|
||||
) (state.BeaconState, blocks.ROBlock, error) {
|
||||
blockHeader := ðpb.BeaconBlockHeader{
|
||||
ParentRoot: parentRoot[:],
|
||||
}
|
||||
|
||||
justifiedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: justifiedEpoch,
|
||||
}
|
||||
|
||||
finalizedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: finalizedEpoch,
|
||||
}
|
||||
|
||||
builderPendingPayments := make([]*ethpb.BuilderPendingPayment, 64)
|
||||
for i := range builderPendingPayments {
|
||||
builderPendingPayments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
base := ðpb.BeaconStateGloas{
|
||||
Slot: slot,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
CurrentJustifiedCheckpoint: justifiedCheckpoint,
|
||||
FinalizedCheckpoint: finalizedCheckpoint,
|
||||
LatestBlockHeader: blockHeader,
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: [][]byte{make([]byte, 48)},
|
||||
},
|
||||
Builders: make([]*ethpb.Builder, 0),
|
||||
BuilderPendingPayments: builderPendingPayments,
|
||||
ExecutionPayloadAvailability: make([]byte, 1024),
|
||||
LatestBlockHash: make([]byte, 32),
|
||||
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
ProposerLookahead: make([]uint64, 64),
|
||||
}
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
roblock, err := blocks.NewROBlockWithRoot(signed, blockRoot)
|
||||
return st, roblock, err
|
||||
}
|
||||
|
||||
func testGloasState(t *testing.T, slot primitives.Slot, parentRoot [32]byte, blockHash [32]byte) (*ethpb.BeaconStateGloas, *ethpb.SignedBeaconBlockGloas) {
|
||||
t.Helper()
|
||||
builderPendingPayments := make([]*ethpb.BuilderPendingPayment, 64)
|
||||
for i := range builderPendingPayments {
|
||||
builderPendingPayments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{FeeRecipient: make([]byte, 20)},
|
||||
}
|
||||
}
|
||||
base := ðpb.BeaconStateGloas{
|
||||
Slot: slot,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
BlockRoots: make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot),
|
||||
StateRoots: make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot),
|
||||
Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector),
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
LatestBlockHeader: ðpb.BeaconBlockHeader{
|
||||
ParentRoot: parentRoot[:],
|
||||
StateRoot: make([]byte, 32),
|
||||
BodyRoot: make([]byte, 32),
|
||||
},
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: [][]byte{make([]byte, 48)},
|
||||
},
|
||||
Builders: make([]*ethpb.Builder, 0),
|
||||
BuilderPendingPayments: builderPendingPayments,
|
||||
ExecutionPayloadAvailability: make([]byte, 1024),
|
||||
LatestBlockHash: make([]byte, 32),
|
||||
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
ProposerLookahead: make([]uint64, 64),
|
||||
}
|
||||
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
return base, blk
|
||||
}
|
||||
|
||||
func testSignedEnvelope(t *testing.T, blockRoot [32]byte, slot primitives.Slot, blockHash []byte) *ethpb.SignedExecutionPayloadEnvelope {
|
||||
t.Helper()
|
||||
return ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: ðpb.ExecutionPayloadEnvelope{
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, 32),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: blockHash,
|
||||
Transactions: [][]byte{},
|
||||
Withdrawals: []*enginev1.Withdrawal{},
|
||||
},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
BuilderIndex: 0,
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: slot,
|
||||
StateRoot: make([]byte, 32),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
|
||||
func setupGloasService(t *testing.T, engineClient *mockExecution.EngineClient) (*Service, *testServiceRequirements) {
|
||||
t.Helper()
|
||||
return minimalTestService(t,
|
||||
WithPayloadIDCache(cache.NewPayloadIDCache()),
|
||||
WithExecutionEngineCaller(engineClient),
|
||||
)
|
||||
}
|
||||
|
||||
func insertGloasBlock(t *testing.T, s *Service, base *ethpb.BeaconStateGloas, blk *ethpb.SignedBeaconBlockGloas, blockRoot [32]byte) {
|
||||
t.Helper()
|
||||
ctx := t.Context()
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlockWithRoot(signed, blockRoot)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.cfg.BeaconDB.SaveBlock(ctx, signed))
|
||||
require.NoError(t, s.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: blockRoot[:], Slot: blk.Block.Slot}))
|
||||
require.NoError(t, s.cfg.StateGen.SaveState(ctx, blockRoot, st))
|
||||
require.NoError(t, s.InsertNode(ctx, st, roblock))
|
||||
}
|
||||
|
||||
func TestGetPayloadEnvelopePrestate_UnknownRoot(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
unknownRoot := bytesutil.ToBytes32([]byte("unknown"))
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: unknownRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{},
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
_, err = s.getPayloadEnvelopePrestate(ctx, envelope)
|
||||
require.ErrorContains(t, "not found in forkchoice", err)
|
||||
}
|
||||
|
||||
func TestGetPayloadEnvelopePrestate_OK(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, blk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, blk, blockRoot)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{},
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
st, err := s.getPayloadEnvelopePrestate(ctx, envelope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Slot(1), st.Slot())
|
||||
}
|
||||
|
||||
func TestNotifyNewEnvelope_Valid(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, parentRoot, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:]},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
isValid, err := s.notifyNewEnvelope(ctx, st, envelope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, isValid)
|
||||
}
|
||||
|
||||
func TestNotifyNewEnvelope_Syncing(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{
|
||||
ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus,
|
||||
})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, parentRoot, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:]},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
isValid, err := s.notifyNewEnvelope(ctx, st, envelope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, isValid)
|
||||
}
|
||||
|
||||
func TestNotifyNewEnvelope_Invalid(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{
|
||||
ErrNewPayload: execution.ErrInvalidPayloadStatus,
|
||||
})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, parentRoot, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:]},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.notifyNewEnvelope(ctx, st, envelope)
|
||||
require.Equal(t, true, IsInvalidBlock(err))
|
||||
}
|
||||
|
||||
func TestNotifyForkchoiceUpdateGloas_Valid(t *testing.T) {
|
||||
pid := &enginev1.PayloadIDBytes{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{PayloadIDBytes: pid})
|
||||
ctx := t.Context()
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
attr := payloadattribute.EmptyWithVersion(version.Gloas)
|
||||
|
||||
retPid, err := s.notifyForkchoiceUpdateGloas(ctx, blockHash, attr)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, pid, retPid)
|
||||
}
|
||||
|
||||
func TestNotifyForkchoiceUpdateGloas_Syncing(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{
|
||||
ErrForkchoiceUpdated: execution.ErrAcceptedSyncingPayloadStatus,
|
||||
})
|
||||
ctx := t.Context()
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
_, err := s.notifyForkchoiceUpdateGloas(ctx, blockHash, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestNotifyForkchoiceUpdateGloas_Invalid(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{
|
||||
ErrForkchoiceUpdated: execution.ErrInvalidPayloadStatus,
|
||||
})
|
||||
ctx := t.Context()
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
_, err := s.notifyForkchoiceUpdateGloas(ctx, blockHash, nil)
|
||||
require.Equal(t, true, IsInvalidBlock(err))
|
||||
}
|
||||
|
||||
func TestNotifyForkchoiceUpdateGloas_NilAttributes(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
_, err := s.notifyForkchoiceUpdateGloas(ctx, blockHash, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSavePostPayload(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
protoEnv := testSignedEnvelope(t, blockRoot, 1, blockHash[:])
|
||||
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(protoEnv)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, s.savePostPayload(ctx, signed, st))
|
||||
|
||||
// Verify the envelope was saved in the DB.
|
||||
require.Equal(t, true, s.cfg.BeaconDB.HasExecutionPayloadEnvelope(ctx, blockRoot))
|
||||
}
|
||||
|
||||
func TestValidateExecutionOnEnvelope_Valid(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, parentRoot, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:], ParentHash: make([]byte, 32)},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
isValid, err := s.validateExecutionOnEnvelope(ctx, st, envelope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, isValid)
|
||||
}
|
||||
|
||||
func TestPostPayloadHeadUpdate_NotHead(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
root := bytesutil.ToBytes32([]byte("root1"))
|
||||
headRoot := bytesutil.ToBytes32([]byte("different"))
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: root[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:]},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, s.postPayloadHeadUpdate(ctx, envelope, st, root, headRoot[:]))
|
||||
}
|
||||
|
||||
func TestGetLookupParentRoot_PreGloas(t *testing.T) {
|
||||
service, _ := minimalTestService(t)
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
blk := util.HydrateSignedBeaconBlockDeneb(ðpb.SignedBeaconBlockDeneb{
|
||||
Block: ðpb.BeaconBlockDeneb{
|
||||
ParentRoot: parentRoot[:],
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := service.getLookupParentRoot(roblock)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, parentRoot, got)
|
||||
}
|
||||
|
||||
func TestGetLookupParentRoot_GloasBuildsOnEmpty(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, req := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
parentBlockHash := [32]byte{20}
|
||||
parentNodeBlockHash := [32]byte{99} // Different from parentBlockHash => builds on empty
|
||||
|
||||
// Insert a Gloas node for the parent so BlockHash works.
|
||||
st, parentROBlock, err := prepareGloasForkchoiceState(ctx, 1, parentRoot, params.BeaconConfig().ZeroHash, parentNodeBlockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
|
||||
|
||||
blockHash := [32]byte{10}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := service.getLookupParentRoot(roblock)
|
||||
require.NoError(t, err)
|
||||
// parentBlockHash != parentNodeBlockHash, so it builds on empty => returns parentRoot
|
||||
require.Equal(t, parentRoot, got)
|
||||
}
|
||||
|
||||
func TestGetLookupParentRoot_GloasBuildsOnFull(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, req := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
parentNodeBlockHash := [32]byte{10}
|
||||
|
||||
// Insert a Gloas node for the parent so BlockHash works.
|
||||
st, parentROBlock, err := prepareGloasForkchoiceState(ctx, 1, parentRoot, params.BeaconConfig().ZeroHash, parentNodeBlockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
|
||||
|
||||
// Set parentBlockHash in the bid to match the parent's blockHash.
|
||||
blockHash := [32]byte{20}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentNodeBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := service.getLookupParentRoot(roblock)
|
||||
require.NoError(t, err)
|
||||
// parentBlockHash == parentNodeBlockHash, so it builds on full => returns parentBlockHash
|
||||
require.Equal(t, parentNodeBlockHash, got)
|
||||
}
|
||||
|
||||
func TestGetLookupParentRoot_GloasParentPreForkEpoch(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 2
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, req := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
parentNodeBlockHash := [32]byte{10}
|
||||
parentSlot, err := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
|
||||
require.NoError(t, err)
|
||||
parentSlot = parentSlot - 1
|
||||
|
||||
st, parentROBlock, err := prepareGloasForkchoiceState(
|
||||
ctx,
|
||||
parentSlot,
|
||||
parentRoot,
|
||||
params.BeaconConfig().ZeroHash,
|
||||
parentNodeBlockHash,
|
||||
params.BeaconConfig().ZeroHash,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
|
||||
|
||||
blockHash := [32]byte{20}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentNodeBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: parentSlot + 1,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := service.getLookupParentRoot(roblock)
|
||||
require.NoError(t, err)
|
||||
// Parent slot is pre-fork, so always return parentRoot.
|
||||
require.Equal(t, parentRoot, got)
|
||||
}
|
||||
|
||||
func TestLateBlockTasks_GloasFCU(t *testing.T) {
|
||||
logHook := logTest.NewGlobal()
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
PrepareAllPayloads: true,
|
||||
})
|
||||
defer resetCfg()
|
||||
|
||||
pid := &enginev1.PayloadIDBytes{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
service, tr := setupGloasService(t, &mockExecution.EngineClient{PayloadIDBytes: pid})
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
base.LatestBlockHash = blockHash[:]
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
headRoot := bytesutil.ToBytes32([]byte("headroot"))
|
||||
service.head = &head{
|
||||
root: headRoot,
|
||||
state: st,
|
||||
slot: 1,
|
||||
}
|
||||
|
||||
// Set genesis time so CurrentSlot > HeadSlot, triggering late block logic.
|
||||
service.SetGenesisTime(time.Now().Add(-2 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second))
|
||||
service.SetForkChoiceGenesisTime(service.genesisTime)
|
||||
|
||||
service.lateBlockTasks(tr.ctx)
|
||||
require.LogsDoNotContain(t, logHook, "could not perform late block tasks")
|
||||
}
|
||||
@@ -170,7 +170,7 @@ func (s *Service) saveHead(ctx context.Context, newHeadRoot [32]byte, headBlock
|
||||
// Forward an event capturing a new chain head over a common event feed
|
||||
// done in a goroutine to avoid blocking the critical runtime main routine.
|
||||
go func() {
|
||||
if err := s.notifyNewHeadEvent(ctx, newHeadSlot, headState, newStateRoot[:], newHeadRoot[:]); err != nil {
|
||||
if err := s.notifyNewHeadEvent(ctx, newHeadSlot, newStateRoot[:], newHeadRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not notify event feed of new chain head")
|
||||
}
|
||||
}()
|
||||
@@ -322,7 +322,6 @@ func (s *Service) hasHeadState() bool {
|
||||
func (s *Service) notifyNewHeadEvent(
|
||||
ctx context.Context,
|
||||
newHeadSlot primitives.Slot,
|
||||
newHeadState state.BeaconState,
|
||||
newHeadStateRoot,
|
||||
newHeadRoot []byte,
|
||||
) error {
|
||||
|
||||
@@ -152,7 +152,6 @@ func TestSaveHead_Different_Reorg(t *testing.T) {
|
||||
|
||||
func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
t.Run("genesis_state_root", func(t *testing.T) {
|
||||
bState, _ := util.DeterministicGenesisState(t, 10)
|
||||
srv := testServiceWithDB(t)
|
||||
srv.SetGenesisTime(time.Now())
|
||||
notifier := srv.cfg.StateNotifier.(*mock.MockStateNotifier)
|
||||
@@ -165,7 +164,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
st, blk, err = prepareForkchoiceState(t.Context(), 1, newHeadRoot, [32]byte{}, [32]byte{}, ðpb.Checkpoint{}, ðpb.Checkpoint{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), 1, bState, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), 1, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
|
||||
@@ -202,7 +201,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
st, blk, err = prepareForkchoiceState(t.Context(), 0, newHeadRoot, [32]byte{}, [32]byte{}, ðpb.Checkpoint{}, ðpb.Checkpoint{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
err = srv.notifyNewHeadEvent(t.Context(), epoch2Start, bState, newHeadStateRoot[:], newHeadRoot[:])
|
||||
err = srv.notifyNewHeadEvent(t.Context(), epoch2Start, newHeadStateRoot[:], newHeadRoot[:])
|
||||
require.NoError(t, err)
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
@@ -220,7 +219,6 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
require.DeepSSZEqual(t, wanted, eventHead)
|
||||
})
|
||||
t.Run("epoch transition", func(t *testing.T) {
|
||||
bState, _ := util.DeterministicGenesisState(t, 10)
|
||||
srv := testServiceWithDB(t)
|
||||
srv.SetGenesisTime(time.Now())
|
||||
notifier := srv.cfg.StateNotifier.(*mock.MockStateNotifier)
|
||||
@@ -234,7 +232,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
newHeadSlot := params.BeaconConfig().SlotsPerEpoch
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), newHeadSlot, bState, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), newHeadSlot, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
consensus_types "github.com/OffchainLabs/prysm/v7/consensus-types"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/io/logs"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
prysmTime "github.com/OffchainLabs/prysm/v7/time"
|
||||
@@ -40,7 +40,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
}
|
||||
log = log.WithField("syncBitsCount", agg.SyncCommitteeBits.Count())
|
||||
}
|
||||
if b.Version() >= version.Bellatrix {
|
||||
if b.Version() >= version.Bellatrix && b.Version() < version.Gloas {
|
||||
p, err := b.Body().Execution()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -56,7 +56,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
txsPerSlotCount.Set(float64(len(txs)))
|
||||
}
|
||||
}
|
||||
if b.Version() >= version.Deneb {
|
||||
if b.Version() >= version.Deneb && b.Version() < version.Gloas {
|
||||
kzgs, err := b.Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get blob KZG commitments")
|
||||
@@ -64,7 +64,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
log = log.WithField("kzgCommitmentCount", len(kzgs))
|
||||
}
|
||||
}
|
||||
if b.Version() >= version.Electra {
|
||||
if b.Version() >= version.Electra && b.Version() < version.Gloas {
|
||||
eReqs, err := b.Body().ExecutionRequests()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get execution requests")
|
||||
@@ -80,6 +80,20 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if b.Version() >= version.Gloas {
|
||||
signedBid, err := b.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get signed execution payload bid")
|
||||
} else {
|
||||
bid := signedBid.Message
|
||||
log = log.WithFields(logrus.Fields{
|
||||
"blobKzgCommitmentCount": len(bid.BlobKzgCommitments),
|
||||
"payloadHash": fmt.Sprintf("%#x", bytesutil.Trunc(bid.BlockHash)),
|
||||
"parentHash": fmt.Sprintf("%#x", bytesutil.Trunc(bid.ParentBlockHash)),
|
||||
"builderIndex": bid.BuilderIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
log.Info("Finished applying state transition")
|
||||
return nil
|
||||
}
|
||||
@@ -87,46 +101,62 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
func logBlockSyncStatus(block interfaces.ReadOnlyBeaconBlock, blockRoot [32]byte, justified, finalized *ethpb.Checkpoint, receivedTime time.Time, genesis time.Time, daWaitedTime time.Duration) error {
|
||||
startTime, err := slots.StartTime(genesis, block.Slot())
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to get slot start time")
|
||||
}
|
||||
level := log.Logger.GetLevel()
|
||||
if level >= logrus.DebugLevel {
|
||||
parentRoot := block.ParentRoot()
|
||||
lf := logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"slotInEpoch": block.Slot() % params.BeaconConfig().SlotsPerEpoch,
|
||||
"block": fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8]),
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
"justifiedEpoch": justified.Epoch,
|
||||
"justifiedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(justified.Root)[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8]),
|
||||
"parentRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(parentRoot[:])[:8]),
|
||||
"version": version.String(block.Version()),
|
||||
"sinceSlotStartTime": prysmTime.Now().Sub(startTime),
|
||||
"chainServiceProcessedTime": prysmTime.Now().Sub(receivedTime) - daWaitedTime,
|
||||
"dataAvailabilityWaitedTime": daWaitedTime,
|
||||
}
|
||||
log.WithFields(lf).Debug("Synced new block")
|
||||
parentRoot := block.ParentRoot()
|
||||
blkRoot := fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8])
|
||||
finalizedRoot := fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8])
|
||||
sinceSlotStartTime := prysmTime.Now().Sub(startTime)
|
||||
|
||||
lessFields := logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"block": blkRoot,
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": finalizedRoot,
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
"sinceSlotStartTime": sinceSlotStartTime,
|
||||
}
|
||||
moreFields := logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"slotInEpoch": block.Slot() % params.BeaconConfig().SlotsPerEpoch,
|
||||
"block": blkRoot,
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
"justifiedEpoch": justified.Epoch,
|
||||
"justifiedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(justified.Root)[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": finalizedRoot,
|
||||
"parentRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(parentRoot[:])[:8]),
|
||||
"version": version.String(block.Version()),
|
||||
"sinceSlotStartTime": sinceSlotStartTime,
|
||||
"chainServiceProcessedTime": prysmTime.Now().Sub(receivedTime) - daWaitedTime,
|
||||
}
|
||||
if block.Version() < version.Gloas {
|
||||
moreFields["dataAvailabilityWaitedTime"] = daWaitedTime
|
||||
} else {
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"block": fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8]),
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
}).Info("Synced new block")
|
||||
signedBid, err := block.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get signed execution payload bid for logging")
|
||||
} else {
|
||||
moreFields["blockHash"] = fmt.Sprintf("%#x", bytesutil.Trunc(signedBid.Message.BlockHash))
|
||||
moreFields["parentHash"] = fmt.Sprintf("%#x", bytesutil.Trunc(signedBid.Message.ParentBlockHash))
|
||||
moreFields["builderIndex"] = signedBid.Message.BuilderIndex
|
||||
}
|
||||
}
|
||||
|
||||
level := logs.PackageVerbosity("beacon-chain/blockchain")
|
||||
if level >= logrus.DebugLevel {
|
||||
log.WithFields(moreFields).Info("Synced new block")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.WithFields(lessFields).WithField(logs.LogTargetField, logs.LogTargetUser).Info("Synced new block")
|
||||
log.WithFields(moreFields).WithField(logs.LogTargetField, logs.LogTargetEphemeral).Info("Synced new block")
|
||||
return nil
|
||||
}
|
||||
|
||||
// logs payload related data every slot.
|
||||
func logPayload(block interfaces.ReadOnlyBeaconBlock) error {
|
||||
isExecutionBlk, err := blocks.IsExecutionBlock(block.Body())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not determine if block is execution block")
|
||||
}
|
||||
if !isExecutionBlk {
|
||||
if block.Version() < version.Bellatrix || block.Version() >= version.Gloas {
|
||||
return nil
|
||||
}
|
||||
payload, err := block.Body().Execution()
|
||||
|
||||
@@ -96,7 +96,11 @@ func (s *Service) OnAttestation(ctx context.Context, a ethpb.Att, disparity time
|
||||
// We assume trusted attestation in this function has verified signature.
|
||||
|
||||
// Update forkchoice store with the new attestation for updating weight.
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(a.GetData().BeaconBlockRoot), a.GetData().Target.Epoch)
|
||||
|
||||
attData := a.GetData()
|
||||
payloadStatus := true
|
||||
if attData.Target.Epoch >= params.BeaconConfig().GloasForkEpoch {
|
||||
payloadStatus = attData.CommitteeIndex == 1
|
||||
}
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(attData.BeaconBlockRoot), attData.Slot, payloadStatus)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/go-bitfield"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
|
||||
coreTime "github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
@@ -64,7 +66,6 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
return invalidBlock{error: err}
|
||||
}
|
||||
startTime := time.Now()
|
||||
fcuArgs := &fcuConfig{}
|
||||
|
||||
if features.Get().EnableLightClient && slots.ToEpoch(s.CurrentSlot()) >= params.BeaconConfig().AltairForkEpoch {
|
||||
defer s.processLightClientUpdates(cfg)
|
||||
@@ -83,6 +84,9 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
if err := s.handleBlockAttestations(ctx, cfg.roblock.Block(), cfg.postState); err != nil {
|
||||
return errors.Wrap(err, "could not handle block's attestations")
|
||||
}
|
||||
if err := s.handleBlockPayloadAttestations(ctx, cfg.roblock.Block(), cfg.postState); err != nil {
|
||||
return errors.Wrap(err, "could not handle block's payload attestations")
|
||||
}
|
||||
|
||||
s.InsertSlashingsToForkChoiceStore(ctx, cfg.roblock.Block().Body().AttesterSlashings())
|
||||
if cfg.isValidPayload {
|
||||
@@ -102,7 +106,14 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
s.logNonCanonicalBlockReceived(cfg.roblock.Root(), cfg.headRoot)
|
||||
return nil
|
||||
}
|
||||
s.sendFCU(cfg, fcuArgs)
|
||||
if cfg.roblock.Version() < version.Gloas {
|
||||
s.sendFCU(cfg)
|
||||
} else if s.isNewHead(cfg.headRoot) {
|
||||
if err := s.saveHead(ctx, cfg.headRoot, cfg.roblock, cfg.postState); err != nil {
|
||||
log.WithError(err).Error("Could not save head")
|
||||
}
|
||||
s.pruneAttsFromPool(ctx, cfg.postState, cfg.roblock)
|
||||
}
|
||||
|
||||
// Pre-Fulu the caches are updated when computing the payload attributes
|
||||
if cfg.postState.Version() >= version.Fulu {
|
||||
@@ -232,7 +243,8 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
postVersionAndHeaders[i].version,
|
||||
postVersionAndHeaders[i].header, b)
|
||||
if err != nil {
|
||||
return s.handleInvalidExecutionError(ctx, err, root, b.Block().ParentRoot())
|
||||
// this call does not have the root in forkchoice yet.
|
||||
return s.handleInvalidExecutionError(ctx, err, root, b.Block().ParentRoot(), [32]byte(postVersionAndHeaders[i].header.ParentHash()))
|
||||
}
|
||||
if isValidPayload {
|
||||
if err := s.validateMergeTransitionBlock(ctx, preVersionAndHeaders[i].version,
|
||||
@@ -304,7 +316,18 @@ func (s *Service) areSidecarsAvailable(ctx context.Context, avs das.Availability
|
||||
slot := block.Slot()
|
||||
|
||||
if blockVersion >= version.Fulu {
|
||||
if err := s.areDataColumnsAvailable(ctx, roBlock.Root(), block); err != nil {
|
||||
body := block.Body()
|
||||
if body == nil {
|
||||
return errors.New("invalid nil beacon block body")
|
||||
}
|
||||
kzgCommitments, err := body.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "blob KZG commitments")
|
||||
}
|
||||
if len(kzgCommitments) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := s.areDataColumnsAvailable(ctx, roBlock.Root(), slot); err != nil {
|
||||
return errors.Wrapf(err, "are data columns available for block %#x with slot %d", roBlock.Root(), slot)
|
||||
}
|
||||
|
||||
@@ -366,6 +389,46 @@ func (s *Service) updateEpochBoundaryCaches(ctx context.Context, st state.Beacon
|
||||
return nil
|
||||
}
|
||||
|
||||
// refreshCaches updates the next slot state cache and epoch boundary caches.
|
||||
// Before Fulu this is done synchronously, after Fulu it is deferred to a goroutine.
|
||||
func (s *Service) refreshCaches(ctx context.Context, currentSlot primitives.Slot, headRoot [32]byte, headState state.BeaconState, accessRoot [32]byte) {
|
||||
lastRoot, lastState := transition.LastCachedState()
|
||||
if lastState == nil {
|
||||
lastRoot, lastState = headRoot[:], headState
|
||||
}
|
||||
if lastState.Version() < version.Fulu {
|
||||
s.updateCachesAndEpochBoundary(ctx, currentSlot, headState, accessRoot, lastRoot, lastState)
|
||||
} else {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(s.ctx, slotDeadline)
|
||||
defer cancel()
|
||||
s.updateCachesAndEpochBoundary(ctx, currentSlot, headState, accessRoot, lastRoot, lastState)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// updateCachesAndEpochBoundary updates the next slot state cache and handles
|
||||
// epoch boundary processing. If the lastRoot matches accessRoot, the cached
|
||||
// last state is reused; otherwise, the head state is advanced instead.
|
||||
func (s *Service) updateCachesAndEpochBoundary(ctx context.Context, currentSlot primitives.Slot, headState state.BeaconState, accessRoot [32]byte, lastRoot []byte, lastState state.BeaconState) {
|
||||
if bytes.Equal(lastRoot, accessRoot[:]) {
|
||||
// Happy case, the last advanced state is head, we thus keep it
|
||||
lastState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
|
||||
log.WithError(err).Debug("Could not update next slot state cache")
|
||||
}
|
||||
} else {
|
||||
// Last advanced state was not head, we do not advance this but rather use headstate
|
||||
headState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, accessRoot[:], headState); err != nil {
|
||||
log.WithError(err).Debug("Could not update next slot state cache")
|
||||
}
|
||||
}
|
||||
if err := s.handleEpochBoundary(ctx, currentSlot, headState, accessRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not update epoch boundary caches")
|
||||
}
|
||||
}
|
||||
|
||||
// Epoch boundary tasks: it copies the headState and updates the epoch boundary
|
||||
// caches. The caller of this function must not hold a lock in forkchoice store.
|
||||
func (s *Service) handleEpochBoundary(ctx context.Context, slot primitives.Slot, headState state.BeaconState, blockRoot []byte) error {
|
||||
@@ -401,7 +464,11 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Re
|
||||
}
|
||||
r := bytesutil.ToBytes32(a.GetData().BeaconBlockRoot)
|
||||
if s.cfg.ForkChoiceStore.HasNode(r) {
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Target.Epoch)
|
||||
payloadStatus := true
|
||||
if a.GetData().Target.Epoch >= params.BeaconConfig().GloasForkEpoch {
|
||||
payloadStatus = a.GetData().CommitteeIndex == 1
|
||||
}
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Slot, payloadStatus)
|
||||
} else if features.Get().EnableExperimentalAttestationPool {
|
||||
if err = s.cfg.AttestationCache.Add(a); err != nil {
|
||||
return err
|
||||
@@ -413,6 +480,36 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Re
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleBlockPayloadAttestations feeds payload attestations included in a Gloas block into forkchoice.
|
||||
func (s *Service) handleBlockPayloadAttestations(ctx context.Context, blk interfaces.ReadOnlyBeaconBlock, st state.BeaconState) error {
|
||||
if blk.Version() < version.Gloas {
|
||||
return nil
|
||||
}
|
||||
atts, err := blk.Body().PayloadAttestations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(atts) == 0 {
|
||||
return nil
|
||||
}
|
||||
committee, err := gloas.PayloadCommittee(ctx, st, blk.Slot()-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, att := range atts {
|
||||
root := bytesutil.ToBytes32(att.Data.BeaconBlockRoot)
|
||||
if !s.cfg.ForkChoiceStore.HasNode(root) {
|
||||
continue
|
||||
}
|
||||
for i := range committee {
|
||||
if att.AggregationBits.BitAt(uint64(i)) {
|
||||
s.cfg.ForkChoiceStore.SetPTCVote(root, uint64(i), att.Data.PayloadPresent, att.Data.BlobDataAvailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertSlashingsToForkChoiceStore inserts attester slashing indices to fork choice store.
|
||||
// To call this function, it's caller's responsibility to ensure the slashing object is valid.
|
||||
// This function requires a write lock on forkchoice.
|
||||
@@ -543,6 +640,9 @@ func (s *Service) validateMergeTransitionBlock(ctx context.Context, stateVersion
|
||||
if blocks.IsPreBellatrixVersion(blk.Block().Version()) {
|
||||
return nil
|
||||
}
|
||||
if blk.Block().Version() >= version.Gloas {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip validation if block has an empty payload.
|
||||
payload, err := blk.Block().Body().Execution()
|
||||
@@ -583,11 +683,22 @@ func (s *Service) runLateBlockTasks() {
|
||||
return
|
||||
}
|
||||
|
||||
attThreshold := params.BeaconConfig().SecondsPerSlot / 3
|
||||
ticker := slots.NewSlotTickerWithOffset(s.genesisTime, time.Duration(attThreshold)*time.Second, params.BeaconConfig().SecondsPerSlot)
|
||||
cfg := params.BeaconConfig()
|
||||
attDueBPS := cfg.AttestationDueBPS
|
||||
if slots.ToEpoch(s.CurrentSlot()) >= cfg.GloasForkEpoch {
|
||||
attDueBPS = cfg.AttestationDueBPSGloas
|
||||
}
|
||||
attThreshold := cfg.SlotComponentDuration(attDueBPS)
|
||||
ticker := slots.NewSlotTickerWithOffset(s.genesisTime, attThreshold, cfg.SecondsPerSlot)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C():
|
||||
case slot := <-ticker.C():
|
||||
if attDueBPS != cfg.AttestationDueBPSGloas && slots.ToEpoch(slot) >= cfg.GloasForkEpoch {
|
||||
ticker.Done()
|
||||
attDueBPS = cfg.AttestationDueBPSGloas
|
||||
attThreshold = cfg.SlotComponentDuration(attDueBPS)
|
||||
ticker = slots.NewSlotTickerWithOffset(s.genesisTime, attThreshold, cfg.SecondsPerSlot)
|
||||
}
|
||||
s.lateBlockTasks(s.ctx)
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting routine")
|
||||
@@ -664,7 +775,18 @@ func (s *Service) isDataAvailable(
|
||||
root := roBlock.Root()
|
||||
blockVersion := block.Version()
|
||||
if blockVersion >= version.Fulu {
|
||||
return s.areDataColumnsAvailable(ctx, root, block)
|
||||
body := block.Body()
|
||||
if body == nil {
|
||||
return errors.New("invalid nil beacon block body")
|
||||
}
|
||||
kzgCommitments, err := body.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "blob KZG commitments")
|
||||
}
|
||||
if len(kzgCommitments) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.areDataColumnsAvailable(ctx, root, block.Slot())
|
||||
}
|
||||
|
||||
if blockVersion >= version.Deneb {
|
||||
@@ -679,30 +801,15 @@ func (s *Service) isDataAvailable(
|
||||
func (s *Service) areDataColumnsAvailable(
|
||||
ctx context.Context,
|
||||
root [fieldparams.RootLength]byte,
|
||||
block interfaces.ReadOnlyBeaconBlock,
|
||||
slot primitives.Slot,
|
||||
) error {
|
||||
// We are only required to check within MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS
|
||||
blockSlot, currentSlot := block.Slot(), s.CurrentSlot()
|
||||
blockEpoch, currentEpoch := slots.ToEpoch(blockSlot), slots.ToEpoch(currentSlot)
|
||||
currentSlot := s.CurrentSlot()
|
||||
blockEpoch, currentEpoch := slots.ToEpoch(slot), slots.ToEpoch(currentSlot)
|
||||
if !params.WithinDAPeriod(blockEpoch, currentEpoch) {
|
||||
return nil
|
||||
}
|
||||
|
||||
body := block.Body()
|
||||
if body == nil {
|
||||
return errors.New("invalid nil beacon block body")
|
||||
}
|
||||
|
||||
kzgCommitments, err := body.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "blob KZG commitments")
|
||||
}
|
||||
|
||||
// If block has not commitments there is nothing to wait for.
|
||||
if len(kzgCommitments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// All columns to sample need to be available for the block to be considered available.
|
||||
nodeID := s.cfg.P2P.NodeID()
|
||||
|
||||
@@ -756,7 +863,7 @@ func (s *Service) areDataColumnsAvailable(
|
||||
}
|
||||
|
||||
// Log for DA checks that cross over into the next slot; helpful for debugging.
|
||||
nextSlot, err := slots.StartTime(s.genesisTime, block.Slot()+1)
|
||||
nextSlot, err := slots.StartTime(s.genesisTime, slot+1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to determine slot start time: %w", err)
|
||||
}
|
||||
@@ -771,7 +878,7 @@ func (s *Service) areDataColumnsAvailable(
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"slot": slot,
|
||||
"root": fmt.Sprintf("%#x", root),
|
||||
"columnsExpected": helpers.SortedPrettySliceFromMap(peerInfo.CustodyColumns),
|
||||
"columnsWaiting": helpers.SortedPrettySliceFromMap(missing),
|
||||
@@ -817,7 +924,7 @@ func (s *Service) areDataColumnsAvailable(
|
||||
missingIndices = helpers.SortedPrettySliceFromMap(missing)
|
||||
}
|
||||
|
||||
return errors.Wrapf(ctx.Err(), "data column sidecars slot: %d, BlockRoot: %#x, missing: %v", block.Slot(), root, missingIndices)
|
||||
return errors.Wrapf(ctx.Err(), "data column sidecars slot: %d, BlockRoot: %#x, missing: %v", slot, root, missingIndices)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -915,37 +1022,18 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
headRoot := s.headRoot()
|
||||
headState := s.headState(ctx)
|
||||
s.headLock.RUnlock()
|
||||
lastRoot, lastState := transition.LastCachedState()
|
||||
if lastState == nil {
|
||||
lastRoot, lastState = headRoot[:], headState
|
||||
}
|
||||
// Before Fulu we need to process the next slot to find out if we are proposing.
|
||||
if lastState.Version() < version.Fulu {
|
||||
// Copy all the field tries in our cached state in the event of late
|
||||
// blocks.
|
||||
lastState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
|
||||
log.WithError(err).Debug("Could not update next slot state cache")
|
||||
}
|
||||
if err := s.handleEpochBoundary(ctx, currentSlot, headState, headRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not update epoch boundary caches")
|
||||
}
|
||||
var accessRoot [32]byte
|
||||
isFull, err := headState.IsParentBlockFull()
|
||||
if err != nil || !isFull {
|
||||
accessRoot = headRoot
|
||||
} else {
|
||||
// After Fulu, we can update the caches asynchronously after sending FCU to the engine
|
||||
defer func() {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(s.ctx, slotDeadline)
|
||||
defer cancel()
|
||||
lastState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
|
||||
log.WithError(err).Debug("Could not update next slot state cache")
|
||||
}
|
||||
if err := s.handleEpochBoundary(ctx, currentSlot, headState, headRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not update epoch boundary caches")
|
||||
}
|
||||
}()
|
||||
}()
|
||||
accessRoot, err = headState.LatestBlockHash()
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve latest block hash, using head root as access root")
|
||||
accessRoot = headRoot
|
||||
}
|
||||
}
|
||||
s.refreshCaches(ctx, currentSlot, headRoot, headState, accessRoot)
|
||||
// return early if we already started building a block for the current
|
||||
// head root
|
||||
_, has := s.cfg.PayloadIDCache.PayloadID(s.CurrentSlot()+1, headRoot)
|
||||
@@ -959,26 +1047,38 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
s.headLock.RLock()
|
||||
headBlock, err := s.headBlock()
|
||||
if err != nil {
|
||||
if headState.Version() >= version.Gloas {
|
||||
bh, err := headState.LatestBlockHash()
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve latest block hash")
|
||||
return
|
||||
}
|
||||
_, err = s.notifyForkchoiceUpdateGloas(ctx, bh, attribute)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")
|
||||
}
|
||||
} else {
|
||||
s.headLock.RLock()
|
||||
headBlock, err := s.headBlock()
|
||||
if err != nil {
|
||||
s.headLock.RUnlock()
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve head block")
|
||||
return
|
||||
}
|
||||
s.headLock.RUnlock()
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve head block")
|
||||
return
|
||||
}
|
||||
s.headLock.RUnlock()
|
||||
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: headRoot,
|
||||
headBlock: headBlock,
|
||||
attributes: attribute,
|
||||
}
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
_, err = s.notifyForkchoiceUpdate(ctx, fcuArgs)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: headRoot,
|
||||
headBlock: headBlock,
|
||||
attributes: attribute,
|
||||
}
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
_, err = s.notifyForkchoiceUpdate(ctx, fcuArgs)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,9 +1092,10 @@ func (s *Service) waitForSync() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) handleInvalidExecutionError(ctx context.Context, err error, blockRoot, parentRoot [fieldparams.RootLength]byte) error {
|
||||
// the caller of this function must hold a write lock in forkchoice store.
|
||||
func (s *Service) handleInvalidExecutionError(ctx context.Context, err error, blockRoot, parentRoot [32]byte, parentHash [32]byte) error {
|
||||
if IsInvalidBlock(err) && InvalidBlockLVH(err) != [32]byte{} {
|
||||
return s.pruneInvalidBlock(ctx, blockRoot, parentRoot, InvalidBlockLVH(err))
|
||||
return s.pruneInvalidBlock(ctx, blockRoot, parentRoot, parentHash, InvalidBlockLVH(err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -38,57 +38,67 @@ func (s *Service) CurrentSlot() primitives.Slot {
|
||||
}
|
||||
|
||||
// getFCUArgs returns the arguments to call forkchoice update
|
||||
func (s *Service) getFCUArgs(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
if err := s.getFCUArgsEarlyBlock(cfg, fcuArgs); err != nil {
|
||||
return err
|
||||
func (s *Service) getFCUArgs(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
|
||||
fcuArgs, err := s.getFCUArgsEarlyBlock(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fcuArgs.attributes = s.getPayloadAttribute(cfg.ctx, fcuArgs.headState, fcuArgs.proposingSlot, cfg.headRoot[:])
|
||||
return nil
|
||||
return fcuArgs, nil
|
||||
}
|
||||
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
if cfg.roblock.Root() == cfg.headRoot {
|
||||
fcuArgs.headState = cfg.postState
|
||||
fcuArgs.headBlock = cfg.roblock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return nil
|
||||
return &fcuConfig{
|
||||
headState: cfg.postState,
|
||||
headBlock: cfg.roblock,
|
||||
headRoot: cfg.headRoot,
|
||||
proposingSlot: s.CurrentSlot() + 1,
|
||||
}, nil
|
||||
}
|
||||
return s.fcuArgsNonCanonicalBlock(cfg, fcuArgs)
|
||||
return s.fcuArgsNonCanonicalBlock(cfg)
|
||||
}
|
||||
|
||||
// logNonCanonicalBlockReceived prints a message informing that the received
|
||||
// block is not the head of the chain. It requires the caller holds a lock on
|
||||
// Forkchoice.
|
||||
func (s *Service) logNonCanonicalBlockReceived(blockRoot [32]byte, headRoot [32]byte) {
|
||||
receivedWeight, err := s.cfg.ForkChoiceStore.Weight(blockRoot)
|
||||
receivedWeight, err := s.cfg.ForkChoiceStore.ConsensusNodeWeight(blockRoot)
|
||||
if err != nil {
|
||||
log.WithField("root", fmt.Sprintf("%#x", blockRoot)).Warn("Could not determine node weight")
|
||||
}
|
||||
headWeight, err := s.cfg.ForkChoiceStore.Weight(headRoot)
|
||||
headWeight, err := s.cfg.ForkChoiceStore.ConsensusNodeWeight(headRoot)
|
||||
if err != nil {
|
||||
log.WithField("root", fmt.Sprintf("%#x", headRoot)).Warn("Could not determine node weight")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
fields := logrus.Fields{
|
||||
"receivedRoot": fmt.Sprintf("%#x", blockRoot),
|
||||
"receivedWeight": receivedWeight,
|
||||
"headRoot": fmt.Sprintf("%#x", headRoot),
|
||||
"headWeight": headWeight,
|
||||
}).Debug("Head block is not the received block")
|
||||
}
|
||||
headEmpty, headFull, err := s.cfg.ForkChoiceStore.PayloadWeights(headRoot)
|
||||
if err == nil {
|
||||
fields["headEmptyWeight"] = headEmpty
|
||||
fields["headFullWeight"] = headFull
|
||||
}
|
||||
log.WithFields(fields).Debug("Head block is not the received block")
|
||||
}
|
||||
|
||||
// fcuArgsNonCanonicalBlock returns the arguments to the FCU call when the
|
||||
// incoming block is non-canonical, that is, based on the head root.
|
||||
func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
headState, headBlock, err := s.getStateAndBlock(cfg.ctx, cfg.headRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
fcuArgs.headState = headState
|
||||
fcuArgs.headBlock = headBlock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return nil
|
||||
return &fcuConfig{
|
||||
headState: headState,
|
||||
headBlock: headBlock,
|
||||
headRoot: cfg.headRoot,
|
||||
proposingSlot: s.CurrentSlot() + 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sendStateFeedOnBlock sends an event that a new block has been synced
|
||||
@@ -189,36 +199,62 @@ func reportProcessingTime(startTime time.Time) {
|
||||
onBlockProcessingTime.Observe(float64(time.Since(startTime).Milliseconds()))
|
||||
}
|
||||
|
||||
// getBlockPreState returns the pre state of an incoming block. It uses the parent root of the block
|
||||
// GetPrestateToPropose returns the pre-state for a proposer to base its block on.
|
||||
// It is similar to GetBlockPreState but it lacks unnecessary verifications.
|
||||
func (s *Service) GetPrestateToPropose(ctx context.Context, b consensus_blocks.ROBlock) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.GetPreStateToPropose")
|
||||
defer span.End()
|
||||
|
||||
accessRoot, err := s.getLookupParentRoot(b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get lookup parent root")
|
||||
}
|
||||
|
||||
bl := b.Block()
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, accessRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get pre state for slot %d", bl.Slot())
|
||||
}
|
||||
if preState == nil || preState.IsNil() {
|
||||
return nil, errors.Wrapf(err, "nil pre state for slot %d", bl.Slot())
|
||||
}
|
||||
return preState, nil
|
||||
}
|
||||
|
||||
// GetBlockPreState returns the pre state of an incoming block. It uses the parent root of the block
|
||||
// to retrieve the state in DB. It verifies the pre state's validity and the incoming block
|
||||
// is in the correct time window.
|
||||
func (s *Service) getBlockPreState(ctx context.Context, b interfaces.ReadOnlyBeaconBlock) (state.BeaconState, error) {
|
||||
func (s *Service) GetBlockPreState(ctx context.Context, b consensus_blocks.ROBlock) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.getBlockPreState")
|
||||
defer span.End()
|
||||
|
||||
accessRoot, err := s.getLookupParentRoot(b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get lookup parent root")
|
||||
}
|
||||
// Verify incoming block has a valid pre state.
|
||||
if err := s.verifyBlkPreState(ctx, b.ParentRoot()); err != nil {
|
||||
if err := s.verifyBlkPreState(ctx, accessRoot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, b.ParentRoot())
|
||||
bl := b.Block()
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, accessRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get pre state for slot %d", b.Slot())
|
||||
return nil, errors.Wrapf(err, "could not get pre state for slot %d", bl.Slot())
|
||||
}
|
||||
if preState == nil || preState.IsNil() {
|
||||
return nil, errors.Wrapf(err, "nil pre state for slot %d", b.Slot())
|
||||
return nil, errors.Wrapf(err, "nil pre state for slot %d", bl.Slot())
|
||||
}
|
||||
|
||||
// Verify block slot time is not from the future.
|
||||
if err := slots.VerifyTime(s.genesisTime, b.Slot(), params.BeaconConfig().MaximumGossipClockDisparityDuration()); err != nil {
|
||||
if err := slots.VerifyTime(s.genesisTime, bl.Slot(), params.BeaconConfig().MaximumGossipClockDisparityDuration()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify block is later than the finalized epoch slot.
|
||||
if err := s.verifyBlkFinalizedSlot(b); err != nil {
|
||||
if err := s.verifyBlkFinalizedSlot(bl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return preState, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
statefeed "github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed/state"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
@@ -731,13 +732,13 @@ func TestOnBlock_CanFinalize_WithOnTick(t *testing.T) {
|
||||
currStoreJustifiedEpoch := service.CurrentJustifiedCheckpt().Epoch
|
||||
currStoreFinalizedEpoch := service.FinalizedCheckpt().Epoch
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -783,13 +784,13 @@ func TestOnBlock_CanFinalize(t *testing.T) {
|
||||
currStoreJustifiedEpoch := service.CurrentJustifiedCheckpt().Epoch
|
||||
currStoreFinalizedEpoch := service.FinalizedCheckpt().Epoch
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -847,13 +848,13 @@ func TestOnBlock_CallNewPayloadAndForkchoiceUpdated(t *testing.T) {
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1322,13 +1323,13 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
wg.Add(4)
|
||||
var lock sync.Mutex
|
||||
go func() {
|
||||
preState, err := service.getBlockPreState(ctx, wsb1.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb1, r1)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb1)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb1, r1)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1336,13 +1337,13 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
preState, err := service.getBlockPreState(ctx, wsb2.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb2, r2)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb2)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb2, r2)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1350,13 +1351,13 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
preState, err := service.getBlockPreState(ctx, wsb3.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb3, r3)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb3)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb3, r3)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1364,13 +1365,13 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
preState, err := service.getBlockPreState(ctx, wsb4.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb4, r4)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb4)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb4, r4)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1442,13 +1443,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1464,13 +1465,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1487,13 +1488,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1512,13 +1513,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
firstInvalidRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, firstInvalidRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, firstInvalidRoot, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, firstInvalidRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1549,12 +1550,12 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, rowsb)
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
// Check that forkchoice's head and store's headroot are the previous head (since the invalid block did
|
||||
@@ -1578,13 +1579,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1647,13 +1648,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1670,13 +1671,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1692,13 +1693,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
lastValidRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, lastValidRoot, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1723,13 +1724,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
invalidRoots[i-13], err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, invalidRoots[i-13])
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, invalidRoots[i-13], wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, invalidRoots[i-13])
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1753,12 +1754,12 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, rowsb)
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
|
||||
@@ -1793,13 +1794,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1820,13 +1821,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1850,13 +1851,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
root, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1912,13 +1913,13 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1934,13 +1935,13 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1956,13 +1957,13 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
lastValidRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, lastValidRoot, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1989,13 +1990,13 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
// Save current justified and finalized epochs for future use.
|
||||
currStoreJustifiedEpoch := service.CurrentJustifiedCheckpt().Epoch
|
||||
currStoreFinalizedEpoch := service.FinalizedCheckpt().Epoch
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -2006,6 +2007,7 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
// Check that we have justified the second epoch
|
||||
jc := service.cfg.ForkChoiceStore.JustifiedCheckpoint()
|
||||
require.Equal(t, primitives.Epoch(2), jc.Epoch)
|
||||
time.Sleep(20 * time.Millisecond) // wait for async forkchoice update to be processed
|
||||
|
||||
// import block 19 to find out that the whole chain 13--18 was in fact
|
||||
// invalid
|
||||
@@ -2020,12 +2022,12 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, rowsb)
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
|
||||
@@ -2112,13 +2114,13 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -2180,13 +2182,13 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -2416,14 +2418,12 @@ func Test_getFCUArgs(t *testing.T) {
|
||||
isValidPayload: true,
|
||||
}
|
||||
// error branch
|
||||
fcuArgs := &fcuConfig{}
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
_, err = s.getFCUArgs(cfg)
|
||||
require.ErrorContains(t, "block does not exist", err)
|
||||
|
||||
// canonical branch
|
||||
cfg.headRoot = cfg.roblock.Root()
|
||||
fcuArgs = &fcuConfig{}
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
fcuArgs, err := s.getFCUArgs(cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cfg.roblock.Root(), fcuArgs.headRoot)
|
||||
}
|
||||
@@ -2455,7 +2455,9 @@ func TestRollbackBlock(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
@@ -2468,7 +2470,7 @@ func TestRollbackBlock(t *testing.T) {
|
||||
|
||||
// Set invalid parent root to trigger forkchoice error.
|
||||
wsb.SetParentRoot([]byte("bad"))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Rollback block insertion into db and caches.
|
||||
@@ -2513,7 +2515,9 @@ func TestRollbackBlock_SavePostStateInfo_ContextDeadline(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
@@ -2569,13 +2573,13 @@ func TestRollbackBlock_ContextDeadline(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -2586,7 +2590,9 @@ func TestRollbackBlock_ContextDeadline(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
@@ -2600,8 +2606,6 @@ func TestRollbackBlock_ContextDeadline(t *testing.T) {
|
||||
// Set deadlined context when processing the block
|
||||
cancCtx, canc := context.WithCancel(t.Context())
|
||||
canc()
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
|
||||
parentRoot = roblock.Block().ParentRoot()
|
||||
|
||||
@@ -3477,3 +3481,219 @@ func TestProcessLightClientFinalityUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBlockPayloadAttestations(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
t.Run("pre-Gloas block is no-op", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
blk := util.NewBeaconBlockElectra()
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
st, err := util.NewBeaconStateElectra()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(t.Context(), wsb.Block(), st))
|
||||
})
|
||||
|
||||
t.Run("empty payload attestations", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
blk := util.NewBeaconBlockGloas()
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
st, err := util.NewBeaconStateGloas()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(t.Context(), wsb.Block(), st))
|
||||
})
|
||||
|
||||
t.Run("unknown root is skipped", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
numVals := 2048
|
||||
headState := gloasStateWithValidators(t, 2, numVals)
|
||||
|
||||
unknownRoot := bytesutil.ToBytes32([]byte("unknown"))
|
||||
bits := bitfield.NewBitvector512()
|
||||
bits.SetBitAt(0, true)
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
PayloadAttestations: []*ethpb.PayloadAttestation{
|
||||
{
|
||||
AggregationBits: bits,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: unknownRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(ctx, wsb.Block(), headState))
|
||||
})
|
||||
|
||||
t.Run("known root sets PTC votes", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
numVals := 2048
|
||||
headState := gloasStateWithValidators(t, 2, numVals)
|
||||
|
||||
base, insertBlk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, insertBlk, blockRoot)
|
||||
|
||||
ptc, err := gloas.PayloadCommittee(ctx, headState, 1)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, len(ptc))
|
||||
|
||||
bits := bitfield.NewBitvector512()
|
||||
bits.SetBitAt(0, true)
|
||||
bits.SetBitAt(2, true)
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
PayloadAttestations: []*ethpb.PayloadAttestation{
|
||||
{
|
||||
AggregationBits: bits,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(ctx, wsb.Block(), headState))
|
||||
})
|
||||
|
||||
t.Run("multiple attestations", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
numVals := 2048
|
||||
headState := gloasStateWithValidators(t, 2, numVals)
|
||||
|
||||
base, insertBlk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, insertBlk, blockRoot)
|
||||
|
||||
bits1 := bitfield.NewBitvector512()
|
||||
bits1.SetBitAt(0, true)
|
||||
bits2 := bitfield.NewBitvector512()
|
||||
bits2.SetBitAt(1, true)
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
PayloadAttestations: []*ethpb.PayloadAttestation{
|
||||
{
|
||||
AggregationBits: bits1,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: false,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
{
|
||||
AggregationBits: bits2,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: false,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(ctx, wsb.Block(), headState))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateCachesAndEpochBoundary_MatchingRoots(t *testing.T) {
|
||||
service := testServiceNoDB(t)
|
||||
st, _ := util.DeterministicGenesisState(t, 1)
|
||||
accessRoot := [32]byte{'a'}
|
||||
|
||||
service.updateCachesAndEpochBoundary(t.Context(), 1, st, accessRoot, accessRoot[:], st)
|
||||
|
||||
cached := transition.NextSlotState(accessRoot[:], 1)
|
||||
require.NotNil(t, cached)
|
||||
require.Equal(t, primitives.Slot(1), cached.Slot())
|
||||
}
|
||||
|
||||
func TestUpdateCachesAndEpochBoundary_DifferentRoots(t *testing.T) {
|
||||
service := testServiceNoDB(t)
|
||||
headState, _ := util.DeterministicGenesisState(t, 1)
|
||||
lastState, _ := util.DeterministicGenesisState(t, 1)
|
||||
accessRoot := [32]byte{'a'}
|
||||
lastRoot := [32]byte{'b'}
|
||||
|
||||
service.updateCachesAndEpochBoundary(t.Context(), 1, headState, accessRoot, lastRoot[:], lastState)
|
||||
|
||||
// Cache should be keyed by accessRoot, not lastRoot.
|
||||
cached := transition.NextSlotState(accessRoot[:], 1)
|
||||
require.NotNil(t, cached)
|
||||
require.Equal(t, primitives.Slot(1), cached.Slot())
|
||||
|
||||
cached = transition.NextSlotState(lastRoot[:], 1)
|
||||
require.Equal(t, true, cached == nil)
|
||||
}
|
||||
|
||||
func TestRefreshCaches_NoCachedState(t *testing.T) {
|
||||
service := testServiceNoDB(t)
|
||||
st, _ := util.DeterministicGenesisState(t, 1)
|
||||
headRoot := [32]byte{'h'}
|
||||
|
||||
service.refreshCaches(t.Context(), 1, headRoot, st, headRoot)
|
||||
|
||||
cached := transition.NextSlotState(headRoot[:], 1)
|
||||
require.NotNil(t, cached)
|
||||
require.Equal(t, primitives.Slot(1), cached.Slot())
|
||||
}
|
||||
|
||||
func TestRefreshCaches_CachedStateMatchesAccessRoot(t *testing.T) {
|
||||
service := testServiceNoDB(t)
|
||||
st, _ := util.DeterministicGenesisState(t, 1)
|
||||
accessRoot := [32]byte{'a'}
|
||||
headRoot := [32]byte{'h'}
|
||||
|
||||
// Pre-populate the cache with accessRoot.
|
||||
require.NoError(t, transition.UpdateNextSlotCache(t.Context(), accessRoot[:], st))
|
||||
|
||||
service.refreshCaches(t.Context(), 1, headRoot, st, accessRoot)
|
||||
|
||||
cached := transition.NextSlotState(accessRoot[:], 1)
|
||||
require.NotNil(t, cached)
|
||||
require.Equal(t, primitives.Slot(1), cached.Slot())
|
||||
}
|
||||
|
||||
@@ -110,13 +110,13 @@ func TestService_ProcessAttestationsAndUpdateHead(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState))
|
||||
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -172,13 +172,13 @@ func TestService_UpdateHead_NoAtts(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState))
|
||||
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
|
||||
@@ -45,6 +45,8 @@ type BlockReceiver interface {
|
||||
HasBlock(ctx context.Context, root [32]byte) bool
|
||||
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
|
||||
BlockBeingSynced([32]byte) bool
|
||||
GetBlockPreState(ctx context.Context, b blocks.ROBlock) (state.BeaconState, error)
|
||||
GetPrestateToPropose(ctx context.Context, b blocks.ROBlock) (state.BeaconState, error)
|
||||
}
|
||||
|
||||
// BlobReceiver interface defines the methods of chain service for receiving new
|
||||
@@ -95,18 +97,17 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "block copy")
|
||||
}
|
||||
|
||||
preState, err := s.getBlockPreState(ctx, blockCopy.Block())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get block's prestate")
|
||||
}
|
||||
|
||||
currentCheckpoints := s.saveCurrentCheckpoints(preState)
|
||||
roblock, err := blocks.NewROBlockWithRoot(blockCopy, blockRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "new ro block with root")
|
||||
}
|
||||
|
||||
preState, err := s.GetBlockPreState(ctx, roblock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get block's prestate")
|
||||
}
|
||||
|
||||
currentCheckpoints := s.saveCurrentCheckpoints(preState)
|
||||
postState, isValidPayload, err := s.validateExecutionAndConsensus(ctx, preState, roblock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "validator execution and consensus")
|
||||
@@ -211,6 +212,16 @@ func (s *Service) validateExecutionAndConsensus(
|
||||
preState state.BeaconState,
|
||||
block blocks.ROBlock,
|
||||
) (state.BeaconState, bool, error) {
|
||||
if block.Version() >= version.Gloas {
|
||||
postState, err := s.validateStateTransition(ctx, preState, block)
|
||||
if errors.Is(err, ErrNotDescendantOfFinalized) {
|
||||
return nil, false, invalidBlock{error: err, root: block.Root()}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "failed to validate consensus state transition function")
|
||||
}
|
||||
return postState, false, nil
|
||||
}
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -244,6 +255,10 @@ func (s *Service) validateExecutionAndConsensus(
|
||||
}
|
||||
|
||||
func (s *Service) handleDA(ctx context.Context, avs das.AvailabilityChecker, block blocks.ROBlock) (time.Duration, error) {
|
||||
// Gloas DA is handled on the payload enevelope.
|
||||
if block.Version() >= version.Gloas {
|
||||
return 0, nil
|
||||
}
|
||||
var err error
|
||||
start := time.Now()
|
||||
if avs != nil {
|
||||
@@ -633,7 +648,7 @@ func (s *Service) validateExecutionOnBlock(ctx context.Context, ver int, header
|
||||
isValidPayload, err := s.notifyNewPayload(ctx, ver, header, block)
|
||||
if err != nil {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
err = s.handleInvalidExecutionError(ctx, err, block.Root(), block.Block().ParentRoot())
|
||||
err = s.handleInvalidExecutionError(ctx, err, block.Root(), block.Block().ParentRoot(), [32]byte(header.BlockHash()))
|
||||
s.cfg.ForkChoiceStore.Unlock()
|
||||
return false, err
|
||||
}
|
||||
|
||||
319
beacon-chain/blockchain/receive_execution_payload_envelope.go
Normal file
319
beacon-chain/blockchain/receive_execution_payload_envelope.go
Normal file
@@ -0,0 +1,319 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed"
|
||||
statefeed "github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed/state"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/execution"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
payloadattribute "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attribute"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ExecutionPayloadEnvelopeReceiver defines the methods for receiving execution payload envelopes.
|
||||
type ExecutionPayloadEnvelopeReceiver interface {
|
||||
ReceiveExecutionPayloadEnvelope(context.Context, interfaces.ROSignedExecutionPayloadEnvelope) error
|
||||
}
|
||||
|
||||
// ReceiveExecutionPayloadEnvelope processes a signed execution payload envelope for the Gloas fork.
|
||||
func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, signed interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.ReceiveExecutionPayloadEnvelope")
|
||||
defer span.End()
|
||||
|
||||
envelope, err := signed.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope")
|
||||
}
|
||||
root := envelope.BeaconBlockRoot()
|
||||
|
||||
err = s.payloadBeingSynced.set(root)
|
||||
if errors.Is(err, errBlockBeingSynced) {
|
||||
log.WithField("blockRoot", fmt.Sprintf("%#x", root)).Debug("Ignoring payload envelope currently being synced")
|
||||
return nil
|
||||
}
|
||||
defer s.payloadBeingSynced.unset(root)
|
||||
|
||||
preState, err := s.getPayloadEnvelopePrestate(ctx, envelope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var isValidPayload bool
|
||||
g, gCtx := errgroup.WithContext(ctx)
|
||||
|
||||
g.Go(func() error {
|
||||
return gloas.ProcessExecutionPayload(gCtx, preState, signed)
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
var elErr error
|
||||
isValidPayload, elErr = s.validateExecutionOnEnvelope(gCtx, preState, envelope)
|
||||
return elErr
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// DA check: verify data columns are available before inserting payload.
|
||||
bid, err := preState.LatestExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get latest execution payload bid")
|
||||
}
|
||||
if len(bid.BlobKzgCommitments()) > 0 {
|
||||
if err := s.areDataColumnsAvailable(ctx, root, envelope.Slot()); err != nil {
|
||||
return errors.Wrap(err, "data availability check failed for payload envelope")
|
||||
}
|
||||
}
|
||||
if err := s.savePostPayload(ctx, signed, preState); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.InsertPayload(envelope); err != nil {
|
||||
return errors.Wrap(err, "could not insert payload into forkchoice")
|
||||
}
|
||||
|
||||
if isValidPayload {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
if err := s.cfg.ForkChoiceStore.SetOptimisticToValid(ctx, root); err != nil {
|
||||
log.WithError(err).Error("Could not set optimistic to valid")
|
||||
}
|
||||
s.cfg.ForkChoiceStore.Unlock()
|
||||
}
|
||||
|
||||
headRoot, err := s.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get head root")
|
||||
return nil
|
||||
}
|
||||
if err := s.postPayloadHeadUpdate(ctx, envelope, preState, root, headRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.PayloadProcessed,
|
||||
Data: &statefeed.PayloadProcessedData{
|
||||
Slot: envelope.Slot(),
|
||||
BlockRoot: root,
|
||||
},
|
||||
})
|
||||
|
||||
execution, err := envelope.Execution()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get execution payload from envelope for logging")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": envelope.Slot(),
|
||||
"blockRoot": fmt.Sprintf("%#x", bytesutil.Trunc(root[:])),
|
||||
"blockHash": fmt.Sprintf("%#x", bytesutil.Trunc(execution.BlockHash())),
|
||||
"parentHash": fmt.Sprintf("%#x", bytesutil.Trunc(execution.ParentHash())),
|
||||
}).Info("Processed execution payload envelope")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) postPayloadHeadUpdate(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope, st state.BeaconState, root [32]byte, headRoot []byte) error {
|
||||
if !bytes.Equal(headRoot, root[:]) {
|
||||
return nil
|
||||
}
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get execution payload from envelope")
|
||||
}
|
||||
blockHash := bytesutil.ToBytes32(payload.BlockHash())
|
||||
|
||||
s.headLock.Lock()
|
||||
s.head.state = st
|
||||
s.headLock.Unlock()
|
||||
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(s.ctx, slotDeadline)
|
||||
defer cancel()
|
||||
if err := transition.UpdateNextSlotCache(ctx, blockHash[:], st); err != nil {
|
||||
log.WithError(err).Error("Could not update next slot cache")
|
||||
}
|
||||
if err := s.handleEpochBoundary(ctx, envelope.Slot(), st, blockHash[:]); err != nil {
|
||||
log.WithError(err).Error("Could not handle epoch boundary")
|
||||
}
|
||||
}()
|
||||
|
||||
attr := s.getPayloadAttribute(ctx, st, envelope.Slot()+1, headRoot)
|
||||
if s.inRegularSync() {
|
||||
go func() {
|
||||
pid, err := s.notifyForkchoiceUpdateGloas(s.ctx, blockHash, attr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not notify forkchoice update")
|
||||
return
|
||||
}
|
||||
if attr != nil && !attr.IsEmpty() && pid != nil {
|
||||
var pId [8]byte
|
||||
copy(pId[:], pid[:])
|
||||
s.cfg.PayloadIDCache.Set(envelope.Slot()+1, root, pId)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) getPayloadEnvelopePrestate(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.getPayloadEnvelopePrestate")
|
||||
defer span.End()
|
||||
|
||||
root := envelope.BeaconBlockRoot()
|
||||
if !s.InForkchoice(root) {
|
||||
return nil, fmt.Errorf("beacon block root %#x not found in forkchoice", root)
|
||||
}
|
||||
if err := s.verifyBlkPreState(ctx, root); err != nil {
|
||||
return nil, errors.Wrap(err, "could not verify pre-state")
|
||||
}
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, root)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get pre-state by root")
|
||||
}
|
||||
if preState == nil || preState.IsNil() {
|
||||
return nil, fmt.Errorf("nil pre-state for beacon block root %#x", root)
|
||||
}
|
||||
return preState, nil
|
||||
}
|
||||
|
||||
// The returned boolean indicates whether the payload was valid or if it was accepted as syncing (optimistic).
|
||||
func (s *Service) notifyNewEnvelope(ctx context.Context, st state.BeaconState, envelope interfaces.ROExecutionPayloadEnvelope) (bool, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.notifyNewEnvelope")
|
||||
defer span.End()
|
||||
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get execution payload from envelope")
|
||||
}
|
||||
|
||||
latestBid, err := st.LatestExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get latest execution payload bid")
|
||||
}
|
||||
commitments := latestBid.BlobKzgCommitments()
|
||||
versionedHashes := make([]common.Hash, len(commitments))
|
||||
for i, c := range commitments {
|
||||
versionedHashes[i] = primitives.ConvertKzgCommitmentToVersionedHash(c)
|
||||
}
|
||||
|
||||
parentRoot := common.Hash(bytesutil.ToBytes32(st.LatestBlockHeader().ParentRoot))
|
||||
requests := envelope.ExecutionRequests()
|
||||
|
||||
_, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, &parentRoot, requests)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": envelope.Slot(),
|
||||
"payloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(payload.BlockHash())),
|
||||
}).Info("Called new payload with optimistic envelope")
|
||||
return false, nil
|
||||
}
|
||||
if errors.Is(err, execution.ErrInvalidPayloadStatus) {
|
||||
return false, invalidBlock{error: ErrInvalidPayload}
|
||||
}
|
||||
return false, errors.WithMessage(ErrUndefinedExecutionEngineError, err.Error())
|
||||
}
|
||||
|
||||
func (s *Service) validateExecutionOnEnvelope(ctx context.Context, st state.BeaconState, envelope interfaces.ROExecutionPayloadEnvelope) (bool, error) {
|
||||
isValid, err := s.notifyNewEnvelope(ctx, st, envelope)
|
||||
if err == nil {
|
||||
return isValid, nil
|
||||
}
|
||||
|
||||
blockRoot := envelope.BeaconBlockRoot()
|
||||
parentRoot := bytesutil.ToBytes32(st.LatestBlockHeader().ParentRoot)
|
||||
payload, payloadErr := envelope.Execution()
|
||||
if payloadErr != nil {
|
||||
return false, errors.Wrap(payloadErr, "could not get execution payload from envelope")
|
||||
}
|
||||
parentHash := bytesutil.ToBytes32(payload.ParentHash())
|
||||
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
return false, s.handleInvalidExecutionError(ctx, err, blockRoot, parentRoot, parentHash)
|
||||
}
|
||||
|
||||
func (s *Service) savePostPayload(ctx context.Context, signed interfaces.ROSignedExecutionPayloadEnvelope, st state.BeaconState) error {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.savePostPayload")
|
||||
defer span.End()
|
||||
|
||||
protoEnv, ok := signed.Proto().(*ethpb.SignedExecutionPayloadEnvelope)
|
||||
if !ok {
|
||||
return errors.New("could not type assert signed envelope to proto")
|
||||
}
|
||||
if err := s.cfg.BeaconDB.SaveExecutionPayloadEnvelope(ctx, protoEnv); err != nil {
|
||||
return errors.Wrap(err, "could not save execution payload envelope")
|
||||
}
|
||||
|
||||
envelope, err := signed.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope")
|
||||
}
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get execution payload from envelope")
|
||||
}
|
||||
return s.cfg.StateGen.SaveState(ctx, bytesutil.ToBytes32(payload.BlockHash()), st)
|
||||
}
|
||||
|
||||
// notifyForkchoiceUpdateGloas takes the block hash directly because Gloas
|
||||
// blocks don't carry an execution payload in the body.
|
||||
func (s *Service) notifyForkchoiceUpdateGloas(ctx context.Context, blockHash [32]byte, attributes payloadattribute.Attributer) (*enginev1.PayloadIDBytes, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.notifyForkchoiceUpdateGloas")
|
||||
defer span.End()
|
||||
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
finalizedHash := s.cfg.ForkChoiceStore.FinalizedPayloadBlockHash()
|
||||
justifiedHash := s.cfg.ForkChoiceStore.UnrealizedJustifiedPayloadBlockHash()
|
||||
s.cfg.ForkChoiceStore.RUnlock()
|
||||
fcs := &enginev1.ForkchoiceState{
|
||||
HeadBlockHash: blockHash[:],
|
||||
SafeBlockHash: justifiedHash[:],
|
||||
FinalizedBlockHash: finalizedHash[:],
|
||||
}
|
||||
if attributes == nil {
|
||||
attributes = payloadattribute.EmptyWithVersion(version.Gloas)
|
||||
}
|
||||
|
||||
payloadID, lastValidHash, err := s.cfg.ExecutionEngineCaller.ForkchoiceUpdated(ctx, fcs, attributes)
|
||||
if err == nil {
|
||||
return payloadID, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus):
|
||||
log.WithFields(logrus.Fields{
|
||||
"headBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(blockHash[:])),
|
||||
"finalizedPayloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(finalizedHash[:])),
|
||||
}).Info("Called forkchoice updated with optimistic block (Gloas)")
|
||||
return payloadID, nil
|
||||
case errors.Is(err, execution.ErrInvalidPayloadStatus):
|
||||
if len(lastValidHash) == 0 {
|
||||
lastValidHash = defaultLatestValidHash
|
||||
}
|
||||
return nil, invalidBlock{
|
||||
error: ErrInvalidPayload,
|
||||
lastValidHash: bytesutil.ToBytes32(lastValidHash),
|
||||
}
|
||||
default:
|
||||
log.WithError(err).Error(ErrUndefinedExecutionEngineError)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// PayloadAttestationReceiver interface defines the methods of chain service for receiving
|
||||
// validated payload attestation messages.
|
||||
type PayloadAttestationReceiver interface {
|
||||
ReceivePayloadAttestationMessage(context.Context, *ethpb.PayloadAttestationMessage) error
|
||||
}
|
||||
|
||||
// ReceivePayloadAttestationMessage accepts a payload attestation message and updates the
|
||||
// forkchoice PTC vote bitvectors for the referenced beacon block.
|
||||
func (s *Service) ReceivePayloadAttestationMessage(ctx context.Context, a *ethpb.PayloadAttestationMessage) error {
|
||||
if a == nil || a.Data == nil {
|
||||
return errors.New("nil payload attestation message")
|
||||
}
|
||||
root := bytesutil.ToBytes32(a.Data.BeaconBlockRoot)
|
||||
|
||||
st, err := s.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptc, err := gloas.PayloadCommittee(ctx, st, a.Data.Slot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx := slices.Index(ptc, a.ValidatorIndex)
|
||||
if idx == -1 {
|
||||
return errors.New("validator not in PTC")
|
||||
}
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
s.cfg.ForkChoiceStore.SetPTCVote(root, uint64(idx), a.Data.PayloadPresent, a.Data.BlobDataAvailable)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
mockExecution "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
)
|
||||
|
||||
func TestReceivePayloadAttestationMessage_NilMessage(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
err := s.ReceivePayloadAttestationMessage(t.Context(), nil)
|
||||
require.ErrorContains(t, "nil payload attestation message", err)
|
||||
}
|
||||
|
||||
func TestReceivePayloadAttestationMessage_NilData(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
msg := ðpb.PayloadAttestationMessage{}
|
||||
err := s.ReceivePayloadAttestationMessage(t.Context(), msg)
|
||||
require.ErrorContains(t, "nil payload attestation message", err)
|
||||
}
|
||||
|
||||
func TestReceivePayloadAttestationMessage_ValidatorNotInPTC(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
numVals := 2048
|
||||
headState := gloasStateWithValidators(t, 1, numVals)
|
||||
|
||||
base, blk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, blk, blockRoot)
|
||||
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
s.head = &head{root: blockRoot, block: wsb, state: headState, slot: 1}
|
||||
|
||||
ptc, err := gloas.PayloadCommittee(ctx, headState, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Pick a validator index not in the PTC.
|
||||
inPTC := make(map[primitives.ValidatorIndex]bool)
|
||||
for _, idx := range ptc {
|
||||
inPTC[idx] = true
|
||||
}
|
||||
var notInPTC primitives.ValidatorIndex
|
||||
for i := primitives.ValidatorIndex(0); int(i) < numVals; i++ {
|
||||
if !inPTC[i] {
|
||||
notInPTC = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
msg := ðpb.PayloadAttestationMessage{
|
||||
ValidatorIndex: notInPTC,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
},
|
||||
}
|
||||
err = s.ReceivePayloadAttestationMessage(ctx, msg)
|
||||
require.ErrorContains(t, "validator not in PTC", err)
|
||||
}
|
||||
|
||||
func TestReceivePayloadAttestationMessage_OK(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
headState := gloasStateWithValidators(t, 1, 2048)
|
||||
|
||||
base, blk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, blk, blockRoot)
|
||||
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
s.head = &head{root: blockRoot, block: wsb, state: headState, slot: 1}
|
||||
|
||||
ptc, err := gloas.PayloadCommittee(ctx, headState, 1)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, len(ptc))
|
||||
|
||||
msg := ðpb.PayloadAttestationMessage{
|
||||
ValidatorIndex: ptc[0],
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
}
|
||||
require.NoError(t, s.ReceivePayloadAttestationMessage(ctx, msg))
|
||||
}
|
||||
|
||||
// gloasStateWithValidators returns a Gloas beacon state with active validators
|
||||
// for PTC committee computation.
|
||||
func gloasStateWithValidators(t *testing.T, slot primitives.Slot, numVals int) state.BeaconState {
|
||||
t.Helper()
|
||||
validators := make([]*ethpb.Validator, numVals)
|
||||
balances := make([]uint64, numVals)
|
||||
for i := range validators {
|
||||
validators[i] = ðpb.Validator{
|
||||
PublicKey: make([]byte, 48),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra,
|
||||
ActivationEpoch: 0,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
}
|
||||
balances[i] = params.BeaconConfig().MaxEffectiveBalanceElectra
|
||||
}
|
||||
st, err := util.NewBeaconStateGloas(func(s *ethpb.BeaconStateGloas) error {
|
||||
s.Slot = slot
|
||||
s.Validators = validators
|
||||
s.Balances = balances
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
}
|
||||
@@ -62,6 +62,7 @@ type Service struct {
|
||||
syncComplete chan struct{}
|
||||
blobNotifiers *blobNotifierMap
|
||||
blockBeingSynced *currentlySyncingBlock
|
||||
payloadBeingSynced *currentlySyncingBlock
|
||||
blobStorage *filesystem.BlobStorage
|
||||
dataColumnStorage *filesystem.DataColumnStorage
|
||||
slasherEnabled bool
|
||||
@@ -186,6 +187,7 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
blobNotifiers: bn,
|
||||
cfg: &config{},
|
||||
blockBeingSynced: ¤tlySyncingBlock{roots: make(map[[32]byte]struct{})},
|
||||
payloadBeingSynced: ¤tlySyncingBlock{roots: make(map[[32]byte]struct{})},
|
||||
syncCommitteeHeadState: cache.NewSyncCommitteeHeadState(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -104,7 +104,9 @@ func Test_setupForkchoiceTree_Head(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -77,6 +77,8 @@ type ChainService struct {
|
||||
DataColumns []blocks.VerifiedRODataColumn
|
||||
TargetRoot [32]byte
|
||||
MockHeadSlot *primitives.Slot
|
||||
ParentPayloadReadyVal *bool
|
||||
ForkchoiceRoots map[[32]byte]bool
|
||||
}
|
||||
|
||||
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
|
||||
@@ -334,6 +336,16 @@ func (s *ChainService) ReceiveBlock(ctx context.Context, block interfaces.ReadOn
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlockPreState mocks the same method in the chain service.
|
||||
func (s *ChainService) GetBlockPreState(_ context.Context, _ blocks.ROBlock) (state.BeaconState, error) {
|
||||
return s.State, nil
|
||||
}
|
||||
|
||||
// GetPrestateToPropose mocks the same method in the chain service.
|
||||
func (s *ChainService) GetPrestateToPropose(_ context.Context, _ blocks.ROBlock) (state.BeaconState, error) {
|
||||
return s.State.Copy(), nil
|
||||
}
|
||||
|
||||
// HeadSlot mocks HeadSlot method in chain service.
|
||||
func (s *ChainService) HeadSlot() primitives.Slot {
|
||||
if s.MockHeadSlot != nil {
|
||||
@@ -569,7 +581,10 @@ func (s *ChainService) IsOptimistic(_ context.Context) (bool, error) {
|
||||
}
|
||||
|
||||
// InForkchoice mocks the same method in the chain service
|
||||
func (s *ChainService) InForkchoice(_ [32]byte) bool {
|
||||
func (s *ChainService) InForkchoice(root [32]byte) bool {
|
||||
if s.ForkchoiceRoots != nil {
|
||||
return s.ForkchoiceRoots[root]
|
||||
}
|
||||
return !s.NotFinalized
|
||||
}
|
||||
|
||||
@@ -700,6 +715,14 @@ func (s *ChainService) InsertNode(ctx context.Context, st state.BeaconState, blo
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertPayload mocks the same method in the chain service
|
||||
func (s *ChainService) InsertPayload(pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.InsertPayload(pe)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForkChoiceDump mocks the same method in the chain service
|
||||
func (s *ChainService) ForkChoiceDump(ctx context.Context) (*forkchoice2.Dump, error) {
|
||||
if s.ForkChoiceStore != nil {
|
||||
@@ -757,6 +780,24 @@ func (c *ChainService) ReceiveDataColumns(dcs []blocks.VerifiedRODataColumn) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReceivePayloadAttestationMessage implements the same method in the chain service.
|
||||
func (c *ChainService) ReceivePayloadAttestationMessage(_ context.Context, _ *ethpb.PayloadAttestationMessage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReceiveExecutionPayloadEnvelope implements the same method in the chain service.
|
||||
func (c *ChainService) ReceiveExecutionPayloadEnvelope(_ context.Context, _ interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParentPayloadReady mocks the same method in the chain service.
|
||||
func (s *ChainService) ParentPayloadReady(_ interfaces.ReadOnlyBeaconBlock) bool {
|
||||
if s.ParentPayloadReadyVal != nil {
|
||||
return *s.ParentPayloadReadyVal
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DependentRootForEpoch mocks the same method in the chain service
|
||||
func (c *ChainService) DependentRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
|
||||
return c.TargetRoot, nil
|
||||
|
||||
2
beacon-chain/cache/BUILD.bazel
vendored
2
beacon-chain/cache/BUILD.bazel
vendored
@@ -17,6 +17,7 @@ go_library(
|
||||
"error.go",
|
||||
"interfaces.go",
|
||||
"log.go",
|
||||
"payload_attestation.go",
|
||||
"payload_id.go",
|
||||
"proposer_indices.go",
|
||||
"proposer_indices_disabled.go", # keep
|
||||
@@ -76,6 +77,7 @@ go_test(
|
||||
"checkpoint_state_test.go",
|
||||
"committee_fuzz_test.go",
|
||||
"committee_test.go",
|
||||
"payload_attestation_test.go",
|
||||
"payload_id_test.go",
|
||||
"private_access_test.go",
|
||||
"proposer_indices_test.go",
|
||||
|
||||
53
beacon-chain/cache/payload_attestation.go
vendored
Normal file
53
beacon-chain/cache/payload_attestation.go
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
)
|
||||
|
||||
// PayloadAttestationCache tracks seen payload attestation messages for a single slot.
|
||||
type PayloadAttestationCache struct {
|
||||
slot primitives.Slot
|
||||
seen map[primitives.ValidatorIndex]struct{}
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Seen returns true if a vote for the given slot has already been
|
||||
// processed for this validator index.
|
||||
func (p *PayloadAttestationCache) Seen(slot primitives.Slot, idx primitives.ValidatorIndex) bool {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
if p.slot != slot {
|
||||
return false
|
||||
}
|
||||
if p.seen == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := p.seen[idx]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Add marks the given slot and validator index as seen.
|
||||
// This function assumes that the message has already been validated.
|
||||
func (p *PayloadAttestationCache) Add(slot primitives.Slot, idx primitives.ValidatorIndex) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.slot != slot {
|
||||
p.slot = slot
|
||||
p.seen = make(map[primitives.ValidatorIndex]struct{})
|
||||
}
|
||||
if p.seen == nil {
|
||||
p.seen = make(map[primitives.ValidatorIndex]struct{})
|
||||
}
|
||||
p.seen[idx] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear clears the internal cache.
|
||||
func (p *PayloadAttestationCache) Clear() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.slot = 0
|
||||
p.seen = nil
|
||||
}
|
||||
48
beacon-chain/cache/payload_attestation_test.go
vendored
Normal file
48
beacon-chain/cache/payload_attestation_test.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package cache_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPayloadAttestationCache_SeenAndAdd(t *testing.T) {
|
||||
var c cache.PayloadAttestationCache
|
||||
slot1 := primitives.Slot(1)
|
||||
slot2 := primitives.Slot(2)
|
||||
idx1 := primitives.ValidatorIndex(3)
|
||||
idx2 := primitives.ValidatorIndex(4)
|
||||
|
||||
require.False(t, c.Seen(slot1, idx1))
|
||||
|
||||
require.NoError(t, c.Add(slot1, idx1))
|
||||
require.True(t, c.Seen(slot1, idx1))
|
||||
require.False(t, c.Seen(slot1, idx2))
|
||||
require.False(t, c.Seen(slot2, idx1))
|
||||
|
||||
require.NoError(t, c.Add(slot1, idx2))
|
||||
require.True(t, c.Seen(slot1, idx1))
|
||||
require.True(t, c.Seen(slot1, idx2))
|
||||
|
||||
require.NoError(t, c.Add(slot2, idx1))
|
||||
require.True(t, c.Seen(slot2, idx1))
|
||||
require.False(t, c.Seen(slot1, idx1))
|
||||
require.False(t, c.Seen(slot1, idx2))
|
||||
}
|
||||
|
||||
func TestPayloadAttestationCache_Clear(t *testing.T) {
|
||||
var c cache.PayloadAttestationCache
|
||||
slot := primitives.Slot(10)
|
||||
idx := primitives.ValidatorIndex(42)
|
||||
|
||||
require.NoError(t, c.Add(slot, idx))
|
||||
require.True(t, c.Seen(slot, idx))
|
||||
|
||||
c.Clear()
|
||||
require.False(t, c.Seen(slot, idx))
|
||||
|
||||
require.NoError(t, c.Add(slot, idx))
|
||||
require.True(t, c.Seen(slot, idx))
|
||||
}
|
||||
@@ -20,6 +20,7 @@ go_library(
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/epoch:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
@@ -75,7 +76,11 @@ func ProcessAttestationNoVerifySignature(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return SetParticipationAndRewardProposer(ctx, beaconState, att.GetData().Target.Epoch, indices, participatedFlags, totalBalance)
|
||||
if err := beaconState.UpdatePendingPaymentWeight(att, indices, participatedFlags); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to update pending payment weight")
|
||||
}
|
||||
|
||||
return SetParticipationAndRewardProposer(ctx, beaconState, att.GetData().Target.Epoch, indices, participatedFlags, totalBalance, att)
|
||||
}
|
||||
|
||||
// SetParticipationAndRewardProposer retrieves and sets the epoch participation bits in state. Based on the epoch participation, it rewards
|
||||
@@ -105,7 +110,9 @@ func SetParticipationAndRewardProposer(
|
||||
beaconState state.BeaconState,
|
||||
targetEpoch primitives.Epoch,
|
||||
indices []uint64,
|
||||
participatedFlags map[uint8]bool, totalBalance uint64) (state.BeaconState, error) {
|
||||
participatedFlags map[uint8]bool,
|
||||
totalBalance uint64,
|
||||
att ethpb.Att) (state.BeaconState, error) {
|
||||
var proposerRewardNumerator uint64
|
||||
currentEpoch := time.CurrentEpoch(beaconState)
|
||||
var stateErr error
|
||||
@@ -299,6 +306,19 @@ func AttestationParticipationFlagIndices(beaconState state.ReadOnlyBeaconState,
|
||||
participatedFlags[targetFlagIndex] = true
|
||||
}
|
||||
matchedSrcTgtHead := matchedHead && matchedSrcTgt
|
||||
|
||||
var beaconBlockRoot [32]byte
|
||||
copy(beaconBlockRoot[:], data.BeaconBlockRoot)
|
||||
matchingPayload, err := gloas.MatchingPayload(
|
||||
beaconState,
|
||||
beaconBlockRoot,
|
||||
data.Slot,
|
||||
uint64(data.CommitteeIndex),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchedSrcTgtHead = matchedSrcTgtHead && matchingPayload
|
||||
if matchedSrcTgtHead && delay == cfg.MinAttestationInclusionDelay {
|
||||
participatedFlags[headFlagIndex] = true
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package altair_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/go-bitfield"
|
||||
@@ -556,7 +558,7 @@ func TestSetParticipationAndRewardProposer(t *testing.T) {
|
||||
|
||||
b, err := helpers.TotalActiveBalance(beaconState)
|
||||
require.NoError(t, err)
|
||||
st, err := altair.SetParticipationAndRewardProposer(t.Context(), beaconState, test.epoch, test.indices, test.participatedFlags, b)
|
||||
st, err := altair.SetParticipationAndRewardProposer(t.Context(), beaconState, test.epoch, test.indices, test.participatedFlags, b, ðpb.Attestation{})
|
||||
require.NoError(t, err)
|
||||
|
||||
i, err := helpers.BeaconProposerIndex(t.Context(), st)
|
||||
@@ -775,11 +777,67 @@ func TestAttestationParticipationFlagIndices(t *testing.T) {
|
||||
headFlagIndex: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gloas same-slot committee index non-zero errors",
|
||||
inputState: func() state.BeaconState {
|
||||
stateSlot := primitives.Slot(5)
|
||||
slot := primitives.Slot(3)
|
||||
targetRoot := bytes.Repeat([]byte{0xAA}, 32)
|
||||
headRoot := bytes.Repeat([]byte{0xBB}, 32)
|
||||
prevRoot := bytes.Repeat([]byte{0xCC}, 32)
|
||||
return buildGloasStateForFlags(t, stateSlot, slot, targetRoot, headRoot, prevRoot, 0, 0)
|
||||
}(),
|
||||
inputData: ðpb.AttestationData{
|
||||
Slot: 3,
|
||||
CommitteeIndex: 1, // invalid for same-slot
|
||||
BeaconBlockRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||
Source: ðpb.Checkpoint{Root: bytes.Repeat([]byte{0xDD}, 32)},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: bytes.Repeat([]byte{0xAA}, 32),
|
||||
},
|
||||
},
|
||||
inputDelay: 1,
|
||||
participationIndices: nil,
|
||||
},
|
||||
{
|
||||
name: "gloas payload availability matches committee index",
|
||||
inputState: func() state.BeaconState {
|
||||
stateSlot := primitives.Slot(5)
|
||||
slot := primitives.Slot(3)
|
||||
targetRoot := bytes.Repeat([]byte{0xAA}, 32)
|
||||
headRoot := bytes.Repeat([]byte{0xBB}, 32)
|
||||
// Same prev root to make SameSlotAttestation false and use payload availability.
|
||||
return buildGloasStateForFlags(t, stateSlot, slot, targetRoot, headRoot, headRoot, 1, slot)
|
||||
}(),
|
||||
inputData: ðpb.AttestationData{
|
||||
Slot: 3,
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||
Source: ðpb.Checkpoint{Root: bytes.Repeat([]byte{0xDD}, 32)},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: bytes.Repeat([]byte{0xAA}, 32),
|
||||
},
|
||||
},
|
||||
inputDelay: 1,
|
||||
participationIndices: map[uint8]bool{
|
||||
sourceFlagIndex: true,
|
||||
targetFlagIndex: true,
|
||||
headFlagIndex: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
flagIndices, err := altair.AttestationParticipationFlagIndices(test.inputState, test.inputData, test.inputDelay)
|
||||
if test.participationIndices == nil {
|
||||
require.ErrorContains(t, "committee index", err)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, test.participationIndices, flagIndices)
|
||||
if !reflect.DeepEqual(test.participationIndices, flagIndices) {
|
||||
t.Fatalf("unexpected participation indices: got %v want %v", flagIndices, test.participationIndices)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,3 +916,61 @@ func TestMatchingStatus(t *testing.T) {
|
||||
require.Equal(t, test.matchedHead, head)
|
||||
}
|
||||
}
|
||||
|
||||
func buildGloasStateForFlags(t *testing.T, stateSlot, slot primitives.Slot, targetRoot, headRoot, prevRoot []byte, availabilityBit uint8, availabilitySlot primitives.Slot) state.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
blockRoots[0] = targetRoot
|
||||
blockRoots[slot%cfg.SlotsPerHistoricalRoot] = headRoot
|
||||
blockRoots[(slot-1)%cfg.SlotsPerHistoricalRoot] = prevRoot
|
||||
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range stateRoots {
|
||||
stateRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
|
||||
execPayloadAvailability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||
idx := availabilitySlot % cfg.SlotsPerHistoricalRoot
|
||||
byteIndex := idx / 8
|
||||
bitIndex := idx % 8
|
||||
if availabilityBit == 1 {
|
||||
execPayloadAvailability[byteIndex] |= 1 << bitIndex
|
||||
}
|
||||
|
||||
checkpointRoot := bytes.Repeat([]byte{0xDD}, fieldparams.RootLength)
|
||||
justified := ðpb.Checkpoint{Root: checkpointRoot}
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x11}, fieldparams.RootLength),
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
ExecutionPayloadAvailability: execPayloadAvailability,
|
||||
CurrentJustifiedCheckpoint: justified,
|
||||
PreviousJustifiedCheckpoint: justified,
|
||||
Validators: []*ethpb.Validator{
|
||||
{
|
||||
EffectiveBalance: cfg.MinActivationBalance,
|
||||
WithdrawalCredentials: append([]byte{cfg.ETH1AddressWithdrawalPrefixByte}, bytes.Repeat([]byte{0x01}, 31)...),
|
||||
},
|
||||
},
|
||||
Balances: []uint64{cfg.MinActivationBalance},
|
||||
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2),
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
}
|
||||
|
||||
beaconState, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return beaconState
|
||||
}
|
||||
|
||||
@@ -111,10 +111,21 @@ func VerifyAttestationNoVerifySignature(
|
||||
var indexedAtt ethpb.IndexedAtt
|
||||
|
||||
if att.Version() >= version.Electra {
|
||||
if att.GetData().CommitteeIndex != 0 {
|
||||
return errors.New("committee index must be 0 post-Electra")
|
||||
ci := att.GetData().CommitteeIndex
|
||||
// Spec v1.7.0-alpha pseudocode:
|
||||
//
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// assert data.index < 2
|
||||
//
|
||||
if beaconState.Version() >= version.Gloas {
|
||||
if ci >= 2 {
|
||||
return fmt.Errorf("incorrect committee index %d", ci)
|
||||
}
|
||||
} else {
|
||||
if ci != 0 {
|
||||
return errors.New("committee index must be 0 between Electra and Gloas forks")
|
||||
}
|
||||
}
|
||||
|
||||
aggBits := att.GetAggregationBits()
|
||||
committeeIndices := att.CommitteeBitsVal().BitIndices()
|
||||
committees := make([][]primitives.ValidatorIndex, len(committeeIndices))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package blocks_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
@@ -262,7 +263,7 @@ func TestVerifyAttestationNoVerifySignature_Electra(t *testing.T) {
|
||||
CommitteeBits: bitfield.NewBitvector64(),
|
||||
}
|
||||
err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att)
|
||||
assert.ErrorContains(t, "committee index must be 0 post-Electra", err)
|
||||
assert.ErrorContains(t, "committee index must be 0", err)
|
||||
})
|
||||
t.Run("index of committee too big", func(t *testing.T) {
|
||||
aggBits := bitfield.NewBitlist(3)
|
||||
@@ -314,6 +315,75 @@ func TestVerifyAttestationNoVerifySignature_Electra(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyAttestationNoVerifySignature_GloasCommitteeIndexLimit(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
stateSlot := cfg.MinAttestationInclusionDelay + 1
|
||||
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range blockRoots {
|
||||
blockRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range stateRoots {
|
||||
stateRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
|
||||
checkpointRoot := bytes.Repeat([]byte{0xAA}, fieldparams.RootLength)
|
||||
justified := ðpb.Checkpoint{Epoch: 0, Root: checkpointRoot}
|
||||
|
||||
gloasStateProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x11}, fieldparams.RootLength),
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
ExecutionPayloadAvailability: make([]byte, cfg.SlotsPerHistoricalRoot/8),
|
||||
CurrentJustifiedCheckpoint: justified,
|
||||
PreviousJustifiedCheckpoint: justified,
|
||||
Validators: []*ethpb.Validator{
|
||||
{
|
||||
EffectiveBalance: cfg.MinActivationBalance,
|
||||
WithdrawalCredentials: append([]byte{cfg.ETH1AddressWithdrawalPrefixByte}, bytes.Repeat([]byte{0x01}, 31)...),
|
||||
},
|
||||
},
|
||||
Balances: []uint64{cfg.MinActivationBalance},
|
||||
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2),
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
}
|
||||
|
||||
beaconState, err := state_native.InitializeFromProtoGloas(gloasStateProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
committeeBits := bitfield.NewBitvector64()
|
||||
committeeBits.SetBitAt(0, true)
|
||||
aggBits := bitfield.NewBitlist(1)
|
||||
aggBits.SetBitAt(0, true)
|
||||
|
||||
att := ðpb.AttestationElectra{
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 0,
|
||||
CommitteeIndex: 2, // invalid for Gloas (must be <2)
|
||||
BeaconBlockRoot: blockRoots[0],
|
||||
Source: justified,
|
||||
Target: justified,
|
||||
},
|
||||
AggregationBits: aggBits,
|
||||
CommitteeBits: committeeBits,
|
||||
Signature: bytes.Repeat([]byte{0x00}, fieldparams.BLSSignatureLength),
|
||||
}
|
||||
|
||||
err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att)
|
||||
assert.ErrorContains(t, "incorrect committee index 2", err)
|
||||
}
|
||||
|
||||
func TestConvertToIndexed_OK(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
validators := make([]*ethpb.Validator, 2*params.BeaconConfig().SlotsPerEpoch)
|
||||
@@ -583,6 +653,7 @@ func TestVerifyAttestations_HandlesPlannedFork(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetrieveAttestationSignatureSet_VerifiesMultipleAttestations(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
ctx := t.Context()
|
||||
numOfValidators := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(4))
|
||||
validators := make([]*ethpb.Validator, numOfValidators)
|
||||
|
||||
@@ -33,6 +33,8 @@ const (
|
||||
LightClientOptimisticUpdate
|
||||
// PayloadAttributes events are fired upon a missed slot or new head.
|
||||
PayloadAttributes
|
||||
// PayloadProcessed is sent after a payload envelope has been processed.
|
||||
PayloadProcessed
|
||||
)
|
||||
|
||||
// BlockProcessedData is the data sent with BlockProcessed events.
|
||||
@@ -72,3 +74,9 @@ type InitializedData struct {
|
||||
// GenesisValidatorsRoot represents state.validators.HashTreeRoot().
|
||||
GenesisValidatorsRoot []byte
|
||||
}
|
||||
|
||||
// PayloadProcessedData is the data sent with PayloadProcessed events.
|
||||
type PayloadProcessedData struct {
|
||||
Slot primitives.Slot
|
||||
BlockRoot [32]byte
|
||||
}
|
||||
|
||||
@@ -3,18 +3,26 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attestation.go",
|
||||
"bid.go",
|
||||
"deposit_request.go",
|
||||
"log.go",
|
||||
"payload.go",
|
||||
"payload_attestation.go",
|
||||
"pending_payment.go",
|
||||
"proposer_slashing.go",
|
||||
"upgrade.go",
|
||||
"withdrawals.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/requests:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
@@ -25,26 +33,37 @@ go_library(
|
||||
"//crypto/bls/common:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"attestation_test.go",
|
||||
"bid_test.go",
|
||||
"deposit_request_test.go",
|
||||
"payload_attestation_test.go",
|
||||
"payload_test.go",
|
||||
"pending_payment_test.go",
|
||||
"proposer_slashing_test.go",
|
||||
"upgrade_test.go",
|
||||
"withdrawals_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//beacon-chain/state/testing:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
|
||||
52
beacon-chain/core/gloas/attestation.go
Normal file
52
beacon-chain/core/gloas/attestation.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// MatchingPayload returns true if the attestation's committee index matches the expected payload index.
|
||||
//
|
||||
// For pre-Gloas forks, this always returns true.
|
||||
//
|
||||
// Spec v1.7.0-alpha (pseudocode):
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// if is_attestation_same_slot(state, data):
|
||||
// assert data.index == 0
|
||||
// payload_matches = True
|
||||
// else:
|
||||
// slot_index = data.slot % SLOTS_PER_HISTORICAL_ROOT
|
||||
// payload_index = state.execution_payload_availability[slot_index]
|
||||
// payload_matches = data.index == payload_index
|
||||
func MatchingPayload(
|
||||
beaconState state.ReadOnlyBeaconState,
|
||||
beaconBlockRoot [32]byte,
|
||||
slot primitives.Slot,
|
||||
committeeIndex uint64,
|
||||
) (bool, error) {
|
||||
if beaconState.Version() < version.Gloas {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
sameSlot, err := beaconState.IsAttestationSameSlot(beaconBlockRoot, slot)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to get same slot attestation status")
|
||||
}
|
||||
if sameSlot {
|
||||
if committeeIndex != 0 {
|
||||
return false, fmt.Errorf("committee index %d for same slot attestation must be 0", committeeIndex)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
executionPayloadAvail, err := beaconState.ExecutionPayloadAvailability(slot)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to get execution payload availability status")
|
||||
}
|
||||
return executionPayloadAvail == committeeIndex, nil
|
||||
}
|
||||
110
beacon-chain/core/gloas/attestation_test.go
Normal file
110
beacon-chain/core/gloas/attestation_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func buildStateWithBlockRoots(t *testing.T, stateSlot primitives.Slot, roots map[primitives.Slot][]byte) *state_native.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for slot, root := range roots {
|
||||
blockRoots[slot%cfg.SlotsPerHistoricalRoot] = root
|
||||
}
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
BlockRoots: blockRoots,
|
||||
}
|
||||
|
||||
state, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return state.(*state_native.BeaconState)
|
||||
}
|
||||
|
||||
func TestMatchingPayload(t *testing.T) {
|
||||
t.Run("pre-gloas always true", func(t *testing.T) {
|
||||
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ok, err := MatchingPayload(stIface, [32]byte{}, 0, 123)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
})
|
||||
|
||||
t.Run("same slot requires committee index 0", func(t *testing.T) {
|
||||
root := bytes.Repeat([]byte{0xAA}, 32)
|
||||
state := buildStateWithBlockRoots(t, 6, map[primitives.Slot][]byte{
|
||||
4: root,
|
||||
3: bytes.Repeat([]byte{0xBB}, 32),
|
||||
})
|
||||
|
||||
var rootArr [32]byte
|
||||
copy(rootArr[:], root)
|
||||
|
||||
ok, err := MatchingPayload(state, rootArr, 4, 1)
|
||||
require.ErrorContains(t, "committee index", err)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
|
||||
t.Run("same slot matches when committee index is 0", func(t *testing.T) {
|
||||
root := bytes.Repeat([]byte{0xAA}, 32)
|
||||
state := buildStateWithBlockRoots(t, 6, map[primitives.Slot][]byte{
|
||||
4: root,
|
||||
3: bytes.Repeat([]byte{0xBB}, 32),
|
||||
})
|
||||
|
||||
var rootArr [32]byte
|
||||
copy(rootArr[:], root)
|
||||
|
||||
ok, err := MatchingPayload(state, rootArr, 4, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
})
|
||||
|
||||
t.Run("non same slot checks payload availability", func(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
root := bytes.Repeat([]byte{0xAA}, 32)
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
blockRoots[4%cfg.SlotsPerHistoricalRoot] = bytes.Repeat([]byte{0xCC}, 32)
|
||||
blockRoots[3%cfg.SlotsPerHistoricalRoot] = bytes.Repeat([]byte{0xBB}, 32)
|
||||
|
||||
availability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||
slotIndex := uint64(4)
|
||||
availability[slotIndex/8] = byte(1 << (slotIndex % 8))
|
||||
|
||||
stIface, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Slot: 6,
|
||||
BlockRoots: blockRoots,
|
||||
ExecutionPayloadAvailability: availability,
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x66}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x66}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
state := stIface.(*state_native.BeaconState)
|
||||
require.Equal(t, version.Gloas, state.Version())
|
||||
|
||||
var rootArr [32]byte
|
||||
copy(rootArr[:], root)
|
||||
|
||||
ok, err := MatchingPayload(state, rootArr, 4, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
ok, err = MatchingPayload(state, rootArr, 4, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
}
|
||||
@@ -17,27 +17,56 @@ import (
|
||||
)
|
||||
|
||||
// ProcessExecutionPayloadBid processes a signed execution payload bid in the Gloas fork.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// process_execution_payload_bid(state: BeaconState, block: BeaconBlock):
|
||||
//
|
||||
// signed_bid = block.body.signed_execution_payload_bid
|
||||
// bid = signed_bid.message
|
||||
// builder_index = bid.builder_index
|
||||
// amount = bid.value
|
||||
// if builder_index == BUILDER_INDEX_SELF_BUILD:
|
||||
// assert amount == 0
|
||||
// assert signed_bid.signature == G2_POINT_AT_INFINITY
|
||||
// else:
|
||||
// assert is_active_builder(state, builder_index)
|
||||
// assert can_builder_cover_bid(state, builder_index, amount)
|
||||
// assert verify_execution_payload_bid_signature(state, signed_bid)
|
||||
// assert bid.slot == block.slot
|
||||
// assert bid.parent_block_hash == state.latest_block_hash
|
||||
// assert bid.parent_block_root == block.parent_root
|
||||
// assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
||||
// if amount > 0:
|
||||
// state.builder_pending_payments[...] = BuilderPendingPayment(weight=0, withdrawal=BuilderPendingWithdrawal(fee_recipient=bid.fee_recipient, amount=amount, builder_index=builder_index))
|
||||
// state.latest_execution_payload_bid = bid
|
||||
// <spec fn="process_execution_payload_bid" fork="gloas" hash="823c9f3a">
|
||||
// def process_execution_payload_bid(state: BeaconState, block: BeaconBlock) -> None:
|
||||
// signed_bid = block.body.signed_execution_payload_bid
|
||||
// bid = signed_bid.message
|
||||
// builder_index = bid.builder_index
|
||||
// amount = bid.value
|
||||
//
|
||||
// # For self-builds, amount must be zero regardless of withdrawal credential prefix
|
||||
// if builder_index == BUILDER_INDEX_SELF_BUILD:
|
||||
// assert amount == 0
|
||||
// assert signed_bid.signature == bls.G2_POINT_AT_INFINITY
|
||||
// else:
|
||||
// # Verify that the builder is active
|
||||
// assert is_active_builder(state, builder_index)
|
||||
// # Verify that the builder has funds to cover the bid
|
||||
// assert can_builder_cover_bid(state, builder_index, amount)
|
||||
// # Verify that the bid signature is valid
|
||||
// assert verify_execution_payload_bid_signature(state, signed_bid)
|
||||
//
|
||||
// # Verify commitments are under limit
|
||||
// assert (
|
||||
// len(bid.blob_kzg_commitments)
|
||||
// <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block
|
||||
// )
|
||||
//
|
||||
// # Verify that the bid is for the current slot
|
||||
// assert bid.slot == block.slot
|
||||
// # Verify that the bid is for the right parent block
|
||||
// assert bid.parent_block_hash == state.latest_block_hash
|
||||
// assert bid.parent_block_root == block.parent_root
|
||||
// assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
||||
//
|
||||
// # Record the pending payment if there is some payment
|
||||
// if amount > 0:
|
||||
// pending_payment = BuilderPendingPayment(
|
||||
// weight=0,
|
||||
// withdrawal=BuilderPendingWithdrawal(
|
||||
// fee_recipient=bid.fee_recipient,
|
||||
// amount=amount,
|
||||
// builder_index=builder_index,
|
||||
// ),
|
||||
// )
|
||||
// state.builder_pending_payments[SLOTS_PER_EPOCH + bid.slot % SLOTS_PER_EPOCH] = (
|
||||
// pending_payment
|
||||
// )
|
||||
//
|
||||
// # Cache the signed execution payload bid
|
||||
// state.latest_execution_payload_bid = bid
|
||||
// </spec>
|
||||
func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyBeaconBlock) error {
|
||||
signedBid, err := block.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
@@ -86,6 +115,12 @@ func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyB
|
||||
}
|
||||
}
|
||||
|
||||
maxBlobsPerBlock := params.BeaconConfig().MaxBlobsPerBlockAtEpoch(slots.ToEpoch(block.Slot()))
|
||||
commitmentCount := bid.BlobKzgCommitmentCount()
|
||||
if commitmentCount > uint64(maxBlobsPerBlock) {
|
||||
return fmt.Errorf("bid has %d blob KZG commitments over max %d", commitmentCount, maxBlobsPerBlock)
|
||||
}
|
||||
|
||||
if err := validateBidConsistency(st, bid, block); err != nil {
|
||||
return errors.Wrap(err, "bid consistency validation failed")
|
||||
}
|
||||
|
||||
@@ -184,6 +184,28 @@ func signBid(t *testing.T, sk common.SecretKey, bid *ethpb.ExecutionPayloadBid,
|
||||
return out
|
||||
}
|
||||
|
||||
func blobCommitmentsForSlot(slot primitives.Slot, count int) [][]byte {
|
||||
max := int(params.BeaconConfig().MaxBlobsPerBlockAtEpoch(slots.ToEpoch(slot)))
|
||||
if count > max {
|
||||
count = max
|
||||
}
|
||||
commitments := make([][]byte, count)
|
||||
for i := range commitments {
|
||||
commitments[i] = bytes.Repeat([]byte{0xEE}, 48)
|
||||
}
|
||||
return commitments
|
||||
}
|
||||
|
||||
func tooManyBlobCommitmentsForSlot(slot primitives.Slot) [][]byte {
|
||||
max := int(params.BeaconConfig().MaxBlobsPerBlockAtEpoch(slots.ToEpoch(slot)))
|
||||
count := max + 1
|
||||
commitments := make([][]byte, count)
|
||||
for i := range commitments {
|
||||
commitments[i] = bytes.Repeat([]byte{0xEE}, 48)
|
||||
}
|
||||
return commitments
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_SelfBuildSuccess(t *testing.T) {
|
||||
slot := primitives.Slot(12)
|
||||
proposerIdx := primitives.ValidatorIndex(0)
|
||||
@@ -194,17 +216,17 @@ func TestProcessExecutionPayloadBid_SelfBuildSuccess(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 0,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 0,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
@@ -236,16 +258,16 @@ func TestProcessExecutionPayloadBid_SelfBuildNonZeroAmountFails(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, [48]byte{})
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
}
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
@@ -280,17 +302,17 @@ func TestProcessExecutionPayloadBid_PendingPaymentAndCacheBid(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, balance, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 500_000,
|
||||
ExecutionPayment: 1,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 500_000,
|
||||
ExecutionPayment: 1,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
@@ -341,17 +363,17 @@ func TestProcessExecutionPayloadBid_BuilderNotActive(t *testing.T) {
|
||||
state = stateIface.(*state_native.BeaconState)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x04}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x05}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x04}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -394,17 +416,17 @@ func TestProcessExecutionPayloadBid_CannotCoverBid(t *testing.T) {
|
||||
state = stateIface.(*state_native.BeaconState)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 25,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 25,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -436,17 +458,17 @@ func TestProcessExecutionPayloadBid_InvalidSignature(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
// Use an invalid signature.
|
||||
invalidSig := [96]byte{1}
|
||||
@@ -463,6 +485,42 @@ func TestProcessExecutionPayloadBid_InvalidSignature(t *testing.T) {
|
||||
require.ErrorContains(t, "bid signature validation failed", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_TooManyBlobCommitments(t *testing.T) {
|
||||
slot := primitives.Slot(9)
|
||||
proposerIdx := primitives.ValidatorIndex(0)
|
||||
builderIdx := params.BeaconConfig().BuilderIndexSelfBuild
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
pubKey := [48]byte{}
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
BlobKzgCommitments: tooManyBlobCommitmentsForSlot(slot),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
Signature: common.InfiniteSignature[:],
|
||||
}
|
||||
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err := ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "blob KZG commitments over max", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_SlotMismatch(t *testing.T) {
|
||||
slot := primitives.Slot(10)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
@@ -478,17 +536,17 @@ func TestProcessExecutionPayloadBid_SlotMismatch(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot + 1, // mismatch
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot + 1, // mismatch
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -520,17 +578,17 @@ func TestProcessExecutionPayloadBid_ParentHashMismatch(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -563,17 +621,17 @@ func TestProcessExecutionPayloadBid_ParentRootMismatch(t *testing.T) {
|
||||
|
||||
parentRoot := bytes.Repeat([]byte{0x22}, 32)
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: parentRoot,
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: parentRoot,
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -605,17 +663,17 @@ func TestProcessExecutionPayloadBid_PrevRandaoMismatch(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
|
||||
173
beacon-chain/core/gloas/deposit_request.go
Normal file
173
beacon-chain/core/gloas/deposit_request.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func processDepositRequests(ctx context.Context, beaconState state.BeaconState, requests []*enginev1.DepositRequest) error {
|
||||
if len(requests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, receipt := range requests {
|
||||
if err := processDepositRequest(beaconState, receipt); err != nil {
|
||||
return errors.Wrap(err, "could not apply deposit request")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// processDepositRequest processes the specific deposit request
|
||||
//
|
||||
// <spec fn="process_deposit_request" fork="gloas" hash="3c6b0310">
|
||||
// def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None:
|
||||
// # [New in Gloas:EIP7732]
|
||||
// builder_pubkeys = [b.pubkey for b in state.builders]
|
||||
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// # Regardless of the withdrawal credentials prefix, if a builder/validator
|
||||
// # already exists with this pubkey, apply the deposit to their balance
|
||||
// is_builder = deposit_request.pubkey in builder_pubkeys
|
||||
// is_validator = deposit_request.pubkey in validator_pubkeys
|
||||
// is_builder_prefix = is_builder_withdrawal_credential(deposit_request.withdrawal_credentials)
|
||||
// if is_builder or (is_builder_prefix and not is_validator):
|
||||
// # Apply builder deposits immediately
|
||||
// apply_deposit_for_builder(
|
||||
// state,
|
||||
// deposit_request.pubkey,
|
||||
// deposit_request.withdrawal_credentials,
|
||||
// deposit_request.amount,
|
||||
// deposit_request.signature,
|
||||
// state.slot,
|
||||
// )
|
||||
// return
|
||||
//
|
||||
// # Add validator deposits to the queue
|
||||
// state.pending_deposits.append(
|
||||
// PendingDeposit(
|
||||
// pubkey=deposit_request.pubkey,
|
||||
// withdrawal_credentials=deposit_request.withdrawal_credentials,
|
||||
// amount=deposit_request.amount,
|
||||
// signature=deposit_request.signature,
|
||||
// slot=state.slot,
|
||||
// )
|
||||
// )
|
||||
// </spec>
|
||||
func processDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest) error {
|
||||
if request == nil {
|
||||
return errors.New("nil deposit request")
|
||||
}
|
||||
|
||||
applied, err := applyBuilderDepositRequest(beaconState, request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not apply builder deposit")
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := beaconState.AppendPendingDeposit(ðpb.PendingDeposit{
|
||||
PublicKey: request.Pubkey,
|
||||
WithdrawalCredentials: request.WithdrawalCredentials,
|
||||
Amount: request.Amount,
|
||||
Signature: request.Signature,
|
||||
Slot: beaconState.Slot(),
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "could not append deposit request")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// <spec fn="apply_deposit_for_builder" fork="gloas" hash="e4bc98c7">
|
||||
// def apply_deposit_for_builder(
|
||||
//
|
||||
// state: BeaconState,
|
||||
// pubkey: BLSPubkey,
|
||||
// withdrawal_credentials: Bytes32,
|
||||
// amount: uint64,
|
||||
// signature: BLSSignature,
|
||||
// slot: Slot,
|
||||
//
|
||||
// ) -> None:
|
||||
//
|
||||
// builder_pubkeys = [b.pubkey for b in state.builders]
|
||||
// if pubkey not in builder_pubkeys:
|
||||
// # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||
// if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature):
|
||||
// add_builder_to_registry(state, pubkey, withdrawal_credentials, amount, slot)
|
||||
// else:
|
||||
// # Increase balance by deposit amount
|
||||
// builder_index = builder_pubkeys.index(pubkey)
|
||||
// state.builders[builder_index].balance += amount
|
||||
//
|
||||
// </spec>
|
||||
func applyBuilderDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest) (bool, error) {
|
||||
if beaconState.Version() < version.Gloas {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
pubkey := bytesutil.ToBytes48(request.Pubkey)
|
||||
_, isValidator := beaconState.ValidatorIndexByPubkey(pubkey)
|
||||
idx, isBuilder := beaconState.BuilderIndexByPubkey(pubkey)
|
||||
isBuilderPrefix := helpers.IsBuilderWithdrawalCredential(request.WithdrawalCredentials)
|
||||
if !isBuilder && (!isBuilderPrefix || isValidator) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if isBuilder {
|
||||
if err := beaconState.IncreaseBuilderBalance(idx, request.Amount); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err := applyDepositForNewBuilder(
|
||||
beaconState,
|
||||
request.Pubkey,
|
||||
request.WithdrawalCredentials,
|
||||
request.Amount,
|
||||
request.Signature,
|
||||
); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func applyDepositForNewBuilder(
|
||||
beaconState state.BeaconState,
|
||||
pubkey []byte,
|
||||
withdrawalCredentials []byte,
|
||||
amount uint64,
|
||||
signature []byte,
|
||||
) error {
|
||||
pubkeyBytes := bytesutil.ToBytes48(pubkey)
|
||||
valid, err := helpers.IsValidDepositSignature(ðpb.Deposit_Data{
|
||||
PublicKey: pubkey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Amount: amount,
|
||||
Signature: signature,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not verify deposit signature")
|
||||
}
|
||||
if !valid {
|
||||
log.WithFields(logrus.Fields{
|
||||
"pubkey": fmt.Sprintf("%x", pubkey),
|
||||
}).Warn("ignoring builder deposit: invalid signature")
|
||||
return nil
|
||||
}
|
||||
|
||||
withdrawalCredBytes := bytesutil.ToBytes32(withdrawalCredentials)
|
||||
return beaconState.AddBuilderFromDeposit(pubkeyBytes, withdrawalCredBytes, amount)
|
||||
}
|
||||
150
beacon-chain/core/gloas/deposit_request_test.go
Normal file
150
beacon-chain/core/gloas/deposit_request_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
stateTesting "github.com/OffchainLabs/prysm/v7/beacon-chain/state/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestProcessDepositRequests_EmptyAndNil(t *testing.T) {
|
||||
st := newGloasState(t, nil, nil)
|
||||
|
||||
t.Run("empty requests continues", func(t *testing.T) {
|
||||
err := processDepositRequests(t.Context(), st, []*enginev1.DepositRequest{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("nil request errors", func(t *testing.T) {
|
||||
err := processDepositRequests(t.Context(), st, []*enginev1.DepositRequest{nil})
|
||||
require.ErrorContains(t, "nil deposit request", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessDepositRequest_BuilderDepositAddsBuilder(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
cred := builderWithdrawalCredentials()
|
||||
pd := stateTesting.GeneratePendingDeposit(t, sk, 1234, cred, 0)
|
||||
req := depositRequestFromPending(pd, 1)
|
||||
|
||||
st := newGloasState(t, nil, nil)
|
||||
err = processDepositRequest(st, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
idx, ok := st.BuilderIndexByPubkey(toBytes48(req.Pubkey))
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
builder, err := st.Builder(idx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, builder)
|
||||
require.DeepEqual(t, req.Pubkey, builder.Pubkey)
|
||||
require.DeepEqual(t, []byte{cred[0]}, builder.Version)
|
||||
require.DeepEqual(t, cred[12:], builder.ExecutionAddress)
|
||||
require.Equal(t, uint64(1234), uint64(builder.Balance))
|
||||
require.Equal(t, params.BeaconConfig().FarFutureEpoch, builder.WithdrawableEpoch)
|
||||
|
||||
pending, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(pending))
|
||||
}
|
||||
|
||||
func TestProcessDepositRequest_ExistingBuilderIncreasesBalance(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
pubkey := sk.PublicKey().Marshal()
|
||||
builders := []*ethpb.Builder{
|
||||
{
|
||||
Pubkey: pubkey,
|
||||
Version: []byte{0},
|
||||
ExecutionAddress: bytes.Repeat([]byte{0x11}, 20),
|
||||
Balance: 5,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
},
|
||||
}
|
||||
st := newGloasState(t, nil, builders)
|
||||
|
||||
cred := validatorWithdrawalCredentials()
|
||||
pd := stateTesting.GeneratePendingDeposit(t, sk, 200, cred, 0)
|
||||
req := depositRequestFromPending(pd, 9)
|
||||
|
||||
err = processDepositRequest(st, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
idx, ok := st.BuilderIndexByPubkey(toBytes48(pubkey))
|
||||
require.Equal(t, true, ok)
|
||||
builder, err := st.Builder(idx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(205), uint64(builder.Balance))
|
||||
|
||||
pending, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(pending))
|
||||
}
|
||||
|
||||
func TestApplyDepositForBuilder_InvalidSignatureIgnoresDeposit(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
cred := builderWithdrawalCredentials()
|
||||
st := newGloasState(t, nil, nil)
|
||||
err = applyDepositForNewBuilder(st, sk.PublicKey().Marshal(), cred[:], 100, make([]byte, 96))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := st.BuilderIndexByPubkey(toBytes48(sk.PublicKey().Marshal()))
|
||||
require.Equal(t, false, ok)
|
||||
}
|
||||
|
||||
func newGloasState(t *testing.T, validators []*ethpb.Validator, builders []*ethpb.Builder) state.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
DepositRequestsStartIndex: params.BeaconConfig().UnsetDepositRequestsStartIndex,
|
||||
Validators: validators,
|
||||
Balances: make([]uint64, len(validators)),
|
||||
PendingDeposits: []*ethpb.PendingDeposit{},
|
||||
Builders: builders,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
func depositRequestFromPending(pd *ethpb.PendingDeposit, index uint64) *enginev1.DepositRequest {
|
||||
return &enginev1.DepositRequest{
|
||||
Pubkey: pd.PublicKey,
|
||||
WithdrawalCredentials: pd.WithdrawalCredentials,
|
||||
Amount: pd.Amount,
|
||||
Signature: pd.Signature,
|
||||
Index: index,
|
||||
}
|
||||
}
|
||||
|
||||
func builderWithdrawalCredentials() [32]byte {
|
||||
var cred [32]byte
|
||||
cred[0] = params.BeaconConfig().BuilderWithdrawalPrefixByte
|
||||
copy(cred[12:], bytes.Repeat([]byte{0x22}, 20))
|
||||
return cred
|
||||
}
|
||||
|
||||
func validatorWithdrawalCredentials() [32]byte {
|
||||
var cred [32]byte
|
||||
cred[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
copy(cred[12:], bytes.Repeat([]byte{0x33}, 20))
|
||||
return cred
|
||||
}
|
||||
|
||||
func toBytes48(b []byte) [48]byte {
|
||||
var out [48]byte
|
||||
copy(out[:], b)
|
||||
return out
|
||||
}
|
||||
9
beacon-chain/core/gloas/log.go
Normal file
9
beacon-chain/core/gloas/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Code generated by hack/gen-logs.sh; DO NOT EDIT.
|
||||
// This file is created and regenerated automatically. Anything added here might get removed.
|
||||
package gloas
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
// The prefix for logs from this package will be the text after the last slash in the package path.
|
||||
// If you wish to change this, you should add your desired name in the runtime/logging/logrus-prefixed-formatter/prefix-replacement.go file.
|
||||
var log = logrus.WithField("package", "beacon-chain/core/gloas")
|
||||
377
beacon-chain/core/gloas/payload.go
Normal file
377
beacon-chain/core/gloas/payload.go
Normal file
@@ -0,0 +1,377 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
requests "github.com/OffchainLabs/prysm/v7/beacon-chain/core/requests"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ProcessExecutionPayload processes the signed execution payload envelope for the Gloas fork.
|
||||
//
|
||||
// <spec fn="process_execution_payload" fork="gloas" hash="36bd3af3">
|
||||
// def process_execution_payload(
|
||||
// state: BeaconState,
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// # Removed `body`
|
||||
// # [New in Gloas:EIP7732]
|
||||
// signed_envelope: SignedExecutionPayloadEnvelope,
|
||||
// execution_engine: ExecutionEngine,
|
||||
// # [New in Gloas:EIP7732]
|
||||
// verify: bool = True,
|
||||
// ) -> None:
|
||||
// envelope = signed_envelope.message
|
||||
// payload = envelope.payload
|
||||
//
|
||||
// # Verify signature
|
||||
// if verify:
|
||||
// assert verify_execution_payload_envelope_signature(state, signed_envelope)
|
||||
//
|
||||
// # Cache latest block header state root
|
||||
// previous_state_root = hash_tree_root(state)
|
||||
// if state.latest_block_header.state_root == Root():
|
||||
// state.latest_block_header.state_root = previous_state_root
|
||||
//
|
||||
// # Verify consistency with the beacon block
|
||||
// assert envelope.beacon_block_root == hash_tree_root(state.latest_block_header)
|
||||
// assert envelope.slot == state.slot
|
||||
//
|
||||
// # Verify consistency with the committed bid
|
||||
// committed_bid = state.latest_execution_payload_bid
|
||||
// assert envelope.builder_index == committed_bid.builder_index
|
||||
// assert committed_bid.prev_randao == payload.prev_randao
|
||||
//
|
||||
// # Verify consistency with expected withdrawals
|
||||
// assert hash_tree_root(payload.withdrawals) == hash_tree_root(state.payload_expected_withdrawals)
|
||||
//
|
||||
// # Verify the gas_limit
|
||||
// assert committed_bid.gas_limit == payload.gas_limit
|
||||
// # Verify the block hash
|
||||
// assert committed_bid.block_hash == payload.block_hash
|
||||
// # Verify consistency of the parent hash with respect to the previous execution payload
|
||||
// assert payload.parent_hash == state.latest_block_hash
|
||||
// # Verify timestamp
|
||||
// assert payload.timestamp == compute_time_at_slot(state, state.slot)
|
||||
// # Verify the execution payload is valid
|
||||
// versioned_hashes = [
|
||||
// kzg_commitment_to_versioned_hash(commitment)
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// for commitment in committed_bid.blob_kzg_commitments
|
||||
// ]
|
||||
// requests = envelope.execution_requests
|
||||
// assert execution_engine.verify_and_notify_new_payload(
|
||||
// NewPayloadRequest(
|
||||
// execution_payload=payload,
|
||||
// versioned_hashes=versioned_hashes,
|
||||
// parent_beacon_block_root=state.latest_block_header.parent_root,
|
||||
// execution_requests=requests,
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
|
||||
// for operation in operations:
|
||||
// fn(state, operation)
|
||||
//
|
||||
// for_ops(requests.deposits, process_deposit_request)
|
||||
// for_ops(requests.withdrawals, process_withdrawal_request)
|
||||
// for_ops(requests.consolidations, process_consolidation_request)
|
||||
//
|
||||
// # Queue the builder payment
|
||||
// payment = state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH]
|
||||
// amount = payment.withdrawal.amount
|
||||
// if amount > 0:
|
||||
// state.builder_pending_withdrawals.append(payment.withdrawal)
|
||||
// state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH] = (
|
||||
// BuilderPendingPayment()
|
||||
// )
|
||||
//
|
||||
// # Cache the execution payload hash
|
||||
// state.execution_payload_availability[state.slot % SLOTS_PER_HISTORICAL_ROOT] = 0b1
|
||||
// state.latest_block_hash = payload.block_hash
|
||||
//
|
||||
// # Verify the state root
|
||||
// if verify:
|
||||
// assert envelope.state_root == hash_tree_root(state)
|
||||
// </spec>
|
||||
func ProcessExecutionPayload(
|
||||
ctx context.Context,
|
||||
st state.BeaconState,
|
||||
signedEnvelope interfaces.ROSignedExecutionPayloadEnvelope,
|
||||
) error {
|
||||
if err := VerifyExecutionPayloadEnvelopeSignature(st, signedEnvelope); err != nil {
|
||||
return errors.Wrap(err, "signature verification failed")
|
||||
}
|
||||
|
||||
envelope, err := signedEnvelope.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope from signed envelope")
|
||||
}
|
||||
|
||||
if err := ApplyExecutionPayload(ctx, st, envelope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := st.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get hash tree root")
|
||||
}
|
||||
if r != envelope.StateRoot() {
|
||||
return fmt.Errorf("state root mismatch: expected %#x, got %#x", envelope.StateRoot(), r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyExecutionPayload applies the execution payload envelope to the state and performs the same
|
||||
// consistency checks as the full processing path. This keeps the post-payload state root computation
|
||||
// on a shared code path, even though some bid/payload checks are not strictly required for the root itself.
|
||||
func ApplyExecutionPayload(
|
||||
ctx context.Context,
|
||||
st state.BeaconState,
|
||||
envelope interfaces.ROExecutionPayloadEnvelope,
|
||||
) error {
|
||||
latestHeader := st.LatestBlockHeader()
|
||||
if len(latestHeader.StateRoot) == 0 || bytes.Equal(latestHeader.StateRoot, make([]byte, 32)) {
|
||||
previousStateRoot, err := st.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute state root")
|
||||
}
|
||||
latestHeader.StateRoot = previousStateRoot[:]
|
||||
if err := st.SetLatestBlockHeader(latestHeader); err != nil {
|
||||
return errors.Wrap(err, "could not set latest block header")
|
||||
}
|
||||
}
|
||||
|
||||
blockHeaderRoot, err := latestHeader.HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute block header root")
|
||||
}
|
||||
|
||||
beaconBlockRoot := envelope.BeaconBlockRoot()
|
||||
if !bytes.Equal(beaconBlockRoot[:], blockHeaderRoot[:]) {
|
||||
return errors.Errorf("envelope beacon block root does not match state latest block header root: envelope=%#x, header=%#x", beaconBlockRoot, blockHeaderRoot)
|
||||
}
|
||||
|
||||
if envelope.Slot() != st.Slot() {
|
||||
return errors.Errorf("envelope slot does not match state slot: envelope=%d, state=%d", envelope.Slot(), st.Slot())
|
||||
}
|
||||
|
||||
latestBid, err := st.LatestExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get latest execution payload bid")
|
||||
}
|
||||
if latestBid == nil {
|
||||
return errors.New("latest execution payload bid is nil")
|
||||
}
|
||||
if envelope.BuilderIndex() != latestBid.BuilderIndex() {
|
||||
return errors.Errorf("envelope builder index does not match committed bid builder index: envelope=%d, bid=%d", envelope.BuilderIndex(), latestBid.BuilderIndex())
|
||||
}
|
||||
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get execution payload from envelope")
|
||||
}
|
||||
latestBidPrevRandao := latestBid.PrevRandao()
|
||||
if !bytes.Equal(payload.PrevRandao(), latestBidPrevRandao[:]) {
|
||||
return errors.Errorf("payload prev randao does not match committed bid prev randao: payload=%#x, bid=%#x", payload.PrevRandao(), latestBidPrevRandao)
|
||||
}
|
||||
|
||||
withdrawals, err := payload.Withdrawals()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get withdrawals from payload")
|
||||
}
|
||||
|
||||
ok, err := st.WithdrawalsMatchPayloadExpected(withdrawals)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not validate payload withdrawals")
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("payload withdrawals do not match expected withdrawals")
|
||||
}
|
||||
|
||||
if latestBid.GasLimit() != payload.GasLimit() {
|
||||
return errors.Errorf("committed bid gas limit does not match payload gas limit: bid=%d, payload=%d", latestBid.GasLimit(), payload.GasLimit())
|
||||
}
|
||||
|
||||
bidBlockHash := latestBid.BlockHash()
|
||||
payloadBlockHash := payload.BlockHash()
|
||||
if !bytes.Equal(bidBlockHash[:], payloadBlockHash) {
|
||||
return errors.Errorf("committed bid block hash does not match payload block hash: bid=%#x, payload=%#x", bidBlockHash, payloadBlockHash)
|
||||
}
|
||||
|
||||
latestBlockHash, err := st.LatestBlockHash()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get latest block hash")
|
||||
}
|
||||
if !bytes.Equal(payload.ParentHash(), latestBlockHash[:]) {
|
||||
return errors.Errorf("payload parent hash does not match state latest block hash: payload=%#x, state=%#x", payload.ParentHash(), latestBlockHash)
|
||||
}
|
||||
|
||||
t, err := slots.StartTime(st.GenesisTime(), st.Slot())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute timestamp")
|
||||
}
|
||||
if payload.Timestamp() != uint64(t.Unix()) {
|
||||
return errors.Errorf("payload timestamp does not match expected timestamp: payload=%d, expected=%d", payload.Timestamp(), uint64(t.Unix()))
|
||||
}
|
||||
|
||||
if err := ApplyExecutionPayloadStateMutations(ctx, st, envelope.ExecutionRequests(), [32]byte(payload.BlockHash())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ApplyExecutionPayloadStateMutations(
|
||||
ctx context.Context,
|
||||
st state.BeaconState,
|
||||
executionRequests *enginev1.ExecutionRequests,
|
||||
blockHash [32]byte,
|
||||
) error {
|
||||
if err := processExecutionRequests(ctx, st, executionRequests); err != nil {
|
||||
return errors.Wrap(err, "could not process execution requests")
|
||||
}
|
||||
|
||||
if err := st.QueueBuilderPayment(); err != nil {
|
||||
return errors.Wrap(err, "could not queue builder payment")
|
||||
}
|
||||
|
||||
if err := st.SetExecutionPayloadAvailability(st.Slot(), true); err != nil {
|
||||
return errors.Wrap(err, "could not set execution payload availability")
|
||||
}
|
||||
|
||||
if err := st.SetLatestBlockHash(blockHash); err != nil {
|
||||
return errors.Wrap(err, "could not set latest block hash")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func envelopePublicKey(st state.BeaconState, builderIdx primitives.BuilderIndex) (bls.PublicKey, error) {
|
||||
if builderIdx == params.BeaconConfig().BuilderIndexSelfBuild {
|
||||
return proposerPublicKey(st)
|
||||
}
|
||||
return builderPublicKey(st, builderIdx)
|
||||
}
|
||||
|
||||
func proposerPublicKey(st state.BeaconState) (bls.PublicKey, error) {
|
||||
header := st.LatestBlockHeader()
|
||||
if header == nil {
|
||||
return nil, fmt.Errorf("latest block header is nil")
|
||||
}
|
||||
proposerPubkey := st.PubkeyAtIndex(header.ProposerIndex)
|
||||
publicKey, err := bls.PublicKeyFromBytes(proposerPubkey[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid proposer public key: %w", err)
|
||||
}
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
func builderPublicKey(st state.BeaconState, builderIdx primitives.BuilderIndex) (bls.PublicKey, error) {
|
||||
builder, err := st.Builder(builderIdx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get builder: %w", err)
|
||||
}
|
||||
if builder == nil {
|
||||
return nil, fmt.Errorf("builder at index %d not found", builderIdx)
|
||||
}
|
||||
publicKey, err := bls.PublicKeyFromBytes(builder.Pubkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid builder public key: %w", err)
|
||||
}
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
// processExecutionRequests processes deposits, withdrawals, and consolidations from execution requests.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// for op in requests.deposits: process_deposit_request(state, op)
|
||||
// for op in requests.withdrawals: process_withdrawal_request(state, op)
|
||||
// for op in requests.consolidations: process_consolidation_request(state, op)
|
||||
func processExecutionRequests(ctx context.Context, st state.BeaconState, rqs *enginev1.ExecutionRequests) error {
|
||||
if err := processDepositRequests(ctx, st, rqs.Deposits); err != nil {
|
||||
return errors.Wrap(err, "could not process deposit requests")
|
||||
}
|
||||
|
||||
var err error
|
||||
st, err = requests.ProcessWithdrawalRequests(ctx, st, rqs.Withdrawals)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process withdrawal requests")
|
||||
}
|
||||
err = requests.ProcessConsolidationRequests(ctx, st, rqs.Consolidations)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process consolidation requests")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyExecutionPayloadEnvelopeSignature verifies the BLS signature on a signed execution payload envelope.
|
||||
// <spec fn="verify_execution_payload_envelope_signature" fork="gloas" style="full" hash="49483ae2">
|
||||
// def verify_execution_payload_envelope_signature(
|
||||
//
|
||||
// state: BeaconState, signed_envelope: SignedExecutionPayloadEnvelope
|
||||
//
|
||||
// ) -> bool:
|
||||
//
|
||||
// builder_index = signed_envelope.message.builder_index
|
||||
// if builder_index == BUILDER_INDEX_SELF_BUILD:
|
||||
// validator_index = state.latest_block_header.proposer_index
|
||||
// pubkey = state.validators[validator_index].pubkey
|
||||
// else:
|
||||
// pubkey = state.builders[builder_index].pubkey
|
||||
//
|
||||
// signing_root = compute_signing_root(
|
||||
// signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER)
|
||||
// )
|
||||
// return bls.Verify(pubkey, signing_root, signed_envelope.signature)
|
||||
//
|
||||
// </spec>
|
||||
func VerifyExecutionPayloadEnvelopeSignature(st state.BeaconState, signedEnvelope interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||
envelope, err := signedEnvelope.Envelope()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get envelope: %w", err)
|
||||
}
|
||||
|
||||
builderIdx := envelope.BuilderIndex()
|
||||
publicKey, err := envelopePublicKey(st, builderIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signatureBytes := signedEnvelope.Signature()
|
||||
signature, err := bls.SignatureFromBytes(signatureBytes[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid signature format: %w", err)
|
||||
}
|
||||
|
||||
currentEpoch := slots.ToEpoch(envelope.Slot())
|
||||
domain, err := signing.Domain(
|
||||
st.Fork(),
|
||||
currentEpoch,
|
||||
params.BeaconConfig().DomainBeaconBuilder,
|
||||
st.GenesisValidatorsRoot(),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute signing domain: %w", err)
|
||||
}
|
||||
|
||||
signingRoot, err := signedEnvelope.SigningRoot(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute signing root: %w", err)
|
||||
}
|
||||
|
||||
if !signature.Verify(publicKey, signingRoot[:]) {
|
||||
return fmt.Errorf("signature verification failed: %w", signing.ErrSigFailedToVerify)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -24,14 +24,21 @@ import (
|
||||
)
|
||||
|
||||
// ProcessPayloadAttestations validates payload attestations in a block body.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// process_payload_attestation(state: BeaconState, payload_attestation: PayloadAttestation):
|
||||
//
|
||||
// data = payload_attestation.data
|
||||
// assert data.beacon_block_root == state.latest_block_header.parent_root
|
||||
// assert data.slot + 1 == state.slot
|
||||
// indexed = get_indexed_payload_attestation(state, data.slot, payload_attestation)
|
||||
// assert is_valid_indexed_payload_attestation(state, indexed)
|
||||
// <spec fn="process_payload_attestation" fork="gloas" hash="f46bf0b0">
|
||||
// def process_payload_attestation(
|
||||
// state: BeaconState, payload_attestation: PayloadAttestation
|
||||
// ) -> None:
|
||||
// data = payload_attestation.data
|
||||
//
|
||||
// # Check that the attestation is for the parent beacon block
|
||||
// assert data.beacon_block_root == state.latest_block_header.parent_root
|
||||
// # Check that the attestation is for the previous slot
|
||||
// assert data.slot + 1 == state.slot
|
||||
// # Verify signature
|
||||
// indexed_payload_attestation = get_indexed_payload_attestation(state, payload_attestation)
|
||||
// assert is_valid_indexed_payload_attestation(state, indexed_payload_attestation)
|
||||
// </spec>
|
||||
func ProcessPayloadAttestations(ctx context.Context, st state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) error {
|
||||
atts, err := body.PayloadAttestations()
|
||||
if err != nil {
|
||||
@@ -70,7 +77,7 @@ func ProcessPayloadAttestations(ctx context.Context, st state.BeaconState, body
|
||||
|
||||
// indexedPayloadAttestation converts a payload attestation into its indexed form.
|
||||
func indexedPayloadAttestation(ctx context.Context, st state.ReadOnlyBeaconState, att *eth.PayloadAttestation) (*consensus_types.IndexedPayloadAttestation, error) {
|
||||
committee, err := payloadCommittee(ctx, st, att.Data.Slot)
|
||||
committee, err := PayloadCommittee(ctx, st, att.Data.Slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,19 +96,26 @@ func indexedPayloadAttestation(ctx context.Context, st state.ReadOnlyBeaconState
|
||||
}, nil
|
||||
}
|
||||
|
||||
// payloadCommittee returns the payload timeliness committee for a given slot for the state.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:
|
||||
// PayloadCommittee returns the payload timeliness committee for a given slot for the state.
|
||||
//
|
||||
// epoch = compute_epoch_at_slot(slot)
|
||||
// seed = hash(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(slot))
|
||||
// indices = []
|
||||
// committees_per_slot = get_committee_count_per_slot(state, epoch)
|
||||
// for i in range(committees_per_slot):
|
||||
// committee = get_beacon_committee(state, slot, CommitteeIndex(i))
|
||||
// indices.extend(committee)
|
||||
// return compute_balance_weighted_selection(state, indices, seed, size=PTC_SIZE, shuffle_indices=False)
|
||||
func payloadCommittee(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot) ([]primitives.ValidatorIndex, error) {
|
||||
// <spec fn="get_ptc" fork="gloas" hash="ae15f761">
|
||||
// def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:
|
||||
// """
|
||||
// Get the payload timeliness committee for the given ``slot``.
|
||||
// """
|
||||
// epoch = compute_epoch_at_slot(slot)
|
||||
// seed = hash(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(slot))
|
||||
// indices: List[ValidatorIndex] = []
|
||||
// # Concatenate all committees for this slot in order
|
||||
// committees_per_slot = get_committee_count_per_slot(state, epoch)
|
||||
// for i in range(committees_per_slot):
|
||||
// committee = get_beacon_committee(state, slot, CommitteeIndex(i))
|
||||
// indices.extend(committee)
|
||||
// return compute_balance_weighted_selection(
|
||||
// state, indices, seed, size=PTC_SIZE, shuffle_indices=False
|
||||
// )
|
||||
// </spec>
|
||||
func PayloadCommittee(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot) ([]primitives.ValidatorIndex, error) {
|
||||
epoch := slots.ToEpoch(slot)
|
||||
seed, err := ptcSeed(st, epoch, slot)
|
||||
if err != nil {
|
||||
@@ -114,17 +128,32 @@ func payloadCommittee(ctx context.Context, st state.ReadOnlyBeaconState, slot pr
|
||||
}
|
||||
|
||||
committeesPerSlot := helpers.SlotCommitteeCount(activeCount)
|
||||
out := make([]primitives.ValidatorIndex, 0, activeCount/uint64(params.BeaconConfig().SlotsPerEpoch))
|
||||
|
||||
for i := primitives.CommitteeIndex(0); i < primitives.CommitteeIndex(committeesPerSlot); i++ {
|
||||
committee, err := helpers.BeaconCommitteeFromState(ctx, st, slot, i)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get beacon committee %d", i)
|
||||
selected := make([]primitives.ValidatorIndex, 0, fieldparams.PTCSize)
|
||||
var i uint64
|
||||
for uint64(len(selected)) < fieldparams.PTCSize {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
for committeeIndex := primitives.CommitteeIndex(0); committeeIndex < primitives.CommitteeIndex(committeesPerSlot); committeeIndex++ {
|
||||
if uint64(len(selected)) >= fieldparams.PTCSize {
|
||||
break
|
||||
}
|
||||
|
||||
committee, err := helpers.BeaconCommitteeFromState(ctx, st, slot, committeeIndex)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get beacon committee %d", committeeIndex)
|
||||
}
|
||||
|
||||
selected, i, err = selectByBalanceFill(ctx, st, committee, seed, selected, i)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to sample beacon committee %d", committeeIndex)
|
||||
}
|
||||
}
|
||||
out = append(out, committee...)
|
||||
}
|
||||
|
||||
return selectByBalance(ctx, st, out, seed, fieldparams.PTCSize)
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// ptcSeed computes the seed for the payload timeliness committee.
|
||||
@@ -137,56 +166,87 @@ func ptcSeed(st state.ReadOnlyBeaconState, epoch primitives.Epoch, slot primitiv
|
||||
}
|
||||
|
||||
// selectByBalance selects a balance-weighted subset of input candidates.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// compute_balance_weighted_selection(state, indices, seed, size, shuffle_indices):
|
||||
// Note: shuffle_indices is false for PTC.
|
||||
//
|
||||
// total = len(indices); selected = []; i = 0
|
||||
// while len(selected) < size:
|
||||
// next = i % total
|
||||
// if shuffle_indices: next = compute_shuffled_index(next, total, seed)
|
||||
// if compute_balance_weighted_acceptance(state, indices[next], seed, i):
|
||||
// selected.append(indices[next])
|
||||
// i += 1
|
||||
func selectByBalance(ctx context.Context, st state.ReadOnlyBeaconState, candidates []primitives.ValidatorIndex, seed [32]byte, count uint64) ([]primitives.ValidatorIndex, error) {
|
||||
if len(candidates) == 0 {
|
||||
return nil, errors.New("no candidates for balance weighted selection")
|
||||
}
|
||||
|
||||
// <spec fn="compute_balance_weighted_selection" fork="gloas" hash="2c9f1c23">
|
||||
// def compute_balance_weighted_selection(
|
||||
// state: BeaconState,
|
||||
// indices: Sequence[ValidatorIndex],
|
||||
// seed: Bytes32,
|
||||
// size: uint64,
|
||||
// shuffle_indices: bool,
|
||||
// ) -> Sequence[ValidatorIndex]:
|
||||
// """
|
||||
// Return ``size`` indices sampled by effective balance, using ``indices``
|
||||
// as candidates. If ``shuffle_indices`` is ``True``, candidate indices
|
||||
// are themselves sampled from ``indices`` by shuffling it, otherwise
|
||||
// ``indices`` is traversed in order.
|
||||
// """
|
||||
// total = uint64(len(indices))
|
||||
// assert total > 0
|
||||
// selected: List[ValidatorIndex] = []
|
||||
// i = uint64(0)
|
||||
// while len(selected) < size:
|
||||
// next_index = i % total
|
||||
// if shuffle_indices:
|
||||
// next_index = compute_shuffled_index(next_index, total, seed)
|
||||
// candidate_index = indices[next_index]
|
||||
// if compute_balance_weighted_acceptance(state, candidate_index, seed, i):
|
||||
// selected.append(candidate_index)
|
||||
// i += 1
|
||||
// return selected
|
||||
// </spec>
|
||||
func selectByBalanceFill(
|
||||
ctx context.Context,
|
||||
st state.ReadOnlyBeaconState,
|
||||
candidates []primitives.ValidatorIndex,
|
||||
seed [32]byte,
|
||||
selected []primitives.ValidatorIndex,
|
||||
i uint64,
|
||||
) ([]primitives.ValidatorIndex, uint64, error) {
|
||||
hashFunc := hash.CustomSHA256Hasher()
|
||||
// Pre-allocate buffer for hash input: seed (32 bytes) + round counter (8 bytes).
|
||||
var buf [40]byte
|
||||
copy(buf[:], seed[:])
|
||||
maxBalance := params.BeaconConfig().MaxEffectiveBalanceElectra
|
||||
|
||||
selected := make([]primitives.ValidatorIndex, 0, count)
|
||||
total := uint64(len(candidates))
|
||||
for i := uint64(0); uint64(len(selected)) < count; i++ {
|
||||
for _, idx := range candidates {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
return nil, i, ctx.Err()
|
||||
}
|
||||
idx := candidates[i%total]
|
||||
|
||||
ok, err := acceptByBalance(st, idx, buf[:], hashFunc, maxBalance, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, i, err
|
||||
}
|
||||
if ok {
|
||||
selected = append(selected, idx)
|
||||
}
|
||||
if uint64(len(selected)) == fieldparams.PTCSize {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return selected, nil
|
||||
|
||||
return selected, i, nil
|
||||
}
|
||||
|
||||
// acceptByBalance determines if a validator is accepted based on its effective balance.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// compute_balance_weighted_acceptance(state, index, seed, i):
|
||||
//
|
||||
// MAX_RANDOM_VALUE = 2**16 - 1
|
||||
// random_bytes = hash(seed + uint_to_bytes(i // 16))
|
||||
// offset = i % 16 * 2
|
||||
// random_value = bytes_to_uint64(random_bytes[offset:offset+2])
|
||||
// effective_balance = state.validators[index].effective_balance
|
||||
// return effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value
|
||||
// <spec fn="compute_balance_weighted_acceptance" fork="gloas" hash="9954dcd0">
|
||||
// def compute_balance_weighted_acceptance(
|
||||
// state: BeaconState, index: ValidatorIndex, seed: Bytes32, i: uint64
|
||||
// ) -> bool:
|
||||
// """
|
||||
// Return whether to accept the selection of the validator ``index``, with probability
|
||||
// proportional to its ``effective_balance``, and randomness given by ``seed`` and ``i``.
|
||||
// """
|
||||
// MAX_RANDOM_VALUE = 2**16 - 1
|
||||
// random_bytes = hash(seed + uint_to_bytes(i // 16))
|
||||
// offset = i % 16 * 2
|
||||
// random_value = bytes_to_uint64(random_bytes[offset : offset + 2])
|
||||
// effective_balance = state.validators[index].effective_balance
|
||||
// return effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value
|
||||
// </spec>
|
||||
func acceptByBalance(st state.ReadOnlyBeaconState, idx primitives.ValidatorIndex, seedBuf []byte, hashFunc func([]byte) [32]byte, maxBalance uint64, round uint64) (bool, error) {
|
||||
// Reuse the seed buffer by overwriting the last 8 bytes with the round counter.
|
||||
binary.LittleEndian.PutUint64(seedBuf[len(seedBuf)-8:], round/16)
|
||||
@@ -203,16 +263,26 @@ func acceptByBalance(st state.ReadOnlyBeaconState, idx primitives.ValidatorIndex
|
||||
}
|
||||
|
||||
// validIndexedPayloadAttestation verifies the signature of an indexed payload attestation.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// is_valid_indexed_payload_attestation(state: BeaconState, indexed_payload_attestation: IndexedPayloadAttestation) -> bool:
|
||||
//
|
||||
// indices = indexed_payload_attestation.attesting_indices
|
||||
// return len(indices) > 0 and indices == sorted(indices) and
|
||||
// bls.FastAggregateVerify(
|
||||
// [state.validators[i].pubkey for i in indices],
|
||||
// compute_signing_root(indexed_payload_attestation.data, get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(attestation.data.slot)),
|
||||
// indexed_payload_attestation.signature,
|
||||
// )
|
||||
// <spec fn="is_valid_indexed_payload_attestation" fork="gloas" hash="d76e0f89">
|
||||
// def is_valid_indexed_payload_attestation(
|
||||
// state: BeaconState, attestation: IndexedPayloadAttestation
|
||||
// ) -> bool:
|
||||
// """
|
||||
// Check if ``attestation`` is non-empty, has sorted indices, and has
|
||||
// a valid aggregate signature.
|
||||
// """
|
||||
// # Verify indices are non-empty and sorted
|
||||
// indices = attestation.attesting_indices
|
||||
// if len(indices) == 0 or not indices == sorted(indices):
|
||||
// return False
|
||||
//
|
||||
// # Verify aggregate signature
|
||||
// pubkeys = [state.validators[i].pubkey for i in indices]
|
||||
// domain = get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(attestation.data.slot))
|
||||
// signing_root = compute_signing_root(attestation.data, domain)
|
||||
// return bls.FastAggregateVerify(pubkeys, signing_root, attestation.signature)
|
||||
// </spec>
|
||||
func validIndexedPayloadAttestation(st state.ReadOnlyBeaconState, att *consensus_types.IndexedPayloadAttestation) error {
|
||||
indices := att.AttestingIndices
|
||||
if len(indices) == 0 || !slices.IsSorted(indices) {
|
||||
|
||||
366
beacon-chain/core/gloas/payload_test.go
Normal file
366
beacon-chain/core/gloas/payload_test.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type payloadFixture struct {
|
||||
state state.BeaconState
|
||||
signed interfaces.ROSignedExecutionPayloadEnvelope
|
||||
signedProto *ethpb.SignedExecutionPayloadEnvelope
|
||||
envelope *ethpb.ExecutionPayloadEnvelope
|
||||
payload *enginev1.ExecutionPayloadDeneb
|
||||
slot primitives.Slot
|
||||
}
|
||||
|
||||
func buildPayloadFixture(t *testing.T, mutate func(payload *enginev1.ExecutionPayloadDeneb, bid *ethpb.ExecutionPayloadBid, envelope *ethpb.ExecutionPayloadEnvelope)) payloadFixture {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
slot := primitives.Slot(5)
|
||||
builderIdx := primitives.BuilderIndex(0)
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pk := sk.PublicKey().Marshal()
|
||||
|
||||
randao := bytes.Repeat([]byte{0xAA}, 32)
|
||||
parentHash := bytes.Repeat([]byte{0xBB}, 32)
|
||||
blockHash := bytes.Repeat([]byte{0xCC}, 32)
|
||||
|
||||
withdrawals := []*enginev1.Withdrawal{
|
||||
{Index: 0, ValidatorIndex: 1, Address: bytes.Repeat([]byte{0x01}, 20), Amount: 0},
|
||||
}
|
||||
|
||||
payload := &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: parentHash,
|
||||
FeeRecipient: bytes.Repeat([]byte{0x01}, 20),
|
||||
StateRoot: bytes.Repeat([]byte{0x02}, 32),
|
||||
ReceiptsRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||
LogsBloom: bytes.Repeat([]byte{0x04}, 256),
|
||||
PrevRandao: randao,
|
||||
BlockNumber: 1,
|
||||
GasLimit: 1,
|
||||
GasUsed: 0,
|
||||
Timestamp: 100,
|
||||
ExtraData: []byte{},
|
||||
BaseFeePerGas: bytes.Repeat([]byte{0x05}, 32),
|
||||
BlockHash: blockHash,
|
||||
Transactions: [][]byte{},
|
||||
Withdrawals: withdrawals,
|
||||
BlobGasUsed: 0,
|
||||
ExcessBlobGas: 0,
|
||||
}
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: parentHash,
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xDD}, 32),
|
||||
BlockHash: blockHash,
|
||||
PrevRandao: randao,
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 0,
|
||||
ExecutionPayment: 0,
|
||||
FeeRecipient: bytes.Repeat([]byte{0xEE}, 20),
|
||||
}
|
||||
|
||||
header := ðpb.BeaconBlockHeader{
|
||||
Slot: slot,
|
||||
ParentRoot: bytes.Repeat([]byte{0x11}, 32),
|
||||
StateRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BodyRoot: bytes.Repeat([]byte{0x33}, 32),
|
||||
}
|
||||
headerRoot, err := header.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
envelope := ðpb.ExecutionPayloadEnvelope{
|
||||
Slot: slot,
|
||||
BuilderIndex: builderIdx,
|
||||
BeaconBlockRoot: headerRoot[:],
|
||||
Payload: payload,
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
}
|
||||
|
||||
if mutate != nil {
|
||||
mutate(payload, bid, envelope)
|
||||
}
|
||||
|
||||
genesisRoot := bytes.Repeat([]byte{0xAB}, 32)
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range blockRoots {
|
||||
blockRoots[i] = bytes.Repeat([]byte{0x44}, 32)
|
||||
stateRoots[i] = bytes.Repeat([]byte{0x55}, 32)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = randao
|
||||
}
|
||||
|
||||
withdrawalCreds := make([]byte, 32)
|
||||
withdrawalCreds[0] = cfg.ETH1AddressWithdrawalPrefixByte
|
||||
|
||||
eth1Data := ðpb.Eth1Data{
|
||||
DepositRoot: bytes.Repeat([]byte{0x66}, 32),
|
||||
DepositCount: 0,
|
||||
BlockHash: bytes.Repeat([]byte{0x77}, 32),
|
||||
}
|
||||
|
||||
vals := []*ethpb.Validator{
|
||||
{
|
||||
PublicKey: pk,
|
||||
WithdrawalCredentials: withdrawalCreds,
|
||||
EffectiveBalance: cfg.MinActivationBalance + 1_000,
|
||||
},
|
||||
}
|
||||
balances := []uint64{cfg.MinActivationBalance + 1_000}
|
||||
|
||||
payments := make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2)
|
||||
for i := range payments {
|
||||
payments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
executionPayloadAvailability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||
|
||||
builders := make([]*ethpb.Builder, builderIdx+1)
|
||||
builders[builderIdx] = ðpb.Builder{
|
||||
Pubkey: pk,
|
||||
Version: []byte{0},
|
||||
ExecutionAddress: bytes.Repeat([]byte{0x09}, 20),
|
||||
Balance: 0,
|
||||
DepositEpoch: 0,
|
||||
WithdrawableEpoch: 0,
|
||||
}
|
||||
|
||||
genesisTime := uint64(0)
|
||||
slotSeconds := cfg.SecondsPerSlot * uint64(slot)
|
||||
if payload.Timestamp > slotSeconds {
|
||||
genesisTime = payload.Timestamp - slotSeconds
|
||||
}
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: slot,
|
||||
GenesisTime: genesisTime,
|
||||
GenesisValidatorsRoot: genesisRoot,
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
LatestBlockHeader: header,
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
Eth1Data: eth1Data,
|
||||
Validators: vals,
|
||||
Balances: balances,
|
||||
LatestBlockHash: payload.ParentHash,
|
||||
LatestExecutionPayloadBid: bid,
|
||||
BuilderPendingPayments: payments,
|
||||
ExecutionPayloadAvailability: executionPayloadAvailability,
|
||||
BuilderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
|
||||
PayloadExpectedWithdrawals: payload.Withdrawals,
|
||||
Builders: builders,
|
||||
}
|
||||
|
||||
st, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := st.Copy()
|
||||
ctx := context.Background()
|
||||
require.NoError(t, processExecutionRequests(ctx, expected, envelope.ExecutionRequests))
|
||||
require.NoError(t, expected.QueueBuilderPayment())
|
||||
require.NoError(t, expected.SetExecutionPayloadAvailability(slot, true))
|
||||
var blockHashArr [32]byte
|
||||
copy(blockHashArr[:], payload.BlockHash)
|
||||
require.NoError(t, expected.SetLatestBlockHash(blockHashArr))
|
||||
expectedRoot, err := expected.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
envelope.StateRoot = expectedRoot[:]
|
||||
|
||||
epoch := slots.ToEpoch(slot)
|
||||
domain, err := signing.Domain(st.Fork(), epoch, cfg.DomainBeaconBuilder, st.GenesisValidatorsRoot())
|
||||
require.NoError(t, err)
|
||||
signingRoot, err := signing.ComputeSigningRoot(envelope, domain)
|
||||
require.NoError(t, err)
|
||||
signature := sk.Sign(signingRoot[:]).Marshal()
|
||||
|
||||
signedProto := ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: envelope,
|
||||
Signature: signature,
|
||||
}
|
||||
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
return payloadFixture{
|
||||
state: st,
|
||||
signed: signed,
|
||||
signedProto: signedProto,
|
||||
envelope: envelope,
|
||||
payload: payload,
|
||||
slot: slot,
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayload_Success(t *testing.T) {
|
||||
fixture := buildPayloadFixture(t, nil)
|
||||
require.NoError(t, ProcessExecutionPayload(t.Context(), fixture.state, fixture.signed))
|
||||
|
||||
latestHash, err := fixture.state.LatestBlockHash()
|
||||
require.NoError(t, err)
|
||||
var expectedHash [32]byte
|
||||
copy(expectedHash[:], fixture.payload.BlockHash)
|
||||
require.Equal(t, expectedHash, latestHash)
|
||||
|
||||
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||
paymentIndex := slotsPerEpoch + (fixture.slot % slotsPerEpoch)
|
||||
payments, err := fixture.state.BuilderPendingPayments()
|
||||
require.NoError(t, err)
|
||||
payment := payments[paymentIndex]
|
||||
require.NotNil(t, payment)
|
||||
require.Equal(t, primitives.Gwei(0), payment.Withdrawal.Amount)
|
||||
}
|
||||
|
||||
func TestApplyExecutionPayloadStateMutations_UpdatesAvailabilityAndLatestHash(t *testing.T) {
|
||||
fixture := buildPayloadFixture(t, nil)
|
||||
|
||||
newHash := [32]byte{}
|
||||
newHash[0] = 0x99
|
||||
|
||||
require.NoError(t, ApplyExecutionPayloadStateMutations(t.Context(), fixture.state, fixture.envelope.ExecutionRequests, newHash))
|
||||
|
||||
latestHash, err := fixture.state.LatestBlockHash()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newHash, latestHash)
|
||||
|
||||
available, err := fixture.state.ExecutionPayloadAvailability(fixture.slot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), available)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayload_PrevRandaoMismatch(t *testing.T) {
|
||||
fixture := buildPayloadFixture(t, func(_ *enginev1.ExecutionPayloadDeneb, bid *ethpb.ExecutionPayloadBid, _ *ethpb.ExecutionPayloadEnvelope) {
|
||||
bid.PrevRandao = bytes.Repeat([]byte{0xFF}, 32)
|
||||
})
|
||||
|
||||
err := ProcessExecutionPayload(t.Context(), fixture.state, fixture.signed)
|
||||
require.ErrorContains(t, "prev randao", err)
|
||||
}
|
||||
|
||||
func TestQueueBuilderPayment_ZeroAmountClearsSlot(t *testing.T) {
|
||||
fixture := buildPayloadFixture(t, nil)
|
||||
|
||||
require.NoError(t, fixture.state.QueueBuilderPayment())
|
||||
|
||||
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||
paymentIndex := slotsPerEpoch + (fixture.slot % slotsPerEpoch)
|
||||
payments, err := fixture.state.BuilderPendingPayments()
|
||||
require.NoError(t, err)
|
||||
payment := payments[paymentIndex]
|
||||
require.NotNil(t, payment)
|
||||
require.Equal(t, primitives.Gwei(0), payment.Withdrawal.Amount)
|
||||
}
|
||||
|
||||
func TestVerifyExecutionPayloadEnvelopeSignature(t *testing.T) {
|
||||
fixture := buildPayloadFixture(t, nil)
|
||||
|
||||
t.Run("self build", func(t *testing.T) {
|
||||
proposerSk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
proposerPk := proposerSk.PublicKey().Marshal()
|
||||
|
||||
stPb, ok := fixture.state.ToProtoUnsafe().(*ethpb.BeaconStateGloas)
|
||||
require.Equal(t, true, ok)
|
||||
stPb = proto.Clone(stPb).(*ethpb.BeaconStateGloas)
|
||||
stPb.Validators[0].PublicKey = proposerPk
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(stPb)
|
||||
require.NoError(t, err)
|
||||
|
||||
msg := proto.Clone(fixture.signedProto.Message).(*ethpb.ExecutionPayloadEnvelope)
|
||||
msg.BuilderIndex = params.BeaconConfig().BuilderIndexSelfBuild
|
||||
|
||||
epoch := slots.ToEpoch(msg.Slot)
|
||||
domain, err := signing.Domain(st.Fork(), epoch, params.BeaconConfig().DomainBeaconBuilder, st.GenesisValidatorsRoot())
|
||||
require.NoError(t, err)
|
||||
signingRoot, err := signing.ComputeSigningRoot(msg, domain)
|
||||
require.NoError(t, err)
|
||||
signature := proposerSk.Sign(signingRoot[:]).Marshal()
|
||||
|
||||
signedProto := ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: msg,
|
||||
Signature: signature,
|
||||
}
|
||||
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, VerifyExecutionPayloadEnvelopeSignature(st, signed))
|
||||
})
|
||||
|
||||
t.Run("builder", func(t *testing.T) {
|
||||
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(fixture.signedProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, VerifyExecutionPayloadEnvelopeSignature(fixture.state, signed))
|
||||
})
|
||||
|
||||
t.Run("invalid signature", func(t *testing.T) {
|
||||
t.Run("self build", func(t *testing.T) {
|
||||
proposerSk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
proposerPk := proposerSk.PublicKey().Marshal()
|
||||
|
||||
stPb, ok := fixture.state.ToProtoUnsafe().(*ethpb.BeaconStateGloas)
|
||||
require.Equal(t, true, ok)
|
||||
stPb = proto.Clone(stPb).(*ethpb.BeaconStateGloas)
|
||||
stPb.Validators[0].PublicKey = proposerPk
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(stPb)
|
||||
require.NoError(t, err)
|
||||
|
||||
msg := proto.Clone(fixture.signedProto.Message).(*ethpb.ExecutionPayloadEnvelope)
|
||||
msg.BuilderIndex = params.BeaconConfig().BuilderIndexSelfBuild
|
||||
|
||||
signedProto := ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: msg,
|
||||
Signature: bytes.Repeat([]byte{0xFF}, 96),
|
||||
}
|
||||
badSigned, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = VerifyExecutionPayloadEnvelopeSignature(st, badSigned)
|
||||
require.ErrorContains(t, "invalid signature format", err)
|
||||
})
|
||||
|
||||
t.Run("builder", func(t *testing.T) {
|
||||
signedProto := ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: fixture.signedProto.Message,
|
||||
Signature: bytes.Repeat([]byte{0xFF}, 96),
|
||||
}
|
||||
badSigned, err := blocks.WrappedROSignedExecutionPayloadEnvelope(signedProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = VerifyExecutionPayloadEnvelopeSignature(fixture.state, badSigned)
|
||||
require.ErrorContains(t, "invalid signature format", err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -10,17 +10,21 @@ import (
|
||||
)
|
||||
|
||||
// ProcessBuilderPendingPayments processes the builder pending payments from the previous epoch.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// def process_builder_pending_payments(state: BeaconState) -> None:
|
||||
//
|
||||
// quorum = get_builder_payment_quorum_threshold(state)
|
||||
// for payment in state.builder_pending_payments[:SLOTS_PER_EPOCH]:
|
||||
// if payment.weight >= quorum:
|
||||
// state.builder_pending_withdrawals.append(payment.withdrawal)
|
||||
// <spec fn="process_builder_pending_payments" fork="gloas" hash="10da48dd">
|
||||
// def process_builder_pending_payments(state: BeaconState) -> None:
|
||||
// """
|
||||
// Processes the builder pending payments from the previous epoch.
|
||||
// """
|
||||
// quorum = get_builder_payment_quorum_threshold(state)
|
||||
// for payment in state.builder_pending_payments[:SLOTS_PER_EPOCH]:
|
||||
// if payment.weight >= quorum:
|
||||
// state.builder_pending_withdrawals.append(payment.withdrawal)
|
||||
//
|
||||
// old_payments = state.builder_pending_payments[SLOTS_PER_EPOCH:]
|
||||
// new_payments = [BuilderPendingPayment() for _ in range(SLOTS_PER_EPOCH)]
|
||||
// state.builder_pending_payments = old_payments + new_payments
|
||||
// old_payments = state.builder_pending_payments[SLOTS_PER_EPOCH:]
|
||||
// new_payments = [BuilderPendingPayment() for _ in range(SLOTS_PER_EPOCH)]
|
||||
// state.builder_pending_payments = old_payments + new_payments
|
||||
// </spec>
|
||||
func ProcessBuilderPendingPayments(state state.BeaconState) error {
|
||||
quorum, err := builderQuorumThreshold(state)
|
||||
if err != nil {
|
||||
@@ -53,12 +57,16 @@ func ProcessBuilderPendingPayments(state state.BeaconState) error {
|
||||
}
|
||||
|
||||
// builderQuorumThreshold calculates the quorum threshold for builder payments.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// def get_builder_payment_quorum_threshold(state: BeaconState) -> uint64:
|
||||
//
|
||||
// per_slot_balance = get_total_active_balance(state) // SLOTS_PER_EPOCH
|
||||
// quorum = per_slot_balance * BUILDER_PAYMENT_THRESHOLD_NUMERATOR
|
||||
// return uint64(quorum // BUILDER_PAYMENT_THRESHOLD_DENOMINATOR)
|
||||
// <spec fn="get_builder_payment_quorum_threshold" fork="gloas" hash="a64b7ffb">
|
||||
// def get_builder_payment_quorum_threshold(state: BeaconState) -> uint64:
|
||||
// """
|
||||
// Calculate the quorum threshold for builder payments.
|
||||
// """
|
||||
// per_slot_balance = get_total_active_balance(state) // SLOTS_PER_EPOCH
|
||||
// quorum = per_slot_balance * BUILDER_PAYMENT_THRESHOLD_NUMERATOR
|
||||
// return uint64(quorum // BUILDER_PAYMENT_THRESHOLD_DENOMINATOR)
|
||||
// </spec>
|
||||
func builderQuorumThreshold(state state.ReadOnlyBeaconState) (primitives.Gwei, error) {
|
||||
activeBalance, err := helpers.TotalActiveBalance(state)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,16 +11,20 @@ import (
|
||||
)
|
||||
|
||||
// RemoveBuilderPendingPayment removes the pending builder payment for the proposal slot.
|
||||
// Spec v1.7.0 (pseudocode):
|
||||
//
|
||||
// <spec fn="process_proposer_slashing" fork="gloas" lines="22-32" hash="4da721ef">
|
||||
// # [New in Gloas:EIP7732]
|
||||
// # Remove the BuilderPendingPayment corresponding to
|
||||
// # this proposal if it is still in the 2-epoch window.
|
||||
// slot = header_1.slot
|
||||
// proposal_epoch = compute_epoch_at_slot(slot)
|
||||
// if proposal_epoch == get_current_epoch(state):
|
||||
// payment_index = SLOTS_PER_EPOCH + slot % SLOTS_PER_EPOCH
|
||||
// state.builder_pending_payments[payment_index] = BuilderPendingPayment()
|
||||
// payment_index = SLOTS_PER_EPOCH + slot % SLOTS_PER_EPOCH
|
||||
// state.builder_pending_payments[payment_index] = BuilderPendingPayment()
|
||||
// elif proposal_epoch == get_previous_epoch(state):
|
||||
// payment_index = slot % SLOTS_PER_EPOCH
|
||||
// state.builder_pending_payments[payment_index] = BuilderPendingPayment()
|
||||
// payment_index = slot % SLOTS_PER_EPOCH
|
||||
// state.builder_pending_payments[payment_index] = BuilderPendingPayment()
|
||||
// </spec>
|
||||
func RemoveBuilderPendingPayment(st state.BeaconState, header *eth.BeaconBlockHeader) error {
|
||||
proposalEpoch := slots.ToEpoch(header.Slot)
|
||||
currentEpoch := time.CurrentEpoch(st)
|
||||
|
||||
306
beacon-chain/core/gloas/upgrade.go
Normal file
306
beacon-chain/core/gloas/upgrade.go
Normal file
@@ -0,0 +1,306 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// UpgradeToGloas updates inputs a generic state to return the version Gloas state.
|
||||
//
|
||||
// <spec fn="upgrade_to_gloas" fork="gloas" hash="6e66df25">
|
||||
// def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState:
|
||||
// epoch = fulu.get_current_epoch(pre)
|
||||
//
|
||||
// post = BeaconState(
|
||||
// genesis_time=pre.genesis_time,
|
||||
// genesis_validators_root=pre.genesis_validators_root,
|
||||
// slot=pre.slot,
|
||||
// fork=Fork(
|
||||
// previous_version=pre.fork.current_version,
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// current_version=GLOAS_FORK_VERSION,
|
||||
// epoch=epoch,
|
||||
// ),
|
||||
// latest_block_header=pre.latest_block_header,
|
||||
// block_roots=pre.block_roots,
|
||||
// state_roots=pre.state_roots,
|
||||
// historical_roots=pre.historical_roots,
|
||||
// eth1_data=pre.eth1_data,
|
||||
// eth1_data_votes=pre.eth1_data_votes,
|
||||
// eth1_deposit_index=pre.eth1_deposit_index,
|
||||
// validators=pre.validators,
|
||||
// balances=pre.balances,
|
||||
// randao_mixes=pre.randao_mixes,
|
||||
// slashings=pre.slashings,
|
||||
// previous_epoch_participation=pre.previous_epoch_participation,
|
||||
// current_epoch_participation=pre.current_epoch_participation,
|
||||
// justification_bits=pre.justification_bits,
|
||||
// previous_justified_checkpoint=pre.previous_justified_checkpoint,
|
||||
// current_justified_checkpoint=pre.current_justified_checkpoint,
|
||||
// finalized_checkpoint=pre.finalized_checkpoint,
|
||||
// inactivity_scores=pre.inactivity_scores,
|
||||
// current_sync_committee=pre.current_sync_committee,
|
||||
// next_sync_committee=pre.next_sync_committee,
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// # Removed `latest_execution_payload_header`
|
||||
// # [New in Gloas:EIP7732]
|
||||
// latest_execution_payload_bid=ExecutionPayloadBid(
|
||||
// block_hash=pre.latest_execution_payload_header.block_hash,
|
||||
// ),
|
||||
// next_withdrawal_index=pre.next_withdrawal_index,
|
||||
// next_withdrawal_validator_index=pre.next_withdrawal_validator_index,
|
||||
// historical_summaries=pre.historical_summaries,
|
||||
// deposit_requests_start_index=pre.deposit_requests_start_index,
|
||||
// deposit_balance_to_consume=pre.deposit_balance_to_consume,
|
||||
// exit_balance_to_consume=pre.exit_balance_to_consume,
|
||||
// earliest_exit_epoch=pre.earliest_exit_epoch,
|
||||
// consolidation_balance_to_consume=pre.consolidation_balance_to_consume,
|
||||
// earliest_consolidation_epoch=pre.earliest_consolidation_epoch,
|
||||
// pending_deposits=pre.pending_deposits,
|
||||
// pending_partial_withdrawals=pre.pending_partial_withdrawals,
|
||||
// pending_consolidations=pre.pending_consolidations,
|
||||
// proposer_lookahead=pre.proposer_lookahead,
|
||||
// # [New in Gloas:EIP7732]
|
||||
// builders=[],
|
||||
// # [New in Gloas:EIP7732]
|
||||
// next_withdrawal_builder_index=BuilderIndex(0),
|
||||
// # [New in Gloas:EIP7732]
|
||||
// execution_payload_availability=[0b1 for _ in range(SLOTS_PER_HISTORICAL_ROOT)],
|
||||
// # [New in Gloas:EIP7732]
|
||||
// builder_pending_payments=[BuilderPendingPayment() for _ in range(2 * SLOTS_PER_EPOCH)],
|
||||
// # [New in Gloas:EIP7732]
|
||||
// builder_pending_withdrawals=[],
|
||||
// # [New in Gloas:EIP7732]
|
||||
// latest_block_hash=pre.latest_execution_payload_header.block_hash,
|
||||
// # [New in Gloas:EIP7732]
|
||||
// payload_expected_withdrawals=[],
|
||||
// )
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// onboard_builders_from_pending_deposits(post)
|
||||
//
|
||||
// return post
|
||||
// </spec>
|
||||
//
|
||||
// <spec fn="process_execution_payload_bid" fork="gloas" hash="823c9f3a">
|
||||
// def process_execution_payload_bid(state: BeaconState, block: BeaconBlock) -> None:
|
||||
// signed_bid = block.body.signed_execution_payload_bid
|
||||
// bid = signed_bid.message
|
||||
// builder_index = bid.builder_index
|
||||
// amount = bid.value
|
||||
//
|
||||
// # For self-builds, amount must be zero regardless of withdrawal credential prefix
|
||||
// if builder_index == BUILDER_INDEX_SELF_BUILD:
|
||||
// assert amount == 0
|
||||
// assert signed_bid.signature == bls.G2_POINT_AT_INFINITY
|
||||
// else:
|
||||
// # Verify that the builder is active
|
||||
// assert is_active_builder(state, builder_index)
|
||||
// # Verify that the builder has funds to cover the bid
|
||||
// assert can_builder_cover_bid(state, builder_index, amount)
|
||||
// # Verify that the bid signature is valid
|
||||
// assert verify_execution_payload_bid_signature(state, signed_bid)
|
||||
//
|
||||
// # Verify commitments are under limit
|
||||
// assert (
|
||||
// len(bid.blob_kzg_commitments)
|
||||
// <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block
|
||||
// )
|
||||
//
|
||||
// # Verify that the bid is for the current slot
|
||||
// assert bid.slot == block.slot
|
||||
// # Verify that the bid is for the right parent block
|
||||
// assert bid.parent_block_hash == state.latest_block_hash
|
||||
// assert bid.parent_block_root == block.parent_root
|
||||
// assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
||||
//
|
||||
// # Record the pending payment if there is some payment
|
||||
// if amount > 0:
|
||||
// pending_payment = BuilderPendingPayment(
|
||||
// weight=0,
|
||||
// withdrawal=BuilderPendingWithdrawal(
|
||||
// fee_recipient=bid.fee_recipient,
|
||||
// amount=amount,
|
||||
// builder_index=builder_index,
|
||||
// ),
|
||||
// )
|
||||
// state.builder_pending_payments[SLOTS_PER_EPOCH + bid.slot % SLOTS_PER_EPOCH] = (
|
||||
// pending_payment
|
||||
// )
|
||||
//
|
||||
// # Cache the signed execution payload bid
|
||||
// state.latest_execution_payload_bid = bid
|
||||
// </spec>
|
||||
func UpgradeToGloas(beaconState state.BeaconState) (state.BeaconState, error) {
|
||||
s, err := upgradeToGloas(beaconState)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert to gloas")
|
||||
}
|
||||
if err := s.OnboardBuildersFromPendingDeposits(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to onboard builders from pending deposits")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func upgradeToGloas(beaconState state.BeaconState) (state.BeaconState, error) {
|
||||
currentSyncCommittee, err := beaconState.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nextSyncCommittee, err := beaconState.NextSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prevEpochParticipation, err := beaconState.PreviousEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentEpochParticipation, err := beaconState.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inactivityScores, err := beaconState.InactivityScores()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payloadHeader, err := beaconState.LatestExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wi, err := beaconState.NextWithdrawalIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vi, err := beaconState.NextWithdrawalValidatorIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
summaries, err := beaconState.HistoricalSummaries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depositRequestsStartIndex, err := beaconState.DepositRequestsStartIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depositBalanceToConsume, err := beaconState.DepositBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exitBalanceToConsume, err := beaconState.ExitBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
earliestExitEpoch, err := beaconState.EarliestExitEpoch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
consolidationBalanceToConsume, err := beaconState.ConsolidationBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
earliestConsolidationEpoch, err := beaconState.EarliestConsolidationEpoch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pendingDeposits, err := beaconState.PendingDeposits()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pendingPartialWithdrawals, err := beaconState.PendingPartialWithdrawals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pendingConsolidations, err := beaconState.PendingConsolidations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proposerLookahead, err := beaconState.ProposerLookahead()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proposerLookaheadU64 := make([]uint64, len(proposerLookahead))
|
||||
for i, v := range proposerLookahead {
|
||||
proposerLookaheadU64[i] = uint64(v)
|
||||
}
|
||||
|
||||
executionPayloadAvailability := make([]byte, int((params.BeaconConfig().SlotsPerHistoricalRoot+7)/8))
|
||||
for i := range executionPayloadAvailability {
|
||||
executionPayloadAvailability[i] = 0xff
|
||||
}
|
||||
|
||||
builderPendingPayments := make([]*ethpb.BuilderPendingPayment, int(params.BeaconConfig().SlotsPerEpoch*2))
|
||||
for i := range builderPendingPayments {
|
||||
builderPendingPayments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
s := ðpb.BeaconStateGloas{
|
||||
GenesisTime: uint64(beaconState.GenesisTime().Unix()),
|
||||
GenesisValidatorsRoot: beaconState.GenesisValidatorsRoot(),
|
||||
Slot: beaconState.Slot(),
|
||||
Fork: ðpb.Fork{
|
||||
PreviousVersion: beaconState.Fork().CurrentVersion,
|
||||
CurrentVersion: params.BeaconConfig().GloasForkVersion,
|
||||
Epoch: time.CurrentEpoch(beaconState),
|
||||
},
|
||||
LatestBlockHeader: beaconState.LatestBlockHeader(),
|
||||
BlockRoots: beaconState.BlockRoots(),
|
||||
StateRoots: beaconState.StateRoots(),
|
||||
HistoricalRoots: beaconState.HistoricalRoots(),
|
||||
Eth1Data: beaconState.Eth1Data(),
|
||||
Eth1DataVotes: beaconState.Eth1DataVotes(),
|
||||
Eth1DepositIndex: beaconState.Eth1DepositIndex(),
|
||||
Validators: beaconState.Validators(),
|
||||
Balances: beaconState.Balances(),
|
||||
RandaoMixes: beaconState.RandaoMixes(),
|
||||
Slashings: beaconState.Slashings(),
|
||||
PreviousEpochParticipation: prevEpochParticipation,
|
||||
CurrentEpochParticipation: currentEpochParticipation,
|
||||
JustificationBits: beaconState.JustificationBits(),
|
||||
PreviousJustifiedCheckpoint: beaconState.PreviousJustifiedCheckpoint(),
|
||||
CurrentJustifiedCheckpoint: beaconState.CurrentJustifiedCheckpoint(),
|
||||
FinalizedCheckpoint: beaconState.FinalizedCheckpoint(),
|
||||
InactivityScores: inactivityScores,
|
||||
CurrentSyncCommittee: currentSyncCommittee,
|
||||
NextSyncCommittee: nextSyncCommittee,
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: payloadHeader.BlockHash(),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
ParentBlockHash: make([]byte, fieldparams.RootLength),
|
||||
ParentBlockRoot: make([]byte, fieldparams.RootLength),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
},
|
||||
NextWithdrawalIndex: wi,
|
||||
NextWithdrawalValidatorIndex: vi,
|
||||
HistoricalSummaries: summaries,
|
||||
DepositRequestsStartIndex: depositRequestsStartIndex,
|
||||
DepositBalanceToConsume: depositBalanceToConsume,
|
||||
ExitBalanceToConsume: exitBalanceToConsume,
|
||||
EarliestExitEpoch: earliestExitEpoch,
|
||||
ConsolidationBalanceToConsume: consolidationBalanceToConsume,
|
||||
EarliestConsolidationEpoch: earliestConsolidationEpoch,
|
||||
PendingDeposits: pendingDeposits,
|
||||
PendingPartialWithdrawals: pendingPartialWithdrawals,
|
||||
PendingConsolidations: pendingConsolidations,
|
||||
ProposerLookahead: proposerLookaheadU64,
|
||||
Builders: []*ethpb.Builder{},
|
||||
NextWithdrawalBuilderIndex: primitives.BuilderIndex(0),
|
||||
ExecutionPayloadAvailability: executionPayloadAvailability,
|
||||
BuilderPendingPayments: builderPendingPayments,
|
||||
BuilderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
|
||||
LatestBlockHash: payloadHeader.BlockHash(),
|
||||
PayloadExpectedWithdrawals: []*enginev1.Withdrawal{},
|
||||
}
|
||||
return state_native.InitializeFromProtoUnsafeGloas(s)
|
||||
}
|
||||
177
beacon-chain/core/gloas/upgrade_test.go
Normal file
177
beacon-chain/core/gloas/upgrade_test.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package gloas_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
consensusblocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
)
|
||||
|
||||
func TestUpgradeToGloas_Basic(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateFulu(t, params.BeaconConfig().MaxValidatorsPerCommittee)
|
||||
|
||||
require.NoError(t, st.SetHistoricalRoots([][]byte{{1}}))
|
||||
|
||||
lookaheadSize := int(params.BeaconConfig().MinSeedLookahead+1) * int(params.BeaconConfig().SlotsPerEpoch)
|
||||
lookahead := make([]primitives.ValidatorIndex, lookaheadSize)
|
||||
for i := range lookahead {
|
||||
lookahead[i] = primitives.ValidatorIndex(i)
|
||||
}
|
||||
require.NoError(t, st.SetProposerLookahead(lookahead))
|
||||
|
||||
require.NoError(t, st.SetPendingPartialWithdrawals([]*ethpb.PendingPartialWithdrawal{{Index: 1, Amount: 2}}))
|
||||
require.NoError(t, st.SetPendingConsolidations([]*ethpb.PendingConsolidation{{SourceIndex: 3, TargetIndex: 4}}))
|
||||
|
||||
blockHash := bytes.Repeat([]byte{0xAB}, 32)
|
||||
header := &enginev1.ExecutionPayloadHeaderDeneb{BlockHash: blockHash}
|
||||
wrappedHeader, err := consensusblocks.WrappedExecutionPayloadHeaderDeneb(header)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetLatestExecutionPayloadHeader(wrappedHeader))
|
||||
|
||||
preForkState := st.Copy()
|
||||
mSt, err := gloas.UpgradeToGloas(st)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, preForkState.GenesisTime(), mSt.GenesisTime())
|
||||
require.DeepSSZEqual(t, preForkState.GenesisValidatorsRoot(), mSt.GenesisValidatorsRoot())
|
||||
require.Equal(t, preForkState.Slot(), mSt.Slot())
|
||||
|
||||
require.DeepSSZEqual(t, ðpb.Fork{
|
||||
PreviousVersion: st.Fork().CurrentVersion,
|
||||
CurrentVersion: params.BeaconConfig().GloasForkVersion,
|
||||
Epoch: time.CurrentEpoch(st),
|
||||
}, mSt.Fork())
|
||||
|
||||
bid, err := mSt.LatestExecutionPayloadBid()
|
||||
require.NoError(t, err)
|
||||
wantBlockHash := [32]byte{}
|
||||
copy(wantBlockHash[:], blockHash)
|
||||
require.DeepSSZEqual(t, wantBlockHash, bid.BlockHash())
|
||||
require.DeepSSZEqual(t, [20]byte{}, bid.FeeRecipient())
|
||||
require.DeepSSZEqual(t, [32]byte{}, bid.ParentBlockHash())
|
||||
require.DeepSSZEqual(t, [32]byte{}, bid.ParentBlockRoot())
|
||||
require.DeepSSZEqual(t, [32]byte{}, bid.PrevRandao())
|
||||
|
||||
latestBlockHash, err := mSt.LatestBlockHash()
|
||||
require.NoError(t, err)
|
||||
require.DeepSSZEqual(t, blockHash, latestBlockHash[:])
|
||||
|
||||
pbState, ok := mSt.ToProtoUnsafe().(*ethpb.BeaconStateGloas)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
expectedAvailLen := int((params.BeaconConfig().SlotsPerHistoricalRoot + 7) / 8)
|
||||
require.Equal(t, expectedAvailLen, len(pbState.ExecutionPayloadAvailability))
|
||||
for _, b := range pbState.ExecutionPayloadAvailability {
|
||||
require.Equal(t, byte(0xff), b)
|
||||
}
|
||||
|
||||
require.Equal(t, 0, len(pbState.Builders))
|
||||
require.Equal(t, primitives.BuilderIndex(0), pbState.NextWithdrawalBuilderIndex)
|
||||
require.Equal(t, 0, len(pbState.BuilderPendingWithdrawals))
|
||||
require.Equal(t, 0, len(pbState.PayloadExpectedWithdrawals))
|
||||
|
||||
require.Equal(t, int(params.BeaconConfig().SlotsPerEpoch*2), len(pbState.BuilderPendingPayments))
|
||||
for _, payment := range pbState.BuilderPendingPayments {
|
||||
require.NotNil(t, payment)
|
||||
require.NotNil(t, payment.Withdrawal)
|
||||
require.Equal(t, fieldparams.FeeRecipientLength, len(payment.Withdrawal.FeeRecipient))
|
||||
}
|
||||
|
||||
ppw, err := mSt.PendingPartialWithdrawals()
|
||||
require.NoError(t, err)
|
||||
prePPW, err := preForkState.PendingPartialWithdrawals()
|
||||
require.NoError(t, err)
|
||||
require.DeepSSZEqual(t, prePPW, ppw)
|
||||
|
||||
pc, err := mSt.PendingConsolidations()
|
||||
require.NoError(t, err)
|
||||
prePC, err := preForkState.PendingConsolidations()
|
||||
require.NoError(t, err)
|
||||
require.DeepSSZEqual(t, prePC, pc)
|
||||
}
|
||||
|
||||
func TestUpgradeToGloas_OnboardsBuilderDeposit(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateFulu(t, 4)
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
builderCreds := builderWithdrawalCredentials(0xDD)
|
||||
amount := uint64(1234)
|
||||
depSlot := primitives.Slot(params.BeaconConfig().SlotsPerEpoch*2 + 3)
|
||||
deposit := newPendingDeposit(t, sk, builderCreds, amount, depSlot, true)
|
||||
|
||||
require.NoError(t, st.SetPendingDeposits([]*ethpb.PendingDeposit{deposit}))
|
||||
|
||||
mSt, err := gloas.UpgradeToGloas(st)
|
||||
require.NoError(t, err)
|
||||
|
||||
pbState, ok := mSt.ToProtoUnsafe().(*ethpb.BeaconStateGloas)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
require.Equal(t, 0, len(pbState.PendingDeposits))
|
||||
require.Equal(t, 1, len(pbState.Builders))
|
||||
|
||||
builder := pbState.Builders[0]
|
||||
require.DeepSSZEqual(t, sk.PublicKey().Marshal(), builder.Pubkey)
|
||||
require.DeepSSZEqual(t, builderCreds[12:], builder.ExecutionAddress)
|
||||
require.Equal(t, primitives.Gwei(amount), builder.Balance)
|
||||
require.Equal(t, slots.ToEpoch(depSlot), builder.DepositEpoch)
|
||||
}
|
||||
|
||||
func builderWithdrawalCredentials(addrByte byte) []byte {
|
||||
wc := make([]byte, fieldparams.RootLength)
|
||||
wc[0] = params.BeaconConfig().BuilderWithdrawalPrefixByte
|
||||
for i := 12; i < len(wc); i++ {
|
||||
wc[i] = addrByte
|
||||
}
|
||||
return wc
|
||||
}
|
||||
|
||||
func newPendingDeposit(
|
||||
t *testing.T,
|
||||
sk bls.SecretKey,
|
||||
withdrawalCredentials []byte,
|
||||
amount uint64,
|
||||
slot primitives.Slot,
|
||||
valid bool,
|
||||
) *ethpb.PendingDeposit {
|
||||
t.Helper()
|
||||
signature := make([]byte, fieldparams.BLSSignatureLength)
|
||||
if valid {
|
||||
signature = signDeposit(t, sk, withdrawalCredentials, amount)
|
||||
}
|
||||
return ðpb.PendingDeposit{
|
||||
PublicKey: sk.PublicKey().Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Amount: amount,
|
||||
Signature: signature,
|
||||
Slot: slot,
|
||||
}
|
||||
}
|
||||
|
||||
func signDeposit(t *testing.T, sk bls.SecretKey, withdrawalCredentials []byte, amount uint64) []byte {
|
||||
t.Helper()
|
||||
domain, err := signing.ComputeDomain(params.BeaconConfig().DomainDeposit, nil, nil)
|
||||
require.NoError(t, err)
|
||||
msg := ðpb.DepositMessage{
|
||||
PublicKey: sk.PublicKey().Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Amount: amount,
|
||||
}
|
||||
signingRoot, err := signing.ComputeSigningRoot(msg, domain)
|
||||
require.NoError(t, err)
|
||||
sig := sk.Sign(signingRoot[:])
|
||||
return sig.Marshal()
|
||||
}
|
||||
105
beacon-chain/core/gloas/withdrawals.go
Normal file
105
beacon-chain/core/gloas/withdrawals.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ProcessWithdrawals applies withdrawals to the state for Gloas.
|
||||
//
|
||||
// <spec fn="process_withdrawals" fork="gloas" hash="16d9ad2a">
|
||||
// def process_withdrawals(
|
||||
//
|
||||
// state: BeaconState,
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// # Removed `payload`
|
||||
//
|
||||
// ) -> None:
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// # Return early if the parent block is empty
|
||||
// if not is_parent_block_full(state):
|
||||
// return
|
||||
//
|
||||
// # Get expected withdrawals
|
||||
// expected = get_expected_withdrawals(state)
|
||||
//
|
||||
// # Apply expected withdrawals
|
||||
// apply_withdrawals(state, expected.withdrawals)
|
||||
//
|
||||
// # Update withdrawals fields in the state
|
||||
// update_next_withdrawal_index(state, expected.withdrawals)
|
||||
// # [New in Gloas:EIP7732]
|
||||
// update_payload_expected_withdrawals(state, expected.withdrawals)
|
||||
// # [New in Gloas:EIP7732]
|
||||
// update_builder_pending_withdrawals(state, expected.processed_builder_withdrawals_count)
|
||||
// update_pending_partial_withdrawals(state, expected.processed_partial_withdrawals_count)
|
||||
// # [New in Gloas:EIP7732]
|
||||
// update_next_withdrawal_builder_index(state, expected.processed_builders_sweep_count)
|
||||
// update_next_withdrawal_validator_index(state, expected.withdrawals)
|
||||
//
|
||||
// </spec>
|
||||
func ProcessWithdrawals(st state.BeaconState) error {
|
||||
// Must be called before ProcessExecutionPayloadBid for the current block.
|
||||
full, err := st.IsParentBlockFull()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get parent block full status")
|
||||
}
|
||||
if !full {
|
||||
return nil
|
||||
}
|
||||
|
||||
expected, err := st.ExpectedWithdrawalsGloas()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get expected withdrawals")
|
||||
}
|
||||
|
||||
if err := st.DecreaseWithdrawalBalances(expected.Withdrawals); err != nil {
|
||||
return errors.Wrap(err, "could not decrease withdrawal balances")
|
||||
}
|
||||
|
||||
if len(expected.Withdrawals) > 0 {
|
||||
if err := st.SetNextWithdrawalIndex(expected.Withdrawals[len(expected.Withdrawals)-1].Index + 1); err != nil {
|
||||
return errors.Wrap(err, "could not set next withdrawal index")
|
||||
}
|
||||
}
|
||||
|
||||
if err := st.SetPayloadExpectedWithdrawals(expected.Withdrawals); err != nil {
|
||||
return errors.Wrap(err, "could not set payload expected withdrawals")
|
||||
}
|
||||
|
||||
if err := st.DequeueBuilderPendingWithdrawals(expected.ProcessedBuilderWithdrawalsCount); err != nil {
|
||||
return errors.Wrap(err, "unable to dequeue builder pending withdrawals from state")
|
||||
}
|
||||
|
||||
if err := st.DequeuePendingPartialWithdrawals(expected.ProcessedPartialWithdrawalsCount); err != nil {
|
||||
return errors.Wrap(err, "unable to dequeue partial withdrawals from state")
|
||||
}
|
||||
|
||||
err = st.SetNextWithdrawalBuilderIndex(expected.NextWithdrawalBuilderIndex)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not set next withdrawal builder index")
|
||||
}
|
||||
|
||||
var nextValidatorIndex primitives.ValidatorIndex
|
||||
if uint64(len(expected.Withdrawals)) < params.BeaconConfig().MaxWithdrawalsPerPayload {
|
||||
nextValidatorIndex, err = st.NextWithdrawalValidatorIndex()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get next withdrawal validator index")
|
||||
}
|
||||
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
|
||||
nextValidatorIndex = nextValidatorIndex % primitives.ValidatorIndex(st.NumValidators())
|
||||
} else {
|
||||
nextValidatorIndex = expected.Withdrawals[len(expected.Withdrawals)-1].ValidatorIndex + 1
|
||||
if nextValidatorIndex == primitives.ValidatorIndex(st.NumValidators()) {
|
||||
nextValidatorIndex = 0
|
||||
}
|
||||
}
|
||||
if err := st.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil {
|
||||
return errors.Wrap(err, "could not set next withdrawal validator index")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
388
beacon-chain/core/gloas/withdrawals_test.go
Normal file
388
beacon-chain/core/gloas/withdrawals_test.go
Normal file
@@ -0,0 +1,388 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestProcessWithdrawals(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
build func(t *testing.T) *withdrawalsState
|
||||
check func(t *testing.T, st *withdrawalsState)
|
||||
}{
|
||||
{
|
||||
name: "parent block not full",
|
||||
build: func(t *testing.T) *withdrawalsState {
|
||||
return &withdrawalsState{
|
||||
BeaconState: newGloasState(t, nil, nil),
|
||||
parentFull: false,
|
||||
}
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, false, st.expectedCalled)
|
||||
require.Equal(t, false, st.decreaseCalled)
|
||||
require.Equal(t, false, st.setNextWithdrawalIndexCalled)
|
||||
require.Equal(t, false, st.setPayloadExpectedWithdrawalsCalled)
|
||||
require.Equal(t, false, st.dequeueBuilderCalled)
|
||||
require.Equal(t, false, st.dequeuePartialCalled)
|
||||
require.Equal(t, false, st.setNextBuilderIndexCalled)
|
||||
require.Equal(t, false, st.nextValidatorIndexCalled)
|
||||
require.Equal(t, false, st.setNextValidatorIndexCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "updates indexes when not full payload",
|
||||
build: func(t *testing.T) *withdrawalsState {
|
||||
return &withdrawalsState{
|
||||
BeaconState: newGloasState(t, nil, nil),
|
||||
parentFull: true,
|
||||
numValidators: 10,
|
||||
nextValidatorIndex: 3,
|
||||
expectedResult: state.ExpectedWithdrawalsGloasResult{
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
{Index: 7, ValidatorIndex: 2, Amount: 1, Address: []byte{0x01}},
|
||||
{Index: 8, ValidatorIndex: 4, Amount: 2, Address: []byte{0x02}},
|
||||
},
|
||||
ProcessedBuilderWithdrawalsCount: 5,
|
||||
ProcessedPartialWithdrawalsCount: 2,
|
||||
NextWithdrawalBuilderIndex: 7,
|
||||
},
|
||||
}
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.expectedCalled)
|
||||
require.Equal(t, true, st.decreaseCalled)
|
||||
require.NotNil(t, st.setNextWithdrawalIndexArg)
|
||||
require.Equal(t, uint64(9), *st.setNextWithdrawalIndexArg)
|
||||
require.DeepEqual(t, st.expectedResult.Withdrawals, st.setPayloadExpectedWithdrawalsArg)
|
||||
require.Equal(t, uint64(5), *st.dequeueBuilderArg)
|
||||
require.Equal(t, uint64(2), *st.dequeuePartialArg)
|
||||
require.Equal(t, primitives.BuilderIndex(7), *st.setNextBuilderIndexArg)
|
||||
require.Equal(t, true, st.nextValidatorIndexCalled)
|
||||
|
||||
expectedNext := (uint64(st.nextValidatorIndex) + uint64(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)) % st.numValidators
|
||||
require.Equal(t, primitives.ValidatorIndex(expectedNext), *st.setNextValidatorIndexArg)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "full payload uses last validator index",
|
||||
build: func(t *testing.T) *withdrawalsState {
|
||||
max := int(params.BeaconConfig().MaxWithdrawalsPerPayload)
|
||||
withdrawals := make([]*enginev1.Withdrawal, max)
|
||||
for i := range max {
|
||||
withdrawals[i] = &enginev1.Withdrawal{
|
||||
Index: uint64(i),
|
||||
ValidatorIndex: 0,
|
||||
Amount: 1,
|
||||
Address: []byte{0x03},
|
||||
}
|
||||
}
|
||||
withdrawals[max-1].ValidatorIndex = 4
|
||||
|
||||
return &withdrawalsState{
|
||||
BeaconState: newGloasState(t, nil, nil),
|
||||
parentFull: true,
|
||||
numValidators: 5,
|
||||
expectedResult: state.ExpectedWithdrawalsGloasResult{
|
||||
Withdrawals: withdrawals,
|
||||
NextWithdrawalBuilderIndex: 1,
|
||||
},
|
||||
}
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
max := int(params.BeaconConfig().MaxWithdrawalsPerPayload)
|
||||
require.NotNil(t, st.setNextWithdrawalIndexArg)
|
||||
require.Equal(t, uint64(max), *st.setNextWithdrawalIndexArg)
|
||||
require.Equal(t, false, st.nextValidatorIndexCalled)
|
||||
require.Equal(t, primitives.ValidatorIndex(0), *st.setNextValidatorIndexArg)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty withdrawals skips next index update",
|
||||
build: func(t *testing.T) *withdrawalsState {
|
||||
return &withdrawalsState{
|
||||
BeaconState: newGloasState(t, nil, nil),
|
||||
parentFull: true,
|
||||
numValidators: 8,
|
||||
expectedResult: state.ExpectedWithdrawalsGloasResult{
|
||||
Withdrawals: []*enginev1.Withdrawal{},
|
||||
ProcessedBuilderWithdrawalsCount: 1,
|
||||
ProcessedPartialWithdrawalsCount: 2,
|
||||
NextWithdrawalBuilderIndex: 4,
|
||||
},
|
||||
}
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, false, st.setNextWithdrawalIndexCalled)
|
||||
require.Equal(t, true, st.setPayloadExpectedWithdrawalsCalled)
|
||||
require.Equal(t, true, st.dequeueBuilderCalled)
|
||||
require.Equal(t, true, st.dequeuePartialCalled)
|
||||
require.Equal(t, true, st.setNextBuilderIndexCalled)
|
||||
require.Equal(t, true, st.nextValidatorIndexCalled)
|
||||
require.Equal(t, true, st.setNextValidatorIndexCalled)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
st := tc.build(t)
|
||||
require.NoError(t, ProcessWithdrawals(st))
|
||||
if tc.check != nil {
|
||||
tc.check(t, st)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessWithdrawals_ErrorPaths(t *testing.T) {
|
||||
base := func(t *testing.T) *withdrawalsState {
|
||||
return &withdrawalsState{
|
||||
BeaconState: newGloasState(t, nil, nil),
|
||||
parentFull: true,
|
||||
numValidators: 16,
|
||||
expectedResult: state.ExpectedWithdrawalsGloasResult{
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
{Index: 1, ValidatorIndex: 2, Amount: 1, Address: []byte{0x01}},
|
||||
},
|
||||
ProcessedBuilderWithdrawalsCount: 1,
|
||||
ProcessedPartialWithdrawalsCount: 1,
|
||||
NextWithdrawalBuilderIndex: 2,
|
||||
},
|
||||
nextValidatorIndex: 5,
|
||||
}
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
err error
|
||||
set func(st *withdrawalsState, err error)
|
||||
check func(t *testing.T, st *withdrawalsState)
|
||||
}{
|
||||
{
|
||||
name: "parent block full error",
|
||||
err: errors.New("parent err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.parentErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, false, st.expectedCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expected withdrawals error",
|
||||
err: errors.New("expected err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.expectedErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.expectedCalled)
|
||||
require.Equal(t, false, st.decreaseCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "decrease balances error",
|
||||
err: errors.New("decrease err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.decreaseErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.decreaseCalled)
|
||||
require.Equal(t, false, st.setNextWithdrawalIndexCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set next withdrawal index error",
|
||||
err: errors.New("next index err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.setNextWithdrawalIndexErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.setNextWithdrawalIndexCalled)
|
||||
require.Equal(t, false, st.setPayloadExpectedWithdrawalsCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set payload expected withdrawals error",
|
||||
err: errors.New("payload expected err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.setPayloadExpectedWithdrawalsErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.setPayloadExpectedWithdrawalsCalled)
|
||||
require.Equal(t, false, st.dequeueBuilderCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dequeue builder pending withdrawals error",
|
||||
err: errors.New("dequeue builder err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.dequeueBuilderErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.dequeueBuilderCalled)
|
||||
require.Equal(t, false, st.dequeuePartialCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dequeue pending partial withdrawals error",
|
||||
err: errors.New("dequeue partial err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.dequeuePartialErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.dequeuePartialCalled)
|
||||
require.Equal(t, false, st.setNextBuilderIndexCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set next withdrawal builder index error",
|
||||
err: errors.New("next builder err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.setNextBuilderIndexErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.setNextBuilderIndexCalled)
|
||||
require.Equal(t, false, st.nextValidatorIndexCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "next withdrawal validator index error",
|
||||
err: errors.New("next validator err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.nextValidatorIndexErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.nextValidatorIndexCalled)
|
||||
require.Equal(t, false, st.setNextValidatorIndexCalled)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set next withdrawal validator index error",
|
||||
err: errors.New("set next validator err"),
|
||||
set: func(st *withdrawalsState, err error) {
|
||||
st.setNextValidatorIndexErr = err
|
||||
},
|
||||
check: func(t *testing.T, st *withdrawalsState) {
|
||||
require.Equal(t, true, st.setNextValidatorIndexCalled)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
st := base(t)
|
||||
tc.set(st, tc.err)
|
||||
err := ProcessWithdrawals(st)
|
||||
require.ErrorIs(t, err, tc.err)
|
||||
if tc.check != nil {
|
||||
tc.check(t, st)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type withdrawalsState struct {
|
||||
setNextValidatorIndexCalled bool
|
||||
nextValidatorIndexCalled bool
|
||||
setNextBuilderIndexCalled bool
|
||||
dequeuePartialCalled bool
|
||||
dequeueBuilderCalled bool
|
||||
setPayloadExpectedWithdrawalsCalled bool
|
||||
setNextWithdrawalIndexCalled bool
|
||||
parentFull bool
|
||||
expectedCalled bool
|
||||
decreaseCalled bool
|
||||
numValidators uint64
|
||||
setNextWithdrawalIndexArg *uint64
|
||||
nextValidatorIndex primitives.ValidatorIndex
|
||||
setNextBuilderIndexArg *primitives.BuilderIndex
|
||||
dequeuePartialArg *uint64
|
||||
setNextValidatorIndexArg *primitives.ValidatorIndex
|
||||
dequeueBuilderArg *uint64
|
||||
state.BeaconState
|
||||
setNextValidatorIndexErr error
|
||||
setNextBuilderIndexErr error
|
||||
dequeuePartialErr error
|
||||
dequeueBuilderErr error
|
||||
setPayloadExpectedWithdrawalsErr error
|
||||
nextValidatorIndexErr error
|
||||
decreaseErr error
|
||||
expectedErr error
|
||||
parentErr error
|
||||
setNextWithdrawalIndexErr error
|
||||
setPayloadExpectedWithdrawalsArg []*enginev1.Withdrawal
|
||||
expectedResult state.ExpectedWithdrawalsGloasResult
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) IsParentBlockFull() (bool, error) {
|
||||
return w.parentFull, w.parentErr
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) ExpectedWithdrawalsGloas() (state.ExpectedWithdrawalsGloasResult, error) {
|
||||
w.expectedCalled = true
|
||||
if w.expectedErr != nil {
|
||||
return state.ExpectedWithdrawalsGloasResult{}, w.expectedErr
|
||||
}
|
||||
return w.expectedResult, nil
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) DecreaseWithdrawalBalances(_ []*enginev1.Withdrawal) error {
|
||||
w.decreaseCalled = true
|
||||
return w.decreaseErr
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) SetNextWithdrawalIndex(index uint64) error {
|
||||
w.setNextWithdrawalIndexCalled = true
|
||||
w.setNextWithdrawalIndexArg = &index
|
||||
return w.setNextWithdrawalIndexErr
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) SetPayloadExpectedWithdrawals(withdrawals []*enginev1.Withdrawal) error {
|
||||
w.setPayloadExpectedWithdrawalsCalled = true
|
||||
w.setPayloadExpectedWithdrawalsArg = withdrawals
|
||||
return w.setPayloadExpectedWithdrawalsErr
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) DequeueBuilderPendingWithdrawals(n uint64) error {
|
||||
w.dequeueBuilderCalled = true
|
||||
w.dequeueBuilderArg = &n
|
||||
return w.dequeueBuilderErr
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) DequeuePendingPartialWithdrawals(n uint64) error {
|
||||
w.dequeuePartialCalled = true
|
||||
w.dequeuePartialArg = &n
|
||||
return w.dequeuePartialErr
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) SetNextWithdrawalBuilderIndex(index primitives.BuilderIndex) error {
|
||||
w.setNextBuilderIndexCalled = true
|
||||
w.setNextBuilderIndexArg = &index
|
||||
return w.setNextBuilderIndexErr
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) NextWithdrawalValidatorIndex() (primitives.ValidatorIndex, error) {
|
||||
w.nextValidatorIndexCalled = true
|
||||
if w.nextValidatorIndexErr != nil {
|
||||
return 0, w.nextValidatorIndexErr
|
||||
}
|
||||
return w.nextValidatorIndex, nil
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) NumValidators() int {
|
||||
return int(w.numValidators)
|
||||
}
|
||||
|
||||
func (w *withdrawalsState) SetNextWithdrawalValidatorIndex(index primitives.ValidatorIndex) error {
|
||||
w.setNextValidatorIndexCalled = true
|
||||
w.setNextValidatorIndexArg = &index
|
||||
return w.setNextValidatorIndexErr
|
||||
}
|
||||
@@ -6,6 +6,7 @@ go_library(
|
||||
"attestation.go",
|
||||
"beacon_committee.go",
|
||||
"block.go",
|
||||
"builder.go",
|
||||
"deposit.go",
|
||||
"genesis.go",
|
||||
"legacy.go",
|
||||
|
||||
12
beacon-chain/core/helpers/builder.go
Normal file
12
beacon-chain/core/helpers/builder.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
)
|
||||
|
||||
// IsBuilderWithdrawalCredential returns true if the withdrawal credentials indicate a builder.
|
||||
func IsBuilderWithdrawalCredential(withdrawalCredentials []byte) bool {
|
||||
return len(withdrawalCredentials) == fieldparams.RootLength &&
|
||||
withdrawalCredentials[0] == params.BeaconConfig().BuilderWithdrawalPrefixByte
|
||||
}
|
||||
@@ -73,23 +73,22 @@ func PopulateFromSidecar(sidecar blocks.VerifiedRODataColumn) *SidecarReconstruc
|
||||
|
||||
// ValidatorsCustodyRequirement returns the number of custody groups regarding the validator indices attached to the beacon node.
|
||||
// https://github.com/ethereum/consensus-specs/blob/master/specs/fulu/validator.md#validator-custody
|
||||
func ValidatorsCustodyRequirement(state beaconState.ReadOnlyBeaconState, validatorsIndex map[primitives.ValidatorIndex]bool) (uint64, error) {
|
||||
totalNodeBalance := uint64(0)
|
||||
func ValidatorsCustodyRequirement(st beaconState.ReadOnlyBalances, validatorsIndex map[primitives.ValidatorIndex]bool) (uint64, error) {
|
||||
cfg := params.BeaconConfig()
|
||||
idxs := make([]primitives.ValidatorIndex, 0, len(validatorsIndex))
|
||||
for index := range validatorsIndex {
|
||||
validator, err := state.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "validator at index %v", index)
|
||||
}
|
||||
|
||||
totalNodeBalance += validator.EffectiveBalance()
|
||||
idxs = append(idxs, index)
|
||||
}
|
||||
totalBalance, err := st.EffectiveBalanceSum(idxs)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "effective balances")
|
||||
}
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
numberOfCustodyGroups := cfg.NumberOfCustodyGroups
|
||||
validatorCustodyRequirement := cfg.ValidatorCustodyRequirement
|
||||
balancePerAdditionalCustodyGroup := cfg.BalancePerAdditionalCustodyGroup
|
||||
|
||||
count := totalNodeBalance / balancePerAdditionalCustodyGroup
|
||||
count := totalBalance / balancePerAdditionalCustodyGroup
|
||||
return min(max(count, validatorCustodyRequirement), numberOfCustodyGroups), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,15 @@ func CanUpgradeToFulu(slot primitives.Slot) bool {
|
||||
return epochStart && fuluEpoch
|
||||
}
|
||||
|
||||
// CanUpgradeToGloas returns true if the input `slot` can upgrade to Gloas.
|
||||
// Spec code:
|
||||
// If state.slot % SLOTS_PER_EPOCH == 0 and compute_epoch_at_slot(state.slot) == GLOAS_FORK_EPOCH
|
||||
func CanUpgradeToGloas(slot primitives.Slot) bool {
|
||||
epochStart := slots.IsEpochStart(slot)
|
||||
gloasEpoch := slots.ToEpoch(slot) == params.BeaconConfig().GloasForkEpoch
|
||||
return epochStart && gloasEpoch
|
||||
}
|
||||
|
||||
// CanProcessEpoch checks the eligibility to process epoch.
|
||||
// The epoch can be processed at the end of the last slot of every epoch.
|
||||
//
|
||||
|
||||
@@ -233,6 +233,11 @@ func TestCanUpgradeTo(t *testing.T) {
|
||||
forkEpoch: &cfg.FuluForkEpoch,
|
||||
upgradeFunc: time.CanUpgradeToFulu,
|
||||
},
|
||||
{
|
||||
name: "Gloas",
|
||||
forkEpoch: &cfg.GloasForkEpoch,
|
||||
upgradeFunc: time.CanUpgradeToGloas,
|
||||
},
|
||||
}
|
||||
|
||||
for _, otc := range outerTestCases {
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
srcs = [
|
||||
"electra.go",
|
||||
"errors.go",
|
||||
"gloas.go",
|
||||
"log.go",
|
||||
"skip_slot_cache.go",
|
||||
"state.go",
|
||||
@@ -26,6 +27,7 @@ go_library(
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/execution:go_default_library",
|
||||
"//beacon-chain/core/fulu:go_default_library",
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/requests:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
@@ -67,6 +69,7 @@ go_test(
|
||||
"benchmarks_test.go",
|
||||
"electra_test.go",
|
||||
"exports_test.go",
|
||||
"gloas_operations_test.go",
|
||||
"skip_slot_cache_test.go",
|
||||
"state_fuzz_test.go",
|
||||
"state_test.go",
|
||||
@@ -93,6 +96,7 @@ go_test(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
|
||||
@@ -15,5 +15,6 @@ var (
|
||||
ErrProcessDepositsFailed = errors.New("process deposits failed")
|
||||
ErrProcessVoluntaryExitsFailed = errors.New("process voluntary exits failed")
|
||||
ErrProcessBLSChangesFailed = errors.New("process BLS to execution changes failed")
|
||||
ErrProcessPayloadAttestationsFailed = errors.New("process payload attestations failed")
|
||||
ErrProcessSyncAggregateFailed = errors.New("process sync aggregate failed")
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
package transition
|
||||
|
||||
var ElectraOperations = electraOperations
|
||||
var GloasOperations = gloasOperations
|
||||
|
||||
226
beacon-chain/core/transition/gloas.go
Normal file
226
beacon-chain/core/transition/gloas.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package transition
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/electra"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/epoch/precompute"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/fulu"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
v "github.com/OffchainLabs/prysm/v7/beacon-chain/core/validators"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ProcessSlotsForBlock advances the given state to the slot of the given block.
|
||||
// This function assumes that the parent state is the latest state that has been processed before the given block.
|
||||
// In particular, all that it is needed to get the blocks's prestate is to advance slots and possible epoch transitions.
|
||||
func ProcessSlotsForBlock(
|
||||
ctx context.Context,
|
||||
st state.BeaconState,
|
||||
b interfaces.ReadOnlyBeaconBlock) (state.BeaconState, error) {
|
||||
accessRoot := b.ParentRoot()
|
||||
if st.Version() < version.Gloas {
|
||||
return ProcessSlotsUsingNextSlotCache(ctx, st, accessRoot[:], b.Slot())
|
||||
}
|
||||
full, err := st.IsParentBlockFull()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not determine if parent block is full")
|
||||
}
|
||||
if full {
|
||||
accessRoot, err = st.LatestBlockHash()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get latest block hash")
|
||||
}
|
||||
}
|
||||
return ProcessSlotsUsingNextSlotCache(ctx, st, accessRoot[:], b.Slot())
|
||||
}
|
||||
|
||||
// ProcessOperations
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// <spec fn="process_operations" fork="gloas" hash="05a7a4ea">
|
||||
// def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
// # Disable former deposit mechanism once all prior deposits are processed
|
||||
// eth1_deposit_index_limit = min(
|
||||
// state.eth1_data.deposit_count, state.deposit_requests_start_index
|
||||
// )
|
||||
// if state.eth1_deposit_index < eth1_deposit_index_limit:
|
||||
// assert len(body.deposits) == min(
|
||||
// MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index
|
||||
// )
|
||||
// else:
|
||||
// assert len(body.deposits) == 0
|
||||
//
|
||||
// def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
|
||||
// for operation in operations:
|
||||
// fn(state, operation)
|
||||
//
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// for_ops(body.proposer_slashings, process_proposer_slashing)
|
||||
// for_ops(body.attester_slashings, process_attester_slashing)
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// for_ops(body.attestations, process_attestation)
|
||||
// for_ops(body.deposits, process_deposit)
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// for_ops(body.voluntary_exits, process_voluntary_exit)
|
||||
// for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// # Removed `process_deposit_request`
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// # Removed `process_withdrawal_request`
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// # Removed `process_consolidation_request`
|
||||
// # [New in Gloas:EIP7732]
|
||||
// for_ops(body.payload_attestations, process_payload_attestation)
|
||||
// </spec>
|
||||
func gloasOperations(ctx context.Context, st state.BeaconState, block interfaces.ReadOnlyBeaconBlock) (state.BeaconState, error) {
|
||||
var err error
|
||||
|
||||
bb := block.Body()
|
||||
var exitInfo *v.ExitInfo
|
||||
hasSlashings := len(bb.ProposerSlashings()) > 0 || len(bb.AttesterSlashings()) > 0
|
||||
hasExits := len(bb.VoluntaryExits()) > 0
|
||||
if hasSlashings || hasExits {
|
||||
// ExitInformation is expensive to compute, only do it if we need it.
|
||||
exitInfo = v.ExitInformation(st)
|
||||
if err := helpers.UpdateTotalActiveBalanceCache(st, exitInfo.TotalActiveBalance); err != nil {
|
||||
return nil, errors.Wrap(err, "could not update total active balance cache")
|
||||
}
|
||||
}
|
||||
st, err = blocks.ProcessProposerSlashings(ctx, st, bb.ProposerSlashings(), exitInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrProcessProposerSlashingsFailed, err.Error())
|
||||
}
|
||||
st, err = blocks.ProcessAttesterSlashings(ctx, st, bb.AttesterSlashings(), exitInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrProcessAttesterSlashingsFailed, err.Error())
|
||||
}
|
||||
st, err = electra.ProcessAttestationsNoVerifySignature(ctx, st, block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrProcessAttestationsFailed, err.Error())
|
||||
}
|
||||
if _, err := electra.ProcessDeposits(ctx, st, bb.Deposits()); err != nil {
|
||||
return nil, errors.Wrap(ErrProcessDepositsFailed, err.Error())
|
||||
}
|
||||
st, err = blocks.ProcessVoluntaryExits(ctx, st, bb.VoluntaryExits(), exitInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrProcessVoluntaryExitsFailed, err.Error())
|
||||
}
|
||||
st, err = blocks.ProcessBLSToExecutionChanges(st, block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrProcessBLSChangesFailed, err.Error())
|
||||
}
|
||||
if err := gloas.ProcessPayloadAttestations(ctx, st, bb); err != nil {
|
||||
return nil, errors.Wrap(ErrProcessPayloadAttestationsFailed, err.Error())
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// processEpochGloas describes the per epoch operations that are performed on the beacon state.
|
||||
// It's optimized by pre computing validator attested info and epoch total/attested balances upfront.
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// <spec fn="process_epoch" fork="gloas" hash="393b69ef">
|
||||
// def process_epoch(state: BeaconState) -> None:
|
||||
// process_justification_and_finalization(state)
|
||||
// process_inactivity_updates(state)
|
||||
// process_rewards_and_penalties(state)
|
||||
// process_registry_updates(state)
|
||||
// process_slashings(state)
|
||||
// process_eth1_data_reset(state)
|
||||
// process_pending_deposits(state)
|
||||
// process_pending_consolidations(state)
|
||||
// # [New in Gloas:EIP7732]
|
||||
// process_builder_pending_payments(state)
|
||||
// process_effective_balance_updates(state)
|
||||
// process_slashings_reset(state)
|
||||
// process_randao_mixes_reset(state)
|
||||
// process_historical_summaries_update(state)
|
||||
// process_participation_flag_updates(state)
|
||||
// process_sync_committee_updates(state)
|
||||
// process_proposer_lookahead(state)
|
||||
// </spec>
|
||||
func processEpochGloas(ctx context.Context, state state.BeaconState) error {
|
||||
_, span := trace.StartSpan(ctx, "gloas.ProcessEpoch")
|
||||
defer span.End()
|
||||
|
||||
if state == nil || state.IsNil() {
|
||||
return errors.New("nil state")
|
||||
}
|
||||
vp, bp, err := electra.InitializePrecomputeValidators(ctx, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vp, bp, err = electra.ProcessEpochParticipation(ctx, state, bp, vp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err = precompute.ProcessJustificationAndFinalizationPreCompute(state, bp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process justification")
|
||||
}
|
||||
state, vp, err = electra.ProcessInactivityScores(ctx, state, vp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process inactivity updates")
|
||||
}
|
||||
state, err = electra.ProcessRewardsAndPenaltiesPrecompute(state, bp, vp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process rewards and penalties")
|
||||
}
|
||||
if err := electra.ProcessRegistryUpdates(ctx, state); err != nil {
|
||||
return errors.Wrap(err, "could not process registry updates")
|
||||
}
|
||||
if err := electra.ProcessSlashings(state); err != nil {
|
||||
return err
|
||||
}
|
||||
state, err = electra.ProcessEth1DataReset(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = electra.ProcessPendingDeposits(ctx, state, primitives.Gwei(bp.ActiveCurrentEpoch)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = electra.ProcessPendingConsolidations(ctx, state); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = gloas.ProcessBuilderPendingPayments(state); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = electra.ProcessEffectiveBalanceUpdates(state); err != nil {
|
||||
return err
|
||||
}
|
||||
state, err = electra.ProcessSlashingsReset(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err = electra.ProcessRandaoMixesReset(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err = electra.ProcessHistoricalDataUpdate(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err = electra.ProcessParticipationFlagUpdates(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = electra.ProcessSyncCommitteeUpdates(ctx, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fulu.ProcessProposerLookahead(ctx, state); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
235
beacon-chain/core/transition/gloas_operations_test.go
Normal file
235
beacon-chain/core/transition/gloas_operations_test.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package transition_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/go-bitfield"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
)
|
||||
|
||||
func newGloasBlock(t *testing.T, body *ethpb.BeaconBlockBodyGloas) interfaces.ReadOnlyBeaconBlock {
|
||||
t.Helper()
|
||||
hydrated := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{Body: body},
|
||||
})
|
||||
signed, err := blocks.NewSignedBeaconBlock(hydrated)
|
||||
require.NoError(t, err)
|
||||
return signed.Block()
|
||||
}
|
||||
|
||||
func emptyGloasBody() *ethpb.BeaconBlockBodyGloas {
|
||||
return util.HydrateBeaconBlockBodyGloas(nil)
|
||||
}
|
||||
|
||||
func TestGloasOperations_HappyPath(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, 16)
|
||||
// A plain Electra state is fine here because we exercise zero operations.
|
||||
blk := newGloasBlock(t, emptyGloasBody())
|
||||
|
||||
_, err := transition.GloasOperations(context.Background(), st, blk)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestGloasOperations_ProcessingErrors covers every sentinel error the
|
||||
// function can return, one sub-test per operation step.
|
||||
func TestGloasOperations_ProcessingErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
modifyBlk func(*ethpb.BeaconBlockBodyGloas)
|
||||
errSentinel error
|
||||
errSubstr string
|
||||
}{
|
||||
{
|
||||
name: "ErrProcessProposerSlashingsFailed – out-of-bounds proposer index",
|
||||
modifyBlk: func(b *ethpb.BeaconBlockBodyGloas) {
|
||||
b.ProposerSlashings = []*ethpb.ProposerSlashing{
|
||||
{
|
||||
Header_1: ðpb.SignedBeaconBlockHeader{
|
||||
Header: ðpb.BeaconBlockHeader{
|
||||
Slot: 1,
|
||||
ProposerIndex: 999999,
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
BodyRoot: make([]byte, 32),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Header_2: ðpb.SignedBeaconBlockHeader{
|
||||
Header: ðpb.BeaconBlockHeader{
|
||||
Slot: 1,
|
||||
ProposerIndex: 999999,
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
BodyRoot: make([]byte, 32),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
errSentinel: transition.ErrProcessProposerSlashingsFailed,
|
||||
errSubstr: "process proposer slashings failed",
|
||||
},
|
||||
{
|
||||
name: "ErrProcessAttesterSlashingsFailed – out-of-bounds attesting index",
|
||||
modifyBlk: func(b *ethpb.BeaconBlockBodyGloas) {
|
||||
makeIndexed := func(root []byte) *ethpb.IndexedAttestationElectra {
|
||||
return ðpb.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{999999},
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 1,
|
||||
CommitteeIndex: 0,
|
||||
BeaconBlockRoot: root,
|
||||
Source: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
Target: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
root1 := make([]byte, 32)
|
||||
root2 := make([]byte, 32)
|
||||
root2[0] = 0xff // different roots → slashable
|
||||
b.AttesterSlashings = []*ethpb.AttesterSlashingElectra{
|
||||
{
|
||||
Attestation_1: makeIndexed(root1),
|
||||
Attestation_2: makeIndexed(root2),
|
||||
},
|
||||
}
|
||||
},
|
||||
errSentinel: transition.ErrProcessAttesterSlashingsFailed,
|
||||
errSubstr: "process attester slashings failed",
|
||||
},
|
||||
|
||||
{
|
||||
name: "ErrProcessAttestationsFailed – invalid committee index",
|
||||
modifyBlk: func(b *ethpb.BeaconBlockBodyGloas) {
|
||||
b.Attestations = []*ethpb.AttestationElectra{
|
||||
{
|
||||
AggregationBits: []byte{0b00000001},
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 1,
|
||||
CommitteeIndex: 999999, // no such committee
|
||||
BeaconBlockRoot: make([]byte, 32),
|
||||
Source: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
Target: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
},
|
||||
CommitteeBits: []byte{0b00000001},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
},
|
||||
errSentinel: transition.ErrProcessAttestationsFailed,
|
||||
errSubstr: "process attestations failed",
|
||||
},
|
||||
|
||||
{
|
||||
name: "ErrProcessDepositsFailed – empty merkle proof",
|
||||
modifyBlk: func(b *ethpb.BeaconBlockBodyGloas) {
|
||||
b.Deposits = []*ethpb.Deposit{
|
||||
{
|
||||
Proof: [][]byte{}, // invalid: proof must not be empty
|
||||
Data: ðpb.Deposit_Data{
|
||||
PublicKey: make([]byte, 48),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
Amount: 32_000_000_000,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
errSentinel: transition.ErrProcessDepositsFailed,
|
||||
errSubstr: "process deposits failed",
|
||||
},
|
||||
|
||||
{
|
||||
name: "ErrProcessVoluntaryExitsFailed – out-of-bounds validator index",
|
||||
modifyBlk: func(b *ethpb.BeaconBlockBodyGloas) {
|
||||
b.VoluntaryExits = []*ethpb.SignedVoluntaryExit{
|
||||
{
|
||||
Exit: ðpb.VoluntaryExit{
|
||||
Epoch: 0,
|
||||
ValidatorIndex: 999999,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
},
|
||||
errSentinel: transition.ErrProcessVoluntaryExitsFailed,
|
||||
errSubstr: "process voluntary exits failed",
|
||||
},
|
||||
|
||||
{
|
||||
name: "ErrProcessBLSChangesFailed – out-of-bounds validator index",
|
||||
modifyBlk: func(b *ethpb.BeaconBlockBodyGloas) {
|
||||
b.BlsToExecutionChanges = []*ethpb.SignedBLSToExecutionChange{
|
||||
{
|
||||
Message: ðpb.BLSToExecutionChange{
|
||||
ValidatorIndex: 999999,
|
||||
FromBlsPubkey: make([]byte, 48),
|
||||
ToExecutionAddress: make([]byte, 20),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
},
|
||||
errSentinel: transition.ErrProcessBLSChangesFailed,
|
||||
errSubstr: "process BLS to execution changes failed",
|
||||
},
|
||||
|
||||
{
|
||||
name: "ErrProcessPayloadAttestationsFailed – wrong beacon block root",
|
||||
modifyBlk: func(b *ethpb.BeaconBlockBodyGloas) {
|
||||
b.PayloadAttestations = []*ethpb.PayloadAttestation{
|
||||
{
|
||||
AggregationBits: bitfield.NewBitvector512(),
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: make([]byte, 32), // all-zeros ≠ header.parent_root
|
||||
Slot: 0,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
},
|
||||
errSentinel: transition.ErrProcessPayloadAttestationsFailed,
|
||||
errSubstr: "process payload attestations failed",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, 128)
|
||||
|
||||
// For the payload-attestation sub-test we need the state's latest block
|
||||
// header to have a non-zero parent root so the all-zeros root in the
|
||||
// attestation definitely mismatches.
|
||||
if tc.errSentinel == transition.ErrProcessPayloadAttestationsFailed {
|
||||
hdr := ðpb.BeaconBlockHeader{
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
BodyRoot: make([]byte, 32),
|
||||
}
|
||||
hdr.ParentRoot[0] = 0xde
|
||||
require.NoError(t, st.SetLatestBlockHeader(hdr))
|
||||
}
|
||||
|
||||
body := emptyGloasBody()
|
||||
tc.modifyBlk(body)
|
||||
|
||||
gloasBlk := newGloasBlock(t, body)
|
||||
|
||||
_, err := transition.GloasOperations(ctx, st, gloasBlk)
|
||||
require.NotNil(t, err, "expected an error but got nil")
|
||||
require.ErrorContains(t, tc.errSubstr, err)
|
||||
require.Equal(t, true, errors.Is(err, tc.errSentinel))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/epoch/precompute"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/execution"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/fulu"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
@@ -143,10 +144,11 @@ func ProcessSlot(ctx context.Context, state state.BeaconState) (state.BeaconStat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Spec v1.6.1 (pseudocode):
|
||||
// <spec fn="process_slot" fork="gloas" lines="11-13" hash="62b28839">
|
||||
// # [New in Gloas:EIP7732]
|
||||
// # Unset the next payload availability
|
||||
// state.execution_payload_availability[(state.slot + 1) % SLOTS_PER_HISTORICAL_ROOT] = 0b0
|
||||
// </spec>
|
||||
if state.Version() >= version.Gloas {
|
||||
index := uint64((state.Slot() + 1) % params.BeaconConfig().SlotsPerHistoricalRoot)
|
||||
if err := state.UpdateExecutionPayloadAvailabilityAtIndex(index, 0x0); err != nil {
|
||||
@@ -315,7 +317,11 @@ func ProcessSlotsCore(ctx context.Context, span trace.Span, state state.BeaconSt
|
||||
func ProcessEpoch(ctx context.Context, state state.BeaconState) (state.BeaconState, error) {
|
||||
var err error
|
||||
if time.CanProcessEpoch(state) {
|
||||
if state.Version() >= version.Fulu {
|
||||
if state.Version() >= version.Gloas {
|
||||
if err = processEpochGloas(ctx, state); err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not process %s epoch", version.String(state.Version())))
|
||||
}
|
||||
} else if state.Version() >= version.Fulu {
|
||||
if err = fulu.ProcessEpoch(ctx, state); err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not process %s epoch", version.String(state.Version())))
|
||||
}
|
||||
@@ -401,6 +407,15 @@ func UpgradeState(ctx context.Context, state state.BeaconState) (state.BeaconSta
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if time.CanUpgradeToGloas(slot) {
|
||||
state, err = gloas.UpgradeToGloas(state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if upgraded {
|
||||
log.WithField("version", version.String(state.Version())).Info("Upgraded state to")
|
||||
}
|
||||
|
||||
@@ -73,12 +73,12 @@ func newGloasState(t *testing.T, slot primitives.Slot, availability []byte) stat
|
||||
ExecutionPayloadAvailability: availability,
|
||||
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, int(cfg.SlotsPerEpoch*2)),
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitmentsRoot: make([]byte, 32),
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: [][]byte{make([]byte, 48)},
|
||||
},
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/altair"
|
||||
b "github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition/interop"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/validators"
|
||||
@@ -62,8 +63,7 @@ func ExecuteStateTransitionNoVerifyAnySig(
|
||||
interop.WriteBlockToDisk(signed, false /* Has the block failed */)
|
||||
interop.WriteStateToDisk(st)
|
||||
|
||||
parentRoot := signed.Block().ParentRoot()
|
||||
st, err = ProcessSlotsUsingNextSlotCache(ctx, st, parentRoot[:], signed.Block().Slot())
|
||||
st, err = ProcessSlotsForBlock(ctx, st, signed.Block())
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not process slots")
|
||||
}
|
||||
@@ -134,8 +134,7 @@ func CalculateStateRoot(
|
||||
|
||||
// Execute per slots transition.
|
||||
var err error
|
||||
parentRoot := signed.Block().ParentRoot()
|
||||
state, err = ProcessSlotsUsingNextSlotCache(ctx, state, parentRoot[:], signed.Block().Slot())
|
||||
state, err = ProcessSlotsForBlock(ctx, state, signed.Block())
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "could not process slots")
|
||||
}
|
||||
@@ -356,11 +355,16 @@ func ProcessOperationsNoVerifyAttsSigs(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
} else if beaconBlock.Version() < version.Gloas {
|
||||
state, err = electraOperations(ctx, state, beaconBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
state, err = gloasOperations(ctx, state, beaconBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return state, nil
|
||||
@@ -403,23 +407,47 @@ func ProcessBlockForStateRoot(
|
||||
return nil, errors.Wrap(err, "could not process block header")
|
||||
}
|
||||
|
||||
enabled, err := b.IsExecutionEnabled(state, blk.Body())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not check if execution is enabled")
|
||||
}
|
||||
if enabled {
|
||||
executionData, err := blk.Body().Execution()
|
||||
if state.Version() >= version.Gloas {
|
||||
// <spec fn="process_block" fork="gloas" hash="cc0f05ee">
|
||||
// def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
||||
// process_block_header(state, block)
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// process_withdrawals(state)
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// # Removed `process_execution_payload`
|
||||
// # [New in Gloas:EIP7732]
|
||||
// process_execution_payload_bid(state, block)
|
||||
// process_randao(state, block.body)
|
||||
// process_eth1_data(state, block.body)
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// process_operations(state, block.body)
|
||||
// process_sync_aggregate(state, block.body.sync_aggregate)
|
||||
// </spec>
|
||||
if err := gloas.ProcessWithdrawals(state); err != nil {
|
||||
return nil, errors.Wrap(ErrProcessWithdrawalsFailed, err.Error())
|
||||
}
|
||||
if err := gloas.ProcessExecutionPayloadBid(state, blk); err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution payload bid")
|
||||
}
|
||||
} else {
|
||||
enabled, err := b.IsExecutionEnabled(state, blk.Body())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not check if execution is enabled")
|
||||
}
|
||||
if state.Version() >= version.Capella {
|
||||
state, err = b.ProcessWithdrawals(state, executionData)
|
||||
if enabled {
|
||||
executionData, err := blk.Body().Execution()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrProcessWithdrawalsFailed, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
if state.Version() >= version.Capella {
|
||||
state, err = b.ProcessWithdrawals(state, executionData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrProcessWithdrawalsFailed, err.Error())
|
||||
}
|
||||
}
|
||||
if err = b.ProcessPayload(state, blk.Body()); err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution data")
|
||||
}
|
||||
}
|
||||
if err = b.ProcessPayload(state, blk.Body()); err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution data")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ type ReadOnlyDatabase interface {
|
||||
OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
BackfillStatus(context.Context) (*dbval.BackfillStatus, error)
|
||||
|
||||
// Execution payload envelope operations (Gloas+).
|
||||
ExecutionPayloadEnvelope(ctx context.Context, blockRoot [32]byte) (*ethpb.SignedBlindedExecutionPayloadEnvelope, error)
|
||||
HasExecutionPayloadEnvelope(ctx context.Context, blockRoot [32]byte) bool
|
||||
|
||||
// P2P Metadata operations.
|
||||
MetadataSeqNum(ctx context.Context) (uint64, error)
|
||||
}
|
||||
@@ -115,6 +119,10 @@ type NoHeadAccessDatabase interface {
|
||||
SaveLightClientUpdate(ctx context.Context, period uint64, update interfaces.LightClientUpdate) error
|
||||
SaveLightClientBootstrap(ctx context.Context, blockRoot []byte, bootstrap interfaces.LightClientBootstrap) error
|
||||
|
||||
// Execution payload envelope operations (Gloas+).
|
||||
SaveExecutionPayloadEnvelope(ctx context.Context, envelope *ethpb.SignedExecutionPayloadEnvelope) error
|
||||
DeleteExecutionPayloadEnvelope(ctx context.Context, blockRoot [32]byte) error
|
||||
|
||||
CleanUpDirtyStates(ctx context.Context, slotsPerArchivedPoint primitives.Slot) error
|
||||
DeleteHistoricalDataBeforeSlot(ctx context.Context, slot primitives.Slot, batchSize int) (int, error)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ go_library(
|
||||
"encoding.go",
|
||||
"error.go",
|
||||
"execution_chain.go",
|
||||
"execution_payload_envelope.go",
|
||||
"finalized_block_roots.go",
|
||||
"genesis.go",
|
||||
"key.go",
|
||||
@@ -96,6 +97,7 @@ go_test(
|
||||
"deposit_contract_test.go",
|
||||
"encoding_test.go",
|
||||
"execution_chain_test.go",
|
||||
"execution_payload_envelope_test.go",
|
||||
"finalized_block_roots_test.go",
|
||||
"genesis_test.go",
|
||||
"init_test.go",
|
||||
|
||||
@@ -2,6 +2,7 @@ package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
@@ -33,6 +34,9 @@ func (s *Store) LastArchivedRoot(ctx context.Context) [32]byte {
|
||||
if err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(stateSlotIndicesBucket)
|
||||
_, blockRoot = bkt.Cursor().Last()
|
||||
if len(blockRoot) > 0 {
|
||||
blockRoot = slices.Clone(blockRoot)
|
||||
}
|
||||
return nil
|
||||
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
|
||||
panic(err) // lint:nopanic -- View never returns an error.
|
||||
@@ -51,6 +55,9 @@ func (s *Store) ArchivedPointRoot(ctx context.Context, slot primitives.Slot) [32
|
||||
if err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(stateSlotIndicesBucket)
|
||||
blockRoot = bucket.Get(bytesutil.SlotToBytesBigEndian(slot))
|
||||
if len(blockRoot) > 0 {
|
||||
blockRoot = slices.Clone(blockRoot)
|
||||
}
|
||||
return nil
|
||||
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
|
||||
panic(err) // lint:nopanic -- View never returns an error.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user