├── .all-contributorsrc ├── .credo.exs ├── .dockerignore ├── .fork_version ├── .formatter.exs ├── .github ├── CODEOWNERS ├── SECURITY.md ├── config │ └── assertoor │ │ ├── cl-stability-check.yml │ │ └── network-params.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── assertoor.yml │ ├── ci.yml │ ├── ci_skipped.yml │ └── lint-pr-title.yml ├── .gitignore ├── .gitmodules ├── .iex.exs ├── .oapi_version ├── .recode.exs ├── .spectest_version ├── .tool-versions ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── bench ├── block_processing.exs ├── bls.exs ├── byte_reversal.exs ├── deposit_tree.exs ├── multiple_blocks_processing.exs ├── shuffling_bench.ex └── ssz │ ├── encode_decode_bench.exs │ └── hash_tree_root_bench.exs ├── config ├── config.exs ├── networks │ ├── gnosis │ │ ├── boot_enr.yaml │ │ └── config.yaml │ ├── holesky │ │ ├── boot_enr.yaml │ │ └── config.yaml │ ├── hoodi │ │ ├── boot_enr.yaml │ │ └── config.yaml │ ├── mainnet │ │ ├── boot_enr.yaml │ │ └── config.yaml │ ├── minimal │ │ └── config.yaml │ └── sepolia │ │ ├── boot_enr.yaml │ │ └── config.yaml ├── presets │ ├── gnosis │ │ ├── altair.yaml │ │ ├── bellatrix.yaml │ │ ├── capella.yaml │ │ ├── deneb.yaml │ │ ├── electra.yaml │ │ └── phase0.yaml │ ├── mainnet │ │ ├── altair.yaml │ │ ├── bellatrix.yaml │ │ ├── capella.yaml │ │ ├── deneb.yaml │ │ ├── electra.yaml │ │ └── phase0.yaml │ └── minimal │ │ ├── altair.yaml │ │ ├── bellatrix.yaml │ │ ├── capella.yaml │ │ ├── deneb.yaml │ │ ├── electra.yaml │ │ └── phase0.yaml └── runtime.exs ├── deposit_tree_file ├── docs ├── PREREQUISITES.md ├── TESTING.md ├── architecture.md ├── assets │ ├── State_Transition_Function_Tree.png │ ├── State_Transition_Function_Tree.svg │ └── stf_tree_legend.png ├── bindings_go.md ├── bitfields.md ├── clients.md ├── concurrency_design.md ├── consensus.md ├── finality.md ├── fork_choice.md ├── handbooks │ └── state_transition.md ├── snappy.md └── specs │ ├── beacon-chain.md │ ├── deposit-contract.md │ ├── electra │ ├── beacon-chain.md │ ├── fork.md │ ├── p2p-interface.md │ └── validator.md │ ├── fork-choice.md │ ├── fork.md │ ├── p2p-interface.md │ └── validator.md ├── electra-gap.md ├── flake.lock ├── flake.nix ├── go.work ├── go.work.sum ├── keymanager-oapi.yaml ├── lib ├── beacon_api │ ├── api_spec.ex │ ├── beacon_api.ex │ ├── controllers │ │ ├── error_controller.ex │ │ ├── v1 │ │ │ ├── beacon_controller.ex │ │ │ ├── config_controller.ex │ │ │ ├── events_controller.ex │ │ │ └── node_controller.ex │ │ └── v2 │ │ │ └── beacon_controller.ex │ ├── endpoint.ex │ ├── error_json.ex │ ├── event_pubsub.ex │ ├── helpers.ex │ ├── router.ex │ └── utils.ex ├── bls.ex ├── chain_spec │ ├── chain_spec.ex │ ├── configs │ │ ├── custom.ex │ │ ├── gen_config.ex │ │ ├── gnosis.ex │ │ ├── holesky.ex │ │ ├── hoodi.ex │ │ ├── mainnet.ex │ │ ├── minimal.ex │ │ └── sepolia.ex │ ├── presets │ │ ├── gen_preset.ex │ │ ├── gnosis.ex │ │ ├── mainnet.ex │ │ └── minimal.ex │ └── utils.ex ├── constants.ex ├── container.ex ├── key_store_api │ ├── api_spec.ex │ ├── controllers │ │ ├── error_controller.ex │ │ └── v1 │ │ │ └── key_store_controller.ex │ ├── endpoint.ex │ ├── error_json.ex │ ├── key_store_api.ex │ └── router.ex ├── keystore.ex ├── kzg.ex ├── lambda_ethereum_consensus │ ├── application.ex │ ├── beacon │ │ ├── beacon_node.ex │ │ ├── checkpoint_sync.ex │ │ ├── pending_blocks.ex │ │ ├── store_setup.ex │ │ └── sync_blocks.ex │ ├── execution │ │ ├── auth.ex │ │ ├── engine_api.ex │ │ ├── engine_api │ │ │ ├── api.ex │ │ │ ├── behaviour.ex │ │ │ └── mocked.ex │ │ ├── execution_chain.ex │ │ ├── execution_client.ex │ │ └── rpc.ex │ ├── fork_choice │ │ ├── fork_choice.ex │ │ ├── handlers.ex │ │ ├── head.ex │ │ └── simple_tree.ex │ ├── hard_fork_alias_injection.ex │ ├── logger │ │ ├── custom_console_logger.ex │ │ └── custom_logfmt_ex.ex │ ├── metrics.ex │ ├── p2p │ │ ├── blob_downloader.ex │ │ ├── block_downloader.ex │ │ ├── gossip │ │ │ ├── attestation.ex │ │ │ ├── beacon_block.ex │ │ │ ├── blob_sidecar.ex │ │ │ ├── handler.ex │ │ │ ├── operations_collector.ex │ │ │ └── sync_committee.ex │ │ ├── incoming_requests_handler.ex │ │ ├── metadata.ex │ │ ├── peerbook.ex │ │ ├── req_resp.ex │ │ └── utils.ex │ ├── prom_ex.ex │ ├── prom_ex_plugin.ex │ ├── state_transition │ │ ├── accessors.ex │ │ ├── cache.ex │ │ ├── epoch_processing.ex │ │ ├── math.ex │ │ ├── misc.ex │ │ ├── mutators.ex │ │ ├── operations.ex │ │ ├── predicates.ex │ │ ├── shuffling.ex │ │ └── state_transition.ex │ ├── store │ │ ├── blob_db.ex │ │ ├── blobs.ex │ │ ├── block_by_slot.ex │ │ ├── block_db.ex │ │ ├── block_states.ex │ │ ├── blocks.ex │ │ ├── checkpoint_states.ex │ │ ├── db.ex │ │ ├── kv_schema.ex │ │ ├── lru_cache.ex │ │ ├── state_db.ex │ │ ├── state_db │ │ │ ├── block_root_by_slot.ex │ │ │ ├── state_info_by_root.ex │ │ │ └── state_root_by_block_root.ex │ │ ├── store_db.ex │ │ └── utils.ex │ ├── utils.ex │ └── validator │ │ ├── block_builder.ex │ │ ├── build_block_request.ex │ │ ├── duties.ex │ │ ├── utils.ex │ │ ├── validator.ex │ │ └── validator_set.ex ├── libp2p_port.ex ├── snappy.ex ├── snappy_ex.ex ├── ssz.ex ├── ssz_ex │ ├── decode.ex │ ├── encode.ex │ ├── error.ex │ ├── hash.ex │ ├── merkleization.ex │ ├── ssz_ex.ex │ └── utils.ex ├── types │ ├── att_subnet_info.ex │ ├── beacon_chain │ │ ├── attestation.ex │ │ ├── attestation_data.ex │ │ ├── attester_slashing.ex │ │ ├── beacon_block.ex │ │ ├── beacon_block_body.ex │ │ ├── beacon_block_header.ex │ │ ├── beacon_state.ex │ │ ├── blob_identifier.ex │ │ ├── blob_sidecar.ex │ │ ├── bls_to_execution_change.ex │ │ ├── checkpoint.ex │ │ ├── consolidation_request.ex │ │ ├── deposit.ex │ │ ├── deposit_data.ex │ │ ├── deposit_message.ex │ │ ├── deposit_request.ex │ │ ├── eth1_block.ex │ │ ├── eth1_data.ex │ │ ├── execution_payload.ex │ │ ├── execution_payload_header.ex │ │ ├── execution_requests.ex │ │ ├── fork.ex │ │ ├── fork_data.ex │ │ ├── historical_batch.ex │ │ ├── historical_summary.ex │ │ ├── indexed_attestation.ex │ │ ├── pending_attestation.ex │ │ ├── pending_consolidation.ex │ │ ├── pending_deposit.ex │ │ ├── pending_partial_withdrawal.ex │ │ ├── proposer_slashing.ex │ │ ├── signed_beacon_block.ex │ │ ├── signed_beacon_block_header.ex │ │ ├── signed_bls_to_execution_change.ex │ │ ├── signed_voluntary_exit.ex │ │ ├── signing_data.ex │ │ ├── single_attestation.ex │ │ ├── sync_aggregate.ex │ │ ├── sync_aggregator_selection_data.ex │ │ ├── sync_committee.ex │ │ ├── validator.ex │ │ ├── voluntary_exit.ex │ │ ├── withdrawal.ex │ │ └── withdrawal_request.ex │ ├── blobdata.ex │ ├── block_info.ex │ ├── deposit_tree.ex │ ├── deposit_tree_snapshot.ex │ ├── epoch.ex │ ├── execution_chain │ │ ├── blobs_bundle.ex │ │ └── new_payload_request.ex │ ├── p2p │ │ ├── beacon_blocks_by_range_request.ex │ │ ├── enr_fork_id.ex │ │ ├── metadata.ex │ │ └── status_message.ex │ ├── root.ex │ ├── state_info.ex │ ├── store.ex │ ├── sync_subnet_info.ex │ ├── transaction.ex │ ├── type_aliases.ex │ ├── types.ex │ └── validator │ │ ├── aggregate_and_proof.ex │ │ ├── contribution_and_proof.ex │ │ ├── signed_aggregate_and_proof.ex │ │ ├── signed_contribution_and_proof.ex │ │ ├── sync_committee_contribution.ex │ │ └── sync_committee_message.ex └── utils │ ├── bit_field.ex │ ├── bit_list.ex │ ├── bit_vector.ex │ ├── date.ex │ ├── diff.ex │ ├── profile.ex │ └── randao.ex ├── metrics ├── docker-compose.yml ├── grafana │ └── provisioning │ │ ├── dashboards │ │ ├── beam.json │ │ ├── dashboard.yml │ │ └── home.json │ │ └── datasources │ │ ├── loki_ds.yml │ │ └── prometheus_ds.yml ├── loki │ └── loki.yml ├── prometheus │ └── prometheus.yml └── promtail │ └── promtail.yml ├── mix.exs ├── mix.lock ├── native ├── bls_nif │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── kzg_nif │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── official_trusted_setup.txt │ └── src │ │ └── lib.rs ├── libp2p_port │ ├── go.mod │ ├── go.sum │ ├── internal │ │ ├── discovery │ │ │ └── discovery.go │ │ ├── port │ │ │ └── port.go │ │ ├── proto_helpers │ │ │ └── proto_helpers.go │ │ ├── reqresp │ │ │ └── reqresp.go │ │ ├── subscriptions │ │ │ └── subscriptions.go │ │ └── utils │ │ │ └── utils.go │ └── main.go ├── snappy_nif │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── ssz_nif │ ├── .cargo │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── elx_types │ ├── beacon_chain.rs │ ├── mod.rs │ ├── p2p.rs │ └── validator.rs │ ├── lib.rs │ ├── ssz_types │ ├── beacon_chain.rs │ ├── config.rs │ ├── mod.rs │ ├── p2p.rs │ └── validator.rs │ └── utils │ ├── from_elx.rs │ ├── from_ssz.rs │ ├── helpers.rs │ └── mod.rs ├── network_params.yaml ├── proto └── libp2p.proto ├── scripts └── install_protos.sh └── test ├── fixtures ├── blobs │ └── blob_sidecar.ssz_snappy ├── block.ex ├── utils.ex └── validator │ └── proposer │ ├── beacon_state.ssz_snappy │ └── empty_signed_beacon_block.ssz_snappy ├── integration ├── fork_choice │ └── handlers_test.exs └── libp2p_port_test.exs ├── spec ├── meta_utils.ex ├── runner_behaviour.ex ├── runners │ ├── bls.ex │ ├── epoch_processing.ex │ ├── finality.ex │ ├── fork_choice.ex │ ├── helpers │ │ ├── process_blocks.ex │ │ └── ssz_static_containers │ │ │ ├── bits_struct.ex │ │ │ ├── complex_test_struct.ex │ │ │ ├── fixed_test_struct.ex │ │ │ ├── single_field_test_struct.ex │ │ │ ├── small_test_struct.ex │ │ │ └── var_test_struct.ex │ ├── kzg.ex │ ├── light_client.ex │ ├── operations.ex │ ├── random.ex │ ├── rewards.ex │ ├── sanity.ex │ ├── shuffling.ex │ ├── ssz_generic.ex │ ├── ssz_static.ex │ └── sync.ex ├── tasks │ ├── check_enabled_tests.ex │ └── generate_spec_tests.ex ├── testcase.ex └── utils.ex ├── test_helper.exs └── unit ├── att_subnet_info.exs ├── beacon_api ├── beacon_api_v1_test.exs └── beacon_api_v2_test.exs ├── bit_field_test.exs ├── bit_list_test.exs ├── bit_vector_test.exs ├── blobs_test.exs ├── block_states.exs ├── blocks.exs ├── bls_test.exs ├── deposit_tree_test.exs ├── diff_test.exs ├── execution_test.exs ├── fork_choice └── handlers_test.exs ├── keystore_test.exs ├── libp2p_port_test.exs ├── mainnet_config_test.exs ├── metadata.exs ├── minimal_config_test.exs ├── operations_collector.exs ├── p2p_test.exs ├── pending_blocks.exs ├── req_resp_test.exs ├── shuffling_test.exs ├── simple_tree_test.exs ├── snappy_test.exs ├── snappyex_test.exs ├── ssz_ex_test.exs ├── ssz_test.exs ├── state_transition └── misc_test.exs ├── store ├── block_by_slot_test.exs ├── block_db_test.exs ├── block_root_by_slot_test.exs ├── kv_schema_test.exs ├── state_db_test.exs ├── state_info_by_root_test.exs └── state_root_by_block_root_test.exs ├── sync_subnet_info.exs └── validator └── block_builder_test.exs /.dockerignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | .elixir_ls/ 3 | .github/ 4 | .vscode/ 5 | bench/ 6 | deps/ 7 | docs/ 8 | level_db/ 9 | metrics/ 10 | priv/ 11 | test/ 12 | tmp/ 13 | .all-contributorsrc 14 | .credo.exs 15 | .formatter.exs 16 | .spectest_version 17 | flake.* 18 | native/**/target/ 19 | Dockerfile 20 | **/*.pb.ex 21 | **/*.pb.go 22 | -------------------------------------------------------------------------------- /.fork_version: -------------------------------------------------------------------------------- 1 | electra 2 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: 4 | ["{mix,.formatter,.recode}.exs", "{config,lib,bench}/**/*.{ex,exs}"] ++ 5 | ((Path.wildcard("test/*") -- ["test/generated"]) |> Enum.map(&(&1 <> "/**/*.{ex,exs}"))), 6 | plugins: [Recode.FormatterPlugin] 7 | ] 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lambdaclass core team 2 | * @lambdaclass/lambda-consensus-reviewers 3 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | We take the security of our project seriously. If you discover a vulnerability, we encourage you to report it responsibly so we can address it promptly. 6 | 7 | ### How to Report 8 | 9 | 1. Navigate to the **Security** tab of this repository. 10 | 2. Click on **"Report a Vulnerability"** to open the GitHub Security Advisories form. 11 | 3. Fill out the form with as much detail as possible, including: 12 | - A clear description of the issue. 13 | - Steps to reproduce the vulnerability. 14 | - The affected versions or components. 15 | - Any potential impact or severity details. 16 | 17 | Alternatively, you can send an email to **[security@lambdaclass.com](mailto:security@lambdaclass.com)** with the same details. 18 | 19 | ### Guidelines for Reporting 20 | 21 | - **Do not publicly disclose vulnerabilities** until we have confirmed and fixed the issue. 22 | - Include any proof-of-concept code, if possible, to help us verify the vulnerability more efficiently. 23 | - If applicable, specify if the vulnerability is already being exploited. 24 | 25 | ### Our Response Process 26 | 27 | - We commit to handling reports with diligence. 28 | - We will investigate all reported vulnerabilities thoroughly and transparently. 29 | - Once the vulnerability has been fixed, we will disclose the details publicly to ensure awareness and understanding. 30 | 31 | 32 | ### Reward Program 33 | 34 | While we do not currently offer a formal bug bounty program, we value your contribution and will recognize your efforts in our changelog or release notes (if you consent). 35 | 36 | Thank you for helping us improve the security of our project! 37 | -------------------------------------------------------------------------------- /.github/config/assertoor/network-params.yml: -------------------------------------------------------------------------------- 1 | participants: 2 | - el_type: geth 3 | el_image: ethereum/client-go:v1.15.6 4 | cl_type: lighthouse 5 | cl_image: sigp/lighthouse:v7.0.0-beta.5 6 | validator_count: 32 7 | count: 2 8 | - el_type: geth 9 | el_image: ethereum/client-go:v1.15.6 10 | cl_type: lambda 11 | cl_image: lambda_ethereum_consensus:latest 12 | use_separate_vc: false 13 | count: 1 14 | validator_count: 32 15 | cl_max_mem: 4096 16 | keymanager_enabled: true 17 | 18 | network_params: 19 | electra_fork_epoch: 0 20 | 21 | additional_services: 22 | - assertoor 23 | - tx_fuzz 24 | - dora 25 | 26 | assertoor_params: 27 | run_stability_check: false 28 | run_block_proposal_check: false 29 | tests: 30 | - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/main/.github/config/assertoor/cl-stability-check.yml 31 | 32 | tx_fuzz_params: 33 | tx_fuzz_extra_args: ["--txcount=3", "--accounts=80"] 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "mix" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "cargo" 8 | directory: "/native/bls_nif" 9 | schedule: 10 | interval: "weekly" 11 | - package-ecosystem: "gomod" 12 | directory: "/native/libp2p_port" 13 | schedule: 14 | interval: "weekly" 15 | - package-ecosystem: "github-actions" 16 | directory: "/" # Expands to `.github/workflows` 17 | schedule: 18 | interval: "weekly" 19 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Motivation** 2 | 3 | 4 | 5 | **Description** 6 | 7 | 8 | 9 | 10 | 11 | Closes #issue_number 12 | 13 | -------------------------------------------------------------------------------- /.github/workflows/assertoor.yml: -------------------------------------------------------------------------------- 1 | name: Assertoor tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ '*' ] 8 | paths-ignore: 9 | - 'docs/**' 10 | - '.all-contributorsrc' 11 | - 'README.md' 12 | - 'LICENSE' 13 | - 'metrics/**' 14 | jobs: 15 | ethereum-testnet: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v3 21 | - name: Build Docker image 22 | uses: docker/build-push-action@v6 23 | with: 24 | context: . 25 | file: ./Dockerfile 26 | tags: lambda_ethereum_consensus:latest 27 | load: true # Important for building without pushing 28 | - name: Setup kurtosis testnet and run assertoor tests 29 | uses: ethpandaops/kurtosis-assertoor-github-action@v1 30 | with: 31 | enclave_name: "elixir-consensus-assertoor" 32 | kurtosis_version: "1.6.0" 33 | ethereum_package_url: 'github.com/lambdaclass/ethereum-package' 34 | ethereum_package_branch: 'lecc-integration-electra' 35 | ethereum_package_args: './.github/config/assertoor/network-params.yml' 36 | -------------------------------------------------------------------------------- /.github/workflows/ci_skipped.yml: -------------------------------------------------------------------------------- 1 | # Workaround to skip the "required" check when jobs are skipped 2 | name: '*CI Skipped*' 3 | 4 | on: 5 | pull_request: 6 | branches: [ '*' ] 7 | paths: 8 | - 'docs/**' 9 | - '.all-contributorsrc' 10 | - 'README.md' 11 | - 'LICENSE' 12 | - 'metrics/**' 13 | 14 | jobs: 15 | compile-native: 16 | name: Build native libraries 17 | runs-on: ubuntu-24.04 18 | if: false 19 | steps: [run: true] 20 | 21 | build: 22 | name: Build project 23 | runs-on: ubuntu-24.04 24 | if: false 25 | steps: [run: true] 26 | 27 | smoke: 28 | name: Start and stop the node 29 | runs-on: ubuntu-24.04 30 | if: false 31 | steps: [run: true] 32 | 33 | test: 34 | name: Test 35 | runs-on: ubuntu-24.04 36 | if: false 37 | steps: [run: true] 38 | 39 | lint: 40 | name: Lint 41 | runs-on: ubuntu-24.04 42 | if: false 43 | steps: [run: true] 44 | 45 | spectests-success: 46 | name: All spec-tests passed 47 | runs-on: ubuntu-24.04 48 | if: false 49 | steps: [run: true] 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # mix dialyzer artifacts 8 | /priv/plts/*.plt 9 | /priv/plts/*.plt.hash 10 | 11 | # The directory Mix downloads your dependencies sources to. 12 | /deps/ 13 | 14 | # Where third-party dependencies like ExDoc output generated docs. 15 | /doc/ 16 | 17 | # Ignore .fetch files in case you like to edit your project deps locally. 18 | /.fetch 19 | 20 | # If the VM crashes, it generates a dump, let's ignore it too. 21 | erl_crash.dump 22 | 23 | # Also ignore archive artifacts (built via "mix archive.build"). 24 | *.ez 25 | 26 | # Ignore package tarball (built via "mix hex.build"). 27 | lambda_ethereum_consensus-*.tar 28 | 29 | # Temporary files, for example, from tests. 30 | /tmp/ 31 | 32 | *.beam 33 | /config/*.secret.exs 34 | .elixir_ls/ 35 | 36 | # Compiled artifacts. 37 | *.o 38 | *.a 39 | *.so 40 | native/libp2p_nif/go_src/main.h 41 | priv 42 | 43 | # VSCode configuration dir. 44 | .vscode/ 45 | 46 | # spec-test vectors 47 | /test/spec/vectors 48 | 49 | /native/libp2p_port/libp2p_port 50 | 51 | # Proto generated code. 52 | *.pb.ex 53 | *.pb.go 54 | 55 | # local db. 56 | /level_db 57 | /logs 58 | 59 | # Generated tests 60 | /test/generated 61 | 62 | # profiling artifacts 63 | callgrind.out.* 64 | *-eflambe-output.bggg 65 | 66 | # beacon node oapi json file 67 | beacon-node-oapi.json 68 | flamegraphs/ 69 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ethereum-package"] 2 | path = ethereum-package 3 | url = https://github.com/lambdaclass/ethereum-package.git 4 | -------------------------------------------------------------------------------- /.iex.exs: -------------------------------------------------------------------------------- 1 | alias LambdaEthereumConsensus.ForkChoice 2 | alias LambdaEthereumConsensus.ForkChoice.Head 3 | alias LambdaEthereumConsensus.StateTransition.Misc 4 | alias LambdaEthereumConsensus.Store.Blocks 5 | alias LambdaEthereumConsensus.Store.StoreDb 6 | alias LambdaEthereumConsensus.Utils 7 | 8 | # Some convenience functions for debugging 9 | store = fn -> StoreDb.fetch_store() |> elem(1) end 10 | 11 | head_root = fn -> store.() |> Head.get_head() |> elem(1) |> Utils.format_binary() end 12 | head_slot = fn -> store.() |> Head.get_head() |> elem(1) |> Blocks.get_block_info() |> then(& &1.signed_block.message.slot) end 13 | 14 | store_calculated_slot = fn -> store.() |> ForkChoice.get_current_slot() end 15 | 16 | epoch = fn slot -> slot |> Misc.compute_epoch_at_slot() end 17 | 18 | block_info = fn "0x"<>root -> root |> Base.decode16(case: :lower) |> elem(1) |> Blocks.get_block_info() end 19 | 20 | blocks_by_status = fn status -> Blocks.get_blocks_with_status(status) |> elem(1) end 21 | blocks_by_status_count = fn status -> blocks_by_status.(status) |> Enum.count() end 22 | -------------------------------------------------------------------------------- /.oapi_version: -------------------------------------------------------------------------------- 1 | v2.4.2 2 | -------------------------------------------------------------------------------- /.recode.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix recode" 2 | [ 3 | version: "0.7.2", 4 | # Can also be set/reset with `--autocorrect`/`--no-autocorrect`. 5 | autocorrect: true, 6 | # With "--dry" no changes will be written to the files. 7 | # Can also be set/reset with `--dry`/`--no-dry`. 8 | # If dry is true then verbose is also active. 9 | dry: false, 10 | # Enables or disables color in the output. 11 | color: true, 12 | # Can also be set/reset with `--verbose`/`--no-verbose`. 13 | verbose: false, 14 | # Can be overwritten by calling `mix recode "lib/**/*.ex"`. 15 | inputs: 16 | ["{mix,.formatter,.recode}.exs", "{config,lib,bench}/**/*.{ex,exs}"] ++ 17 | ((Path.wildcard("test/*") -- ["test/generated"]) |> Enum.map(&(&1 <> "/**/*.{ex,exs}"))), 18 | formatters: [Recode.CLIFormatter], 19 | tasks: [ 20 | # Tasks could be added by a tuple of the tasks module name and an options 21 | # keyword list. A task can be deactivated by `active: false`. The execution of 22 | # a deactivated task can be forced by calling `mix recode --task ModuleName`. 23 | {Recode.Task.AliasExpansion, []}, 24 | {Recode.Task.AliasOrder, []}, 25 | # {Recode.Task.Dbg, [autocorrect: false]}, 26 | # {Recode.Task.EnforceLineLength, [active: false]}, 27 | {Recode.Task.FilterCount, []}, 28 | # {Recode.Task.IOInspect, [autocorrect: false]}, 29 | # {Recode.Task.Nesting, []}, 30 | {Recode.Task.PipeFunOne, []} 31 | 32 | # {Recode.Task.SinglePipe, []}, 33 | 34 | # {Recode.Task.Specs, [exclude: ["test/**/*.{ex,exs}", "mix.exs"], config: [only: :visible]]}, 35 | 36 | # {Recode.Task.TagFIXME, [exit_code: 2]}, 37 | 38 | # {Recode.Task.TagTODO, [exit_code: 4]}, 39 | 40 | # {Recode.Task.TestFileExt, []}, 41 | 42 | # {Recode.Task.UnusedVariable, [active: false]} 43 | ] 44 | ] 45 | -------------------------------------------------------------------------------- /.spectest_version: -------------------------------------------------------------------------------- 1 | v1.5.0-beta.3 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 26.2 2 | elixir 1.16.2-otp-26 3 | golang 1.24.2 4 | rust 1.81.0 5 | protoc 30.2 6 | -------------------------------------------------------------------------------- /bench/block_processing.exs: -------------------------------------------------------------------------------- 1 | alias LambdaEthereumConsensus.ForkChoice 2 | alias LambdaEthereumConsensus.ForkChoice.Handlers 3 | alias LambdaEthereumConsensus.StateTransition.Cache 4 | alias LambdaEthereumConsensus.Store.BlockDb 5 | alias LambdaEthereumConsensus.Store.StateDb 6 | alias Types.BlockInfo 7 | alias Types.StateInfo 8 | alias Utils.Date 9 | 10 | Logger.configure(level: :warning) 11 | Cache.initialize_cache() 12 | 13 | # NOTE: this slot must be at the beginning of an epoch (i.e. a multiple of 32) 14 | slot = 9_649_056 15 | 16 | IO.puts("Fetching state and blocks...") 17 | {:ok, %StateInfo{beacon_state: state}} = StateDb.get_state_by_slot(slot) 18 | {:ok, %BlockInfo{signed_block: block}} = BlockDb.get_block_info_by_slot(slot) 19 | {:ok, %BlockInfo{} = block_info} = BlockDb.get_block_info_by_slot(slot + 1) 20 | {:ok, %BlockInfo{} = block_info_2} = BlockDb.get_block_info_by_slot(slot + 2) 21 | 22 | IO.puts("Initializing store...") 23 | {:ok, store} = Types.Store.get_forkchoice_store(state, block) 24 | store = Handlers.on_tick(store, store.time + 30) 25 | 26 | IO.puts("Processing the block 1...") 27 | 28 | {:ok, new_store} = ForkChoice.process_block(block_info, store) 29 | IO.puts("Processing the block 2...") 30 | 31 | if System.get_env("FLAMA") do 32 | filename = "flamegraphs/stacks.#{Date.now_str()}.out" 33 | Flama.run({ForkChoice, :process_block, [block_info_2, new_store]}, output_file: filename) 34 | IO.puts("Flamegraph saved to #{filename}") 35 | else 36 | Benchee.run( 37 | %{ 38 | "block (full cache)" => fn -> 39 | ForkChoice.process_block(block_info_2, new_store) 40 | end 41 | }, 42 | time: 30 43 | ) 44 | 45 | Benchee.run( 46 | %{ 47 | "block (empty cache)" => fn _ -> 48 | ForkChoice.process_block(block_info_2, new_store) 49 | end 50 | }, 51 | time: 30, 52 | before_each: fn _ -> Cache.clear_cache() end 53 | ) 54 | end 55 | -------------------------------------------------------------------------------- /bench/bls.exs: -------------------------------------------------------------------------------- 1 | public_key = 2 | Base.decode16!( 3 | "a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", 4 | case: :mixed 5 | ) 6 | 7 | message = 8 | Base.decode16!( 9 | "0000000000000000000000000000000000000000000000000000000000000000", 10 | case: :mixed 11 | ) 12 | 13 | signature = 14 | Base.decode16!( 15 | "b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55", 16 | case: :mixed 17 | ) 18 | 19 | pk = [public_key] 20 | pk_10 = List.duplicate(public_key, 10) 21 | pk_100 = List.duplicate(public_key, 100) 22 | pk_500 = List.duplicate(public_key, 500) 23 | pk_2048 = List.duplicate(public_key, 2048) 24 | 25 | Benchee.run( 26 | %{ 27 | "1" => fn -> Bls.fast_aggregate_valid?(pk, message, signature) end, 28 | "10" => fn -> Bls.fast_aggregate_valid?(pk_10, message, signature) end, 29 | "100" => fn -> Bls.fast_aggregate_valid?(pk_100, message, signature) end, 30 | "500" => fn -> Bls.fast_aggregate_valid?(pk_500, message, signature) end, 31 | "2048" => fn -> Bls.fast_aggregate_valid?(pk_2048, message, signature) end 32 | }, 33 | warmup: 2, 34 | time: 5 35 | ) 36 | -------------------------------------------------------------------------------- /bench/byte_reversal.exs: -------------------------------------------------------------------------------- 1 | reverse_number = fn bits -> 2 | bitsize = bit_size(bits) 3 | <> = bits 4 | <> 5 | end 6 | 7 | reverse_list = fn bits -> 8 | bits |> :binary.bin_to_list() |> Enum.reverse() |> :binary.list_to_bin() 9 | end 10 | 11 | reverse_comprehension = fn bits -> 12 | for <>, do: <>, into: <<>> 13 | end 14 | 15 | bits = for i <- 1..512, do: <>, into: <<>> 16 | 17 | Benchee.run( 18 | %{ 19 | "number" => fn -> reverse_number.(bits) end, 20 | "list" => fn -> reverse_list.(bits) end, 21 | "comprehension" => fn -> reverse_comprehension.(bits) end 22 | }, 23 | time: 10, 24 | memory_time: 2 25 | ) 26 | -------------------------------------------------------------------------------- /bench/deposit_tree.exs: -------------------------------------------------------------------------------- 1 | alias LambdaEthereumConsensus.Execution.ExecutionChain 2 | 3 | # The --mode db flag is needed to run this benchmark. 4 | 5 | compressed_tree = File.read!("deposit_tree_file") 6 | {:ok, encoded_tree} = :snappyer.decompress(compressed_tree) 7 | deposit_tree = :erlang.binary_to_term(encoded_tree) 8 | 9 | Benchee.run( 10 | %{ 11 | "ExecutionChain.put" => fn v -> ExecutionChain.put("", v) end 12 | }, 13 | warmup: 2, 14 | time: 5, 15 | inputs: %{ 16 | "DepositTree" => deposit_tree 17 | } 18 | ) 19 | 20 | Benchee.run( 21 | %{ 22 | "ExecutionChain.get" => fn -> ExecutionChain.get("") end 23 | }, 24 | warmup: 2, 25 | time: 5 26 | ) 27 | -------------------------------------------------------------------------------- /bench/shuffling_bench.ex: -------------------------------------------------------------------------------- 1 | alias LambdaEthereumConsensus.StateTransition.Misc 2 | alias LambdaEthereumConsensus.StateTransition.Shuffling 3 | 4 | index_count = 1000 5 | seed = :crypto.strong_rand_bytes(32) 6 | input = 0..(index_count - 1)//1 7 | 8 | Benchee.run( 9 | %{ 10 | "shuffle_list" => fn -> 11 | Shuffling.shuffle_list(Aja.Vector.new(input), seed) 12 | end, 13 | "compute_shuffled_index" => fn -> 14 | for index <- input do 15 | Misc.compute_shuffled_index(index, index_count, seed) 16 | end 17 | end 18 | }, 19 | warmup: 2, 20 | time: 5 21 | ) 22 | -------------------------------------------------------------------------------- /bench/ssz/encode_decode_bench.exs: -------------------------------------------------------------------------------- 1 | alias LambdaEthereumConsensus.Store.StateDb 2 | alias Types.BeaconState 3 | alias Types.Checkpoint 4 | 5 | # To run these benchmarks, you need a BeaconState stored in the Db beforehand. 6 | # The --mode db flag is also needed. 7 | 8 | {:ok, %{beacon_state: state}} = StateDb.get_latest_state() 9 | :mainnet = ChainSpec.get_preset() 10 | {:ok, encoded_state} = Ssz.to_ssz(state) 11 | 12 | checkpoint = %Checkpoint{ 13 | epoch: 12_345, 14 | root: Base.decode16!("0100000000000000000000000000000000000000000000000000000000000001") 15 | } 16 | 17 | {:ok, encoded_checkpoint} = Ssz.to_ssz(checkpoint) 18 | 19 | Benchee.run( 20 | %{ 21 | "SszEx.decode" => fn {v, schema} -> SszEx.decode(v, schema) end, 22 | "Ssz.from_ssz" => fn {v, schema} -> Ssz.from_ssz(v, schema) end 23 | }, 24 | warmup: 2, 25 | time: 5, 26 | inputs: %{ 27 | "BeaconState" => {encoded_state, BeaconState}, 28 | "Checkpoint" => {encoded_checkpoint, Checkpoint} 29 | } 30 | ) 31 | 32 | Benchee.run( 33 | %{ 34 | "SszEx.encode" => fn v -> SszEx.encode(v) end, 35 | "Ssz.to_ssz" => fn v -> Ssz.to_ssz(v) end 36 | }, 37 | warmup: 2, 38 | time: 5, 39 | inputs: %{ 40 | "BeaconState" => state, 41 | "Checkpoint" => checkpoint 42 | } 43 | ) 44 | -------------------------------------------------------------------------------- /bench/ssz/hash_tree_root_bench.exs: -------------------------------------------------------------------------------- 1 | alias LambdaEthereumConsensus.Store.StateDb 2 | alias SszEx.Merkleization 3 | alias Types.BeaconState 4 | 5 | # To run these benchmarks, you need a BeaconState stored in the Db beforehand. 6 | # The --mode db flag is also needed. 7 | 8 | {:ok, %{beacon_state: state}} = StateDb.get_latest_state() 9 | :mainnet = ChainSpec.get_preset() 10 | 11 | Benchee.run( 12 | %{ 13 | "SszEx.hash_tree_root!" => fn {v, schema} -> SszEx.hash_tree_root!(v, schema) end, 14 | "Ssz.hash_tree_root" => fn {v, schema} -> Ssz.hash_tree_root(v, schema) end 15 | }, 16 | warmup: 2, 17 | time: 30, 18 | inputs: %{ 19 | "BeaconState" => {state, BeaconState} 20 | } 21 | ) 22 | 23 | list = Stream.cycle([65_535]) |> Enum.take(316) 24 | schema = {:list, {:int, 16}, 1024} 25 | packed_chunks = Merkleization.pack(list, schema) 26 | limit = Merkleization.chunk_count(schema) 27 | 28 | Benchee.run( 29 | %{ 30 | "Merkleization.merkleize_chunks" => fn {chunks, leaf_count} -> 31 | Merkleization.merkleize_chunks(chunks, leaf_count) 32 | end, 33 | "Merkleization.merkleize_chunks_with_virtual_padding" => fn {chunks, leaf_count} -> 34 | Merkleization.merkleize_chunks_with_virtual_padding(chunks, leaf_count) 35 | end 36 | }, 37 | inputs: %{ 38 | "packed_list" => {packed_chunks, limit} 39 | }, 40 | warmup: 2, 41 | time: 5 42 | ) 43 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # General application configuration 2 | import Config 3 | 4 | # Configure fork 5 | # Available: "deneb" 6 | # Used only for testing 7 | 8 | fork_raw = File.read!(".fork_version") |> String.trim() 9 | 10 | fork = 11 | case fork_raw do 12 | "deneb" -> :deneb 13 | "electra" -> :electra 14 | v -> raise "Invalid fork specified: #{v}" 15 | end 16 | 17 | IO.puts("compilation done for fork: #{fork_raw}") 18 | 19 | config :lambda_ethereum_consensus, :fork, fork 20 | 21 | # Configure logging 22 | config :logger, level: :info, truncate: :infinity 23 | 24 | # Use Jason for JSON parsing in Phoenix 25 | config :phoenix, :json_library, Jason 26 | 27 | # Load minimal config by default, to allow schema checking 28 | config :lambda_ethereum_consensus, ChainSpec, 29 | config: MinimalConfig, 30 | genesis_validators_root: <<0::256>> 31 | 32 | # Configure sentry logger handler 33 | # To enable sentry, set the SENTRY_DSN environment variable to the DSN of your sentry project 34 | config :sentry, 35 | environment_name: Mix.env(), 36 | enable_source_code_context: true, 37 | root_source_code_paths: [File.cwd!()] 38 | 39 | config :lambda_ethereum_consensus, :logger, [ 40 | {:handler, :sentry_handler, Sentry.LoggerHandler, 41 | %{ 42 | config: %{ 43 | metadata: [:file, :line, :slot], 44 | capture_log_messages: true 45 | } 46 | }} 47 | ] 48 | 49 | # Avoid compiling Rustler NIFs when `RUSTLER_SKIP_COMPILE` is set 50 | if System.get_env("RUSTLER_SKIP_COMPILE") do 51 | config :lambda_ethereum_consensus, Bls, skip_compilation?: true 52 | config :lambda_ethereum_consensus, Kzg, skip_compilation?: true 53 | config :lambda_ethereum_consensus, Snappy, skip_compilation?: true 54 | config :lambda_ethereum_consensus, Ssz, skip_compilation?: true 55 | end 56 | 57 | config :sse, 58 | keep_alive: {:system, "SSE_KEEP_ALIVE_IN_MS", 55000} 59 | 60 | config :event_bus, 61 | topics: [:finalized_checkpoint, :block] 62 | 63 | config :mime, :types, %{ 64 | "text/event-stream" => ["sse"] 65 | } 66 | -------------------------------------------------------------------------------- /config/networks/holesky/boot_enr.yaml: -------------------------------------------------------------------------------- 1 | - enr:-Ku4QFo-9q73SspYI8cac_4kTX7yF800VXqJW4Lj3HkIkb5CMqFLxciNHePmMt4XdJzHvhrCC5ADI4D_GkAsxGJRLnQBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAhnTT-AQFwAP__________gmlkgnY0gmlwhLKAiOmJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyk 2 | - enr:-Ku4QPG7F72mbKx3gEQEx07wpYYusGDh-ni6SNkLvOS-hhN-BxIggN7tKlmalb0L5JPoAfqD-akTZ-gX06hFeBEz4WoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAhnTT-AQFwAP__________gmlkgnY0gmlwhJK-DYCJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyk 3 | - enr:-LK4QPxe-mDiSOtEB_Y82ozvxn9aQM07Ui8A-vQHNgYGMMthfsfOabaaTHhhJHFCBQQVRjBww_A5bM1rf8MlkJU_l68Eh2F0dG5ldHOIAADAAAAAAACEZXRoMpBpt9l0BAFwAAABAAAAAAAAgmlkgnY0gmlwhLKAiOmJc2VjcDI1NmsxoQJu6T9pclPObAzEVQ53DpVQqjadmVxdTLL-J3h9NFoCeIN0Y3CCIyiDdWRwgiMo 4 | - enr:-Ly4QGbOw4xNel5EhmDsJJ-QhC9XycWtsetnWoZ0uRy381GHdHsNHJiCwDTOkb3S1Ade0SFQkWJX_pgb3g8Jfh93rvMBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpBpt9l0BAFwAAABAAAAAAAAgmlkgnY0gmlwhJK-DYCJc2VjcDI1NmsxoQOxKv9sv3zKF8GDewgFGGHKP5HCZZpPpTrwl9eXKAWGxIhzeW5jbmV0cwCDdGNwgiMog3VkcIIjKA 5 | - enr:-LS4QG0uV4qvcpJ-HFDJRGBmnlD3TJo7yc4jwK8iP7iKaTlfQ5kZvIDspLMJhk7j9KapuL9yyHaZmwTEZqr10k9XumyCEcmHYXR0bmV0c4gAAAAABgAAAIRldGgykGm32XQEAXAAAAEAAAAAAACCaWSCdjSCaXCErK4j-YlzZWNwMjU2azGhAgfWRBEJlb7gAhXIB5ePmjj2b8io0UpEenq1Kl9cxStJg3RjcIIjKIN1ZHCCIyg 6 | - enr:-Le4QLoE1wFHSlGcm48a9ZESb_MRLqPPu6G0vHqu4MaUcQNDHS69tsy-zkN0K6pglyzX8m24mkb-LtBcbjAYdP1uxm4BhGV0aDKQabfZdAQBcAAAAQAAAAAAAIJpZIJ2NIJpcIQ5gR6Wg2lwNpAgAUHQBwEQAAAAAAAAADR-iXNlY3AyNTZrMaEDPMSNdcL92uNIyCsS177Z6KTXlbZakQqxv3aQcWawNXeDdWRwgiMohHVkcDaCI4I 7 | - enr:-KG4QC9Wm32mtzB5Fbj2ri2TEKglHmIWgvwTQCvNHBopuwpNAi1X6qOsBg_Z1-Bee-kfSrhzUQZSgDUyfH5outUprtoBgmlkgnY0gmlwhHEel3eDaXA2kP6AAAAAAAAAAlBW__4Srr-Jc2VjcDI1NmsxoQO7KE63Z4eSI55S1Yn7q9_xFkJ1Wt-a3LgiXuKGs19s0YN1ZHCCIyiEdWRwNoIjKA 8 | -------------------------------------------------------------------------------- /config/presets/gnosis/altair.yaml: -------------------------------------------------------------------------------- 1 | # Gnosis Beacon Chain preset - Altair 2 | 3 | # Updated penalty values 4 | # --------------------------------------------------------------- 5 | # 3 * 2**24 (= 50,331,648) 6 | INACTIVITY_PENALTY_QUOTIENT_ALTAIR: 50331648 7 | # 2**6 (= 64) 8 | MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: 64 9 | # 2 10 | PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: 2 11 | 12 | # Sync committee 13 | # --------------------------------------------------------------- 14 | # 2**9 (= 512) 15 | SYNC_COMMITTEE_SIZE: 512 16 | # 2**8 (= 256) 17 | EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 512 18 | 19 | # Sync protocol 20 | # --------------------------------------------------------------- 21 | # 1 22 | MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 23 | -------------------------------------------------------------------------------- /config/presets/gnosis/bellatrix.yaml: -------------------------------------------------------------------------------- 1 | # Gnosis Beacon Chain preset - Bellatrix 2 | 3 | # Updated penalty values 4 | # --------------------------------------------------------------- 5 | # 2**24 (= 16,777,216) 6 | INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: 16777216 7 | # 2**5 (= 32) 8 | MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: 32 9 | # 3 10 | PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: 3 11 | 12 | # Execution 13 | # --------------------------------------------------------------- 14 | # 2**30 (= 1,073,741,824) 15 | MAX_BYTES_PER_TRANSACTION: 1073741824 16 | # 2**20 (= 1,048,576) 17 | MAX_TRANSACTIONS_PER_PAYLOAD: 1048576 18 | # 2**8 (= 256) 19 | BYTES_PER_LOGS_BLOOM: 256 20 | # 2**5 (= 32) 21 | MAX_EXTRA_DATA_BYTES: 32 22 | -------------------------------------------------------------------------------- /config/presets/gnosis/capella.yaml: -------------------------------------------------------------------------------- 1 | # Mainnet preset - Capella 2 | 3 | # Misc 4 | # Max operations per block 5 | # --------------------------------------------------------------- 6 | # 2**4 (= 16) 7 | MAX_BLS_TO_EXECUTION_CHANGES: 16 8 | 9 | # Execution 10 | # --------------------------------------------------------------- 11 | # 2**4 (= 16) withdrawals 12 | MAX_WITHDRAWALS_PER_PAYLOAD: 8 13 | 14 | # Withdrawals processing 15 | # --------------------------------------------------------------- 16 | # 2**14 (= 16384) validators 17 | MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 8192 18 | -------------------------------------------------------------------------------- /config/presets/gnosis/deneb.yaml: -------------------------------------------------------------------------------- 1 | # Gnosis preset - Deneb 2 | # https://github.com/gnosischain/specs/tree/master/consensus/preset/gnosis 3 | 4 | # Misc 5 | # --------------------------------------------------------------- 6 | # `uint64(4096)` 7 | FIELD_ELEMENTS_PER_BLOB: 4096 8 | # `uint64(2**12)` (= 4096) 9 | MAX_BLOB_COMMITMENTS_PER_BLOCK: 4096 10 | # `uint64(6)` 11 | MAX_BLOBS_PER_BLOCK: 6 12 | # `floorlog2(BLOB_KZG_COMMITMENTS_GINDEX) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 4 + 1 + 12 = 17 13 | KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 17 14 | -------------------------------------------------------------------------------- /config/presets/gnosis/electra.yaml: -------------------------------------------------------------------------------- 1 | # Mainnet preset - Electra 2 | 3 | # Gwei values 4 | # --------------------------------------------------------------- 5 | # 2**5 * 10**9 (= 32,000,000,000) Gwei 6 | MIN_ACTIVATION_BALANCE: 32000000000 7 | # 2**11 * 10**9 (= 2,048,000,000,000) Gwei 8 | MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 9 | 10 | # State list lengths 11 | # --------------------------------------------------------------- 12 | # `uint64(2**27)` (= 134,217,728) 13 | PENDING_DEPOSITS_LIMIT: 134217728 14 | # `uint64(2**27)` (= 134,217,728) 15 | PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 16 | # `uint64(2**18)` (= 262,144) 17 | PENDING_CONSOLIDATIONS_LIMIT: 262144 18 | 19 | # Reward and penalty quotients 20 | # --------------------------------------------------------------- 21 | # `uint64(2**12)` (= 4,096) 22 | MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 23 | # `uint64(2**12)` (= 4,096) 24 | WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 25 | 26 | # # Max operations per block 27 | # --------------------------------------------------------------- 28 | # `uint64(2**0)` (= 1) 29 | MAX_ATTESTER_SLASHINGS_ELECTRA: 1 30 | # `uint64(2**3)` (= 8) 31 | MAX_ATTESTATIONS_ELECTRA: 8 32 | # `uint64(2**1)` (= 2) 33 | MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 34 | 35 | # Execution 36 | # --------------------------------------------------------------- 37 | # 2**13 (= 8192) deposit requests 38 | MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 8192 39 | # 2**4 (= 16) withdrawal requests 40 | MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16 41 | 42 | # Withdrawals processing 43 | # --------------------------------------------------------------- 44 | # 2**3 ( = 8) pending withdrawals 45 | MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8 46 | 47 | # Pending deposits processing 48 | # --------------------------------------------------------------- 49 | # 2**4 ( = 4) pending deposits 50 | MAX_PENDING_DEPOSITS_PER_EPOCH: 16 -------------------------------------------------------------------------------- /config/presets/mainnet/altair.yaml: -------------------------------------------------------------------------------- 1 | # Mainnet preset - Altair 2 | 3 | # Updated penalty values 4 | # --------------------------------------------------------------- 5 | # 3 * 2**24 (= 50,331,648) 6 | INACTIVITY_PENALTY_QUOTIENT_ALTAIR: 50331648 7 | # 2**6 (= 64) 8 | MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: 64 9 | # 2 10 | PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: 2 11 | 12 | 13 | # Sync committee 14 | # --------------------------------------------------------------- 15 | # 2**9 (= 512) 16 | SYNC_COMMITTEE_SIZE: 512 17 | # 2**8 (= 256) 18 | EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256 19 | 20 | 21 | # Sync protocol 22 | # --------------------------------------------------------------- 23 | # 1 24 | MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 25 | # SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 32 * 256) 26 | UPDATE_TIMEOUT: 8192 27 | -------------------------------------------------------------------------------- /config/presets/mainnet/bellatrix.yaml: -------------------------------------------------------------------------------- 1 | # Mainnet preset - Bellatrix 2 | 3 | # Updated penalty values 4 | # --------------------------------------------------------------- 5 | # 2**24 (= 16,777,216) 6 | INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: 16777216 7 | # 2**5 (= 32) 8 | MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: 32 9 | # 3 10 | PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: 3 11 | 12 | # Execution 13 | # --------------------------------------------------------------- 14 | # 2**30 (= 1,073,741,824) 15 | MAX_BYTES_PER_TRANSACTION: 1073741824 16 | # 2**20 (= 1,048,576) 17 | MAX_TRANSACTIONS_PER_PAYLOAD: 1048576 18 | # 2**8 (= 256) 19 | BYTES_PER_LOGS_BLOOM: 256 20 | # 2**5 (= 32) 21 | MAX_EXTRA_DATA_BYTES: 32 22 | -------------------------------------------------------------------------------- /config/presets/mainnet/capella.yaml: -------------------------------------------------------------------------------- 1 | # Mainnet preset - Capella 2 | 3 | # Misc 4 | # Max operations per block 5 | # --------------------------------------------------------------- 6 | # 2**4 (= 16) 7 | MAX_BLS_TO_EXECUTION_CHANGES: 16 8 | 9 | # Execution 10 | # --------------------------------------------------------------- 11 | # 2**4 (= 16) withdrawals 12 | MAX_WITHDRAWALS_PER_PAYLOAD: 16 13 | 14 | # Withdrawals processing 15 | # --------------------------------------------------------------- 16 | # 2**14 (= 16384) validators 17 | MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 16384 18 | -------------------------------------------------------------------------------- /config/presets/mainnet/deneb.yaml: -------------------------------------------------------------------------------- 1 | # Mainnet preset - Deneb 2 | 3 | # Misc 4 | # --------------------------------------------------------------- 5 | # `uint64(4096)` 6 | FIELD_ELEMENTS_PER_BLOB: 4096 7 | # `uint64(2**12)` (= 4096) 8 | MAX_BLOB_COMMITMENTS_PER_BLOCK: 4096 9 | # `uint64(6)` 10 | MAX_BLOBS_PER_BLOCK: 6 11 | # `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 4 + 1 + 12 = 17 12 | KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 17 13 | -------------------------------------------------------------------------------- /config/presets/mainnet/electra.yaml: -------------------------------------------------------------------------------- 1 | # Mainnet preset - Electra 2 | 3 | # Gwei values 4 | # --------------------------------------------------------------- 5 | # 2**5 * 10**9 (= 32,000,000,000) Gwei 6 | MIN_ACTIVATION_BALANCE: 32000000000 7 | # 2**11 * 10**9 (= 2,048,000,000,000) Gwei 8 | MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 9 | 10 | # State list lengths 11 | # --------------------------------------------------------------- 12 | # `uint64(2**27)` (= 134,217,728) 13 | PENDING_DEPOSITS_LIMIT: 134217728 14 | # `uint64(2**27)` (= 134,217,728) 15 | PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 16 | # `uint64(2**18)` (= 262,144) 17 | PENDING_CONSOLIDATIONS_LIMIT: 262144 18 | 19 | # Reward and penalty quotients 20 | # --------------------------------------------------------------- 21 | # `uint64(2**12)` (= 4,096) 22 | MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 23 | # `uint64(2**12)` (= 4,096) 24 | WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 25 | 26 | # # Max operations per block 27 | # --------------------------------------------------------------- 28 | # `uint64(2**0)` (= 1) 29 | MAX_ATTESTER_SLASHINGS_ELECTRA: 1 30 | # `uint64(2**3)` (= 8) 31 | MAX_ATTESTATIONS_ELECTRA: 8 32 | # `uint64(2**1)` (= 2) 33 | MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 34 | 35 | # Execution 36 | # --------------------------------------------------------------- 37 | # 2**13 (= 8192) deposit requests 38 | MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 8192 39 | # 2**4 (= 16) withdrawal requests 40 | MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16 41 | 42 | # Withdrawals processing 43 | # --------------------------------------------------------------- 44 | # 2**3 ( = 8) pending withdrawals 45 | MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8 46 | 47 | # Pending deposits processing 48 | # --------------------------------------------------------------- 49 | # 2**4 ( = 4) pending deposits 50 | MAX_PENDING_DEPOSITS_PER_EPOCH: 16 51 | -------------------------------------------------------------------------------- /config/presets/minimal/altair.yaml: -------------------------------------------------------------------------------- 1 | # Minimal preset - Altair 2 | 3 | # Updated penalty values 4 | # --------------------------------------------------------------- 5 | # 3 * 2**24 (= 50,331,648) 6 | INACTIVITY_PENALTY_QUOTIENT_ALTAIR: 50331648 7 | # 2**6 (= 64) 8 | MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: 64 9 | # 2 10 | PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: 2 11 | 12 | 13 | # Sync committee 14 | # --------------------------------------------------------------- 15 | # [customized] 16 | SYNC_COMMITTEE_SIZE: 32 17 | # [customized] 18 | EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 8 19 | 20 | 21 | # Sync protocol 22 | # --------------------------------------------------------------- 23 | # 1 24 | MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 25 | # SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 8 * 8) 26 | UPDATE_TIMEOUT: 64 27 | -------------------------------------------------------------------------------- /config/presets/minimal/bellatrix.yaml: -------------------------------------------------------------------------------- 1 | # Minimal preset - Bellatrix 2 | 3 | # Updated penalty values 4 | # --------------------------------------------------------------- 5 | # 2**24 (= 16,777,216) 6 | INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: 16777216 7 | # 2**5 (= 32) 8 | MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: 32 9 | # 3 10 | PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: 3 11 | 12 | # Execution 13 | # --------------------------------------------------------------- 14 | # 2**30 (= 1,073,741,824) 15 | MAX_BYTES_PER_TRANSACTION: 1073741824 16 | # 2**20 (= 1,048,576) 17 | MAX_TRANSACTIONS_PER_PAYLOAD: 1048576 18 | # 2**8 (= 256) 19 | BYTES_PER_LOGS_BLOOM: 256 20 | # 2**5 (= 32) 21 | MAX_EXTRA_DATA_BYTES: 32 22 | -------------------------------------------------------------------------------- /config/presets/minimal/capella.yaml: -------------------------------------------------------------------------------- 1 | # Minimal preset - Capella 2 | 3 | # Max operations per block 4 | # --------------------------------------------------------------- 5 | # 2**4 (= 16) 6 | MAX_BLS_TO_EXECUTION_CHANGES: 16 7 | 8 | 9 | # Execution 10 | # --------------------------------------------------------------- 11 | # [customized] 2**2 (= 4) 12 | MAX_WITHDRAWALS_PER_PAYLOAD: 4 13 | 14 | # Withdrawals processing 15 | # --------------------------------------------------------------- 16 | # [customized] 2**4 (= 16) validators 17 | MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 16 18 | -------------------------------------------------------------------------------- /config/presets/minimal/deneb.yaml: -------------------------------------------------------------------------------- 1 | # Minimal preset - Deneb 2 | 3 | # Misc 4 | # --------------------------------------------------------------- 5 | # `uint64(4096)` 6 | FIELD_ELEMENTS_PER_BLOB: 4096 7 | # [customized] 8 | MAX_BLOB_COMMITMENTS_PER_BLOCK: 32 9 | # [customized] floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK) (= 4 + 1 + 5 = 10) 10 | KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 10 11 | -------------------------------------------------------------------------------- /config/presets/minimal/electra.yaml: -------------------------------------------------------------------------------- 1 | # Minimal preset - Electra 2 | 3 | # Gwei values 4 | # --------------------------------------------------------------- 5 | # 2**5 * 10**9 (= 32,000,000,000) Gwei 6 | MIN_ACTIVATION_BALANCE: 32000000000 7 | # 2**11 * 10**9 (= 2,048,000,000,000) Gwei 8 | MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 9 | 10 | # State list lengths 11 | # --------------------------------------------------------------- 12 | # `uint64(2**27)` (= 134,217,728) 13 | PENDING_DEPOSITS_LIMIT: 134217728 14 | # [customized] `uint64(2**6)` (= 64) 15 | PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64 16 | # [customized] `uint64(2**6)` (= 64) 17 | PENDING_CONSOLIDATIONS_LIMIT: 64 18 | 19 | # Reward and penalty quotients 20 | # --------------------------------------------------------------- 21 | # `uint64(2**12)` (= 4,096) 22 | MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 23 | # `uint64(2**12)` (= 4,096) 24 | WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 25 | 26 | # # Max operations per block 27 | # --------------------------------------------------------------- 28 | # `uint64(2**0)` (= 1) 29 | MAX_ATTESTER_SLASHINGS_ELECTRA: 1 30 | # `uint64(2**3)` (= 8) 31 | MAX_ATTESTATIONS_ELECTRA: 8 32 | # `uint64(2**1)` (= 2) 33 | MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 34 | 35 | # Execution 36 | # --------------------------------------------------------------- 37 | # [customized] 38 | MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 4 39 | # [customized] 2**1 (= 2) withdrawal requests 40 | MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2 41 | 42 | # Withdrawals processing 43 | # --------------------------------------------------------------- 44 | # 2**1 ( = 2) pending withdrawals 45 | MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2 46 | 47 | # Pending deposits processing 48 | # --------------------------------------------------------------- 49 | # 2**4 ( = 4) pending deposits 50 | MAX_PENDING_DEPOSITS_PER_EPOCH: 16 51 | -------------------------------------------------------------------------------- /deposit_tree_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/9f252e72c561d105207aa0e86175d1ecf1a2c292/deposit_tree_file -------------------------------------------------------------------------------- /docs/assets/State_Transition_Function_Tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/9f252e72c561d105207aa0e86175d1ecf1a2c292/docs/assets/State_Transition_Function_Tree.png -------------------------------------------------------------------------------- /docs/assets/stf_tree_legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/9f252e72c561d105207aa0e86175d1ecf1a2c292/docs/assets/stf_tree_legend.png -------------------------------------------------------------------------------- /docs/finality.md: -------------------------------------------------------------------------------- 1 | # Finalization: Casper FFG 2 | 3 | The name stands for Friendly Finality Gadget. It is a "finality gadget" as it always works on top of a block-proposing algorithm. 4 | 5 | **This document will be expanded in a different PR** 6 | -------------------------------------------------------------------------------- /docs/snappy.md: -------------------------------------------------------------------------------- 1 | # Metadata request example 2 | 3 | After sending a *GetMetadata* request to a peer, I get as a response: 4 | 5 | `0011ff060000734e6150705901150000cd11e7d53a03000000000000ffffffffffffffff0f` 6 | 7 | ## *GetMetadata* request header 8 | 9 | Decoding it with the protocol described in the consensus specs (**Req/Resp interaction**), we get: 10 | 11 | ```elixir 12 | # Status (success) 13 | "00" 14 | # Optional header (uncompressed length: 17) 15 | "11" 16 | # Encoded data 17 | "ff060000734e6150705901150000cd11e7d53a03000000000000ffffffffffffffff0f" 18 | ``` 19 | 20 | ## *Snappy* framing format 21 | 22 | We can interpret the encoded data with the Snappy framing format described in [google/snappy](https://github.com/google/snappy/blob/main/framing_format.txt). 23 | Then we get: 24 | 25 | ```elixir 26 | ### Chunk start ### 27 | # Chunk type (stream identifier) 28 | "ff" 29 | # Chunk size in LE (6) 30 | "060000" 31 | # Chunk data ("sNaPpY" in ASCII) 32 | "734e61507059" 33 | 34 | ### Chunk start ### 35 | # Chunk type (uncompressed data) 36 | "01" 37 | # Chunk size in LE (21) 38 | "150000" 39 | # CRC-32C checksum (4 bytes; little-endian) 40 | "cd11e7d5" 41 | # SSZ encoded payload (uncompressed) 42 | "3a03000000000000ffffffffffffffff0f" 43 | ``` 44 | 45 | ## *SSZ* 46 | 47 | Decoding the payload with [simpleserialize.com](https://simpleserialize.com/), we find the received message: 48 | 49 | ```json 50 | { 51 | "seq_number": "826", 52 | "attnets": "0xffffffffffffffff", 53 | "syncnets": "0x0f" 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Nix development environment for LambdaClass Ethereum Consensus Client."; 3 | 4 | # Flake inputs 5 | inputs = { 6 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 7 | rust-overlay.url = "github:oxalica/rust-overlay"; # a helper for Rust + Nix 8 | flake-utils.url = "github:numtide/flake-utils"; 9 | }; 10 | 11 | # Flake outputs 12 | outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }: 13 | flake-utils.lib.eachDefaultSystem ( 14 | system: 15 | let 16 | overlays = [ (import rust-overlay) ]; 17 | pkgs = import nixpkgs { 18 | inherit system overlays; 19 | }; 20 | 21 | rustToolchain = pkgs.rust-bin.stable."1.71.1".default; 22 | otp = pkgs.beam.packages.erlang_26; 23 | 24 | in 25 | { 26 | devShells.default = pkgs.mkShell { 27 | 28 | buildInputs = [ 29 | rustToolchain 30 | pkgs.go_1_21 31 | pkgs.gotools 32 | otp.erlang 33 | nixpkgs.legacyPackages.aarch64-darwin.elixir_1_16 34 | pkgs.elixir_ls 35 | pkgs.glibcLocales 36 | pkgs.protobuf3_24 37 | ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ libiconv ]) 38 | ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ 39 | CoreFoundation 40 | CoreServices 41 | Security 42 | ]); 43 | 44 | shellHook = '' 45 | if [ -f ~/.git-prompt.sh ]; then 46 | GIT_PS1_SHOWUPSTREAM="auto" 47 | GIT_PS1_SHOWCOLORHINTS="yes" 48 | source ~/.git-prompt.sh 49 | export PROMPT_COMMAND='__git_ps1 "\u@\h:\W" "\\\$ ";' 50 | fi 51 | 52 | export PATH="$HOME/go/bin:$HOME/.mix/escripts:$PATH" 53 | ''; 54 | }; 55 | } 56 | ); 57 | } 58 | 59 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.21 2 | 3 | use ./native/libp2p_port 4 | -------------------------------------------------------------------------------- /lib/beacon_api/api_spec.ex: -------------------------------------------------------------------------------- 1 | defmodule BeaconApi.ApiSpec do 2 | @moduledoc false 3 | alias OpenApiSpex.OpenApi 4 | @behaviour OpenApi 5 | 6 | file = "beacon-node-oapi.json" 7 | @external_resource file 8 | @ethspec File.read!(file) 9 | |> Jason.decode!() 10 | |> OpenApiSpex.OpenApi.Decode.decode() 11 | 12 | @impl OpenApi 13 | def spec(), do: @ethspec 14 | end 15 | -------------------------------------------------------------------------------- /lib/beacon_api/beacon_api.ex: -------------------------------------------------------------------------------- 1 | defmodule BeaconApi do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, components, channels, and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use BeaconApi, :controller 9 | use BeaconApi, :html 10 | 11 | The definitions below will be executed for every controller, 12 | component, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define additional modules and import 17 | those modules here. 18 | """ 19 | 20 | def router() do 21 | quote do 22 | use Phoenix.Router, helpers: false 23 | 24 | # Import common connection and controller functions to use in pipelines 25 | import Plug.Conn 26 | import Phoenix.Controller 27 | end 28 | end 29 | 30 | def controller() do 31 | quote do 32 | use Phoenix.Controller, 33 | formats: [:json] 34 | 35 | import Plug.Conn 36 | end 37 | end 38 | 39 | @doc """ 40 | When used, dispatch to the appropriate controller/view/etc. 41 | """ 42 | defmacro __using__(which) when is_atom(which) do 43 | apply(__MODULE__, which, []) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/beacon_api/controllers/error_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BeaconApi.ErrorController do 2 | require Logger 3 | use BeaconApi, :controller 4 | 5 | @spec bad_request(Plug.Conn.t(), binary()) :: Plug.Conn.t() 6 | def bad_request(conn, message) do 7 | Logger.error("Bad request: #{message}, path: #{conn.request_path}") 8 | 9 | conn 10 | |> put_status(400) 11 | |> json(%{ 12 | code: 400, 13 | message: "#{message}" 14 | }) 15 | end 16 | 17 | @spec not_found(Plug.Conn.t(), any) :: Plug.Conn.t() 18 | def not_found(conn, _params) do 19 | Logger.error("Resource not found, path: #{conn.request_path}") 20 | 21 | conn 22 | |> put_status(404) 23 | |> json(%{ 24 | code: 404, 25 | message: "Resource not found" 26 | }) 27 | end 28 | 29 | @spec internal_error(Plug.Conn.t(), any) :: Plug.Conn.t() 30 | def internal_error(conn, _params) do 31 | Logger.error("Internal server error, path: #{conn.request_path}") 32 | 33 | conn 34 | |> put_status(500) 35 | |> json(%{ 36 | code: 500, 37 | message: "Internal server error" 38 | }) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/beacon_api/controllers/v1/events_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BeaconApi.V1.EventsController do 2 | use BeaconApi, :controller 3 | 4 | alias BeaconApi.ApiSpec 5 | alias BeaconApi.EventPubSub 6 | 7 | require Logger 8 | 9 | def open_api_operation(:subscribe), 10 | do: ApiSpec.spec().paths["/eth/v1/events"].get 11 | 12 | @spec subscribe(Plug.Conn.t(), any) :: Plug.Conn.t() 13 | def subscribe(conn, %{"topics" => topics}) do 14 | case parse_topics(topics) do 15 | {:ok, topics} -> 16 | EventPubSub.sse_subscribe(conn, topics) 17 | 18 | {:error, error} -> 19 | send_chunked_error(conn, error) 20 | end 21 | end 22 | 23 | def subscribe(conn, _params) do 24 | error = 25 | Jason.encode!(%{ 26 | code: 400, 27 | message: "Missing field topics" 28 | }) 29 | 30 | send_chunked_error(conn, error) 31 | end 32 | 33 | defp parse_topics(topics_string) do 34 | # topics is a string list in the form of: "finalized_checkpoint, block" we need to split it 35 | topics = topics_string |> String.split(",") |> Enum.map(&String.trim/1) 36 | not_implemented_topics = Enum.reject(topics, &EventPubSub.implemented_topic?/1) 37 | 38 | if Enum.empty?(not_implemented_topics) do 39 | {:ok, topics} 40 | else 41 | {:error, 42 | "Invalid topic/s #{inspect(not_implemented_topics)}. For now, only #{inspect(EventPubSub.implemented_topics())} are supported."} 43 | end 44 | end 45 | 46 | defp send_chunked_error(conn, error) do 47 | conn 48 | |> Plug.Conn.send_chunked(400) 49 | |> Plug.Conn.chunk(error) 50 | |> case do 51 | {:ok, conn} -> Plug.Conn.halt(conn) 52 | {:error, _reason} -> Plug.Conn.halt(conn) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/beacon_api/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule BeaconApi.Endpoint do 2 | use Sentry.PlugCapture 3 | use Phoenix.Endpoint, otp_app: :lambda_ethereum_consensus 4 | 5 | plug(Plug.Parsers, 6 | parsers: [:urlencoded, :multipart, :json], 7 | pass: ["*/*"], 8 | json_decoder: Phoenix.json_library() 9 | ) 10 | 11 | plug(BeaconApi.Router) 12 | plug(Sentry.PlugContext) 13 | end 14 | -------------------------------------------------------------------------------- /lib/beacon_api/error_json.ex: -------------------------------------------------------------------------------- 1 | defmodule BeaconApi.ErrorJSON do 2 | use BeaconApi, :controller 3 | 4 | @spec render(any, any) :: %{message: String.t()} 5 | def render(_, _) do 6 | %{ 7 | message: "There has been an error" 8 | } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/beacon_api/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule BeaconApi.Utils do 2 | @moduledoc """ 3 | Set of useful utilitary functions in the context of the Beacon API 4 | """ 5 | alias BeaconApi.Helpers 6 | alias LambdaEthereumConsensus.Utils.BitList 7 | 8 | @spec parse_id(binary) :: Helpers.block_id() 9 | def parse_id("genesis"), do: :genesis 10 | def parse_id("justified"), do: :justified 11 | def parse_id("finalized"), do: :finalized 12 | def parse_id("head"), do: :head 13 | 14 | def parse_id("0x" <> hex_root) when byte_size(hex_root) == 64 do 15 | case Base.decode16(hex_root) do 16 | {:ok, decoded} -> decoded 17 | :error -> :invalid_id 18 | end 19 | end 20 | 21 | def parse_id("0x" <> _hex_root), do: :invalid_id 22 | 23 | def parse_id(slot) do 24 | case Integer.parse(slot) do 25 | {num, ""} -> num 26 | _ -> :invalid_id 27 | end 28 | end 29 | 30 | def hex_encode(binary) when is_binary(binary) do 31 | "0x" <> Base.encode16(binary, case: :lower) 32 | end 33 | 34 | def hex_decode("0x" <> binary), do: Base.decode16(binary, case: :lower) 35 | def hex_decode(binary), do: {:error, "Not valid pubkey: #{inspect(binary)}"} 36 | 37 | defp to_json(attribute, module) when is_struct(attribute) do 38 | module.schema() 39 | |> Enum.map(fn {k, schema} -> 40 | {k, Map.fetch!(attribute, k) |> to_json(schema)} 41 | end) 42 | |> Map.new() 43 | end 44 | 45 | defp to_json(binary, {:byte_list, _}), do: to_json(binary) 46 | defp to_json(binary, {:byte_vector, _}), do: to_json(binary) 47 | 48 | defp to_json(list, {x, schema, _}) when x in [:list, :vector], 49 | do: Enum.map(list, fn elem -> to_json(elem, schema) end) 50 | 51 | defp to_json(bitlist, {:bitlist, _}) do 52 | bitlist 53 | |> BitList.to_bytes() 54 | |> hex_encode() 55 | end 56 | 57 | defp to_json(v, _schema), do: to_json(v) 58 | 59 | def to_json(%name{} = v), do: to_json(v, name) 60 | def to_json({k, v}), do: {k, to_json(v)} 61 | def to_json(x) when is_binary(x), do: hex_encode(x) 62 | def to_json(v), do: inspect(v) 63 | end 64 | -------------------------------------------------------------------------------- /lib/chain_spec/chain_spec.ex: -------------------------------------------------------------------------------- 1 | defmodule ChainSpec do 2 | @moduledoc """ 3 | Single entrypoint for fetching chain-specific constants. 4 | """ 5 | 6 | def get_config(), 7 | do: Application.fetch_env!(:lambda_ethereum_consensus, __MODULE__) |> Keyword.fetch!(:config) 8 | 9 | def get_preset(), do: get_config().get("PRESET_BASE") |> String.to_atom() 10 | 11 | def get_fork_version_for_epoch(epoch) do 12 | if epoch >= get("ELECTRA_FORK_EPOCH") do 13 | get("ELECTRA_FORK_VERSION") 14 | else 15 | raise "Forks before Electra are not supported" 16 | end 17 | end 18 | 19 | # NOTE: this only works correctly for Capella 20 | def get(name), do: get_config().get(name) 21 | 22 | def get_all(), do: get_config().get_all() 23 | 24 | def get_genesis_validators_root() do 25 | Application.fetch_env!(:lambda_ethereum_consensus, __MODULE__) 26 | |> Keyword.fetch!(:genesis_validators_root) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/chain_spec/configs/custom.ex: -------------------------------------------------------------------------------- 1 | defmodule CustomConfig do 2 | @moduledoc """ 3 | Custom, dynamically-loaded config constants. 4 | """ 5 | alias ChainSpec.GenConfig 6 | @behaviour GenConfig 7 | 8 | def load_from_file!(path) do 9 | config = ConfigUtils.load_config_from_file!(path) 10 | preset = Map.fetch!(config, "PRESET_BASE") |> ConfigUtils.parse_preset() 11 | config_name = Map.get(config, "CONFIG_NAME") |> ConfigUtils.parse_config() 12 | 13 | merged_config = 14 | preset.get_preset() 15 | |> Map.merge(get_base_config(config_name)) 16 | |> Map.merge(config) 17 | 18 | Application.put_env(:lambda_ethereum_consensus, __MODULE__, merged: merged_config) 19 | end 20 | 21 | @impl GenConfig 22 | def get_all() do 23 | Application.get_env(:lambda_ethereum_consensus, __MODULE__) 24 | |> Keyword.fetch!(:merged) 25 | |> Map.new(fn {k, v} -> {k, parse_int(v)} end) 26 | end 27 | 28 | @impl GenConfig 29 | def get(key), do: get_all() |> Map.fetch!(key) 30 | 31 | # Parses as integer if parsable. If not, returns original value. 32 | defp parse_int(v) when is_binary(v) do 33 | case Integer.parse(v) do 34 | {i, ""} -> i 35 | _ -> v 36 | end 37 | end 38 | 39 | defp parse_int(v), do: v 40 | 41 | defp get_base_config(:unknown), do: %{} 42 | defp get_base_config(config_name), do: config_name.get_all() 43 | end 44 | -------------------------------------------------------------------------------- /lib/chain_spec/configs/gen_config.ex: -------------------------------------------------------------------------------- 1 | defmodule ChainSpec.GenConfig do 2 | @moduledoc """ 3 | Generic config behaviour, for auto-implementing configs. 4 | """ 5 | 6 | defmacro __using__(opts) do 7 | file = Keyword.fetch!(opts, :file) 8 | config = ConfigUtils.load_config_from_file!(file) 9 | preset = Map.fetch!(config, "PRESET_BASE") |> ConfigUtils.parse_preset() 10 | 11 | quote do 12 | file = unquote(file) 13 | config = unquote(Macro.escape(config)) 14 | preset = unquote(preset) 15 | 16 | @external_resource file 17 | @__parsed_config config 18 | @__unified Map.merge(preset.get_preset(), @__parsed_config) 19 | 20 | @behaviour unquote(__MODULE__) 21 | 22 | @impl unquote(__MODULE__) 23 | def get(key), do: Map.fetch!(@__unified, key) 24 | 25 | @impl unquote(__MODULE__) 26 | def get_all(), do: @__unified 27 | end 28 | end 29 | 30 | @doc """ 31 | Fetches a value from config. 32 | """ 33 | @callback get(String.t()) :: term() 34 | 35 | @doc """ 36 | Fetches the full config dictionary. 37 | """ 38 | @callback get_all() :: map() 39 | end 40 | -------------------------------------------------------------------------------- /lib/chain_spec/configs/gnosis.ex: -------------------------------------------------------------------------------- 1 | defmodule GnosisConfig do 2 | @moduledoc """ 3 | Gnosis config constants. 4 | """ 5 | use ChainSpec.GenConfig, file: "config/networks/gnosis/config.yaml" 6 | 7 | genesis_validators_root = 8 | Base.decode16!("F5DCB5564E829AAB27264B9BECD5DFAA017085611224CB3036F573368DBB9D47") 9 | 10 | def genesis_validators_root(), do: unquote(genesis_validators_root) 11 | end 12 | -------------------------------------------------------------------------------- /lib/chain_spec/configs/holesky.ex: -------------------------------------------------------------------------------- 1 | defmodule HoleskyConfig do 2 | @moduledoc """ 3 | Holešky config constants. 4 | """ 5 | use ChainSpec.GenConfig, file: "config/networks/holesky/config.yaml" 6 | 7 | genesis_validators_root = 8 | Base.decode16!("9143AA7C615A7F7115E2B6AAC319C03529DF8242AE705FBA9DF39B79C59FA8B1") 9 | 10 | def genesis_validators_root(), do: unquote(genesis_validators_root) 11 | end 12 | -------------------------------------------------------------------------------- /lib/chain_spec/configs/hoodi.ex: -------------------------------------------------------------------------------- 1 | defmodule HoodiConfig do 2 | @moduledoc """ 3 | Hoodi config constants. 4 | """ 5 | use ChainSpec.GenConfig, file: "config/networks/hoodi/config.yaml" 6 | 7 | genesis_validators_root = 8 | Base.decode16!("212F13FC4DF078B6CB7DB228F1C8307566DCECF900867401A92023D7BA99CB5F") 9 | 10 | def genesis_validators_root(), do: unquote(genesis_validators_root) 11 | end 12 | -------------------------------------------------------------------------------- /lib/chain_spec/configs/mainnet.ex: -------------------------------------------------------------------------------- 1 | defmodule MainnetConfig do 2 | @moduledoc """ 3 | Mainnet config constants. 4 | """ 5 | use ChainSpec.GenConfig, file: "config/networks/mainnet/config.yaml" 6 | 7 | genesis_validators_root = 8 | Base.decode16!("4B363DB94E286120D76EB905340FDD4E54BFE9F06BF33FF6CF5AD27F511BFE95") 9 | 10 | def genesis_validators_root(), do: unquote(genesis_validators_root) 11 | end 12 | -------------------------------------------------------------------------------- /lib/chain_spec/configs/minimal.ex: -------------------------------------------------------------------------------- 1 | defmodule MinimalConfig do 2 | @moduledoc """ 3 | Minimal config constants. These are used only for tests. 4 | """ 5 | use ChainSpec.GenConfig, file: "config/networks/minimal/config.yaml" 6 | 7 | def genesis_validators_root(), do: <<0::256>> 8 | end 9 | -------------------------------------------------------------------------------- /lib/chain_spec/configs/sepolia.ex: -------------------------------------------------------------------------------- 1 | defmodule SepoliaConfig do 2 | @moduledoc """ 3 | Sepolia config constants. 4 | """ 5 | use ChainSpec.GenConfig, file: "config/networks/sepolia/config.yaml" 6 | 7 | genesis_validators_root = 8 | Base.decode16!("D8EA171F3C94AEA21EBC42A1ED61052ACF3F9209C00E4EFBAADDAC09ED9B8078") 9 | 10 | def genesis_validators_root(), do: unquote(genesis_validators_root) 11 | end 12 | -------------------------------------------------------------------------------- /lib/chain_spec/presets/gen_preset.ex: -------------------------------------------------------------------------------- 1 | defmodule ChainSpec.GenPreset do 2 | @moduledoc """ 3 | Generic preset behaviour, for auto-implementing presets. 4 | """ 5 | 6 | defmacro __using__(opts) do 7 | file = Keyword.fetch!(opts, :file) 8 | 9 | quote do 10 | file = unquote(file) 11 | 12 | @external_resource file 13 | 14 | @__parsed_preset ConfigUtils.load_preset_from_dir!(file) 15 | 16 | @behaviour unquote(__MODULE__) 17 | 18 | @impl unquote(__MODULE__) 19 | def get_preset(), do: @__parsed_preset 20 | end 21 | end 22 | 23 | @doc """ 24 | Fetches the whole preset. 25 | """ 26 | @callback get_preset() :: map() 27 | end 28 | -------------------------------------------------------------------------------- /lib/chain_spec/presets/gnosis.ex: -------------------------------------------------------------------------------- 1 | defmodule GnosisPreset do 2 | @moduledoc """ 3 | Mainnet preset constants. 4 | """ 5 | use ChainSpec.GenPreset, file: "config/presets/gnosis" 6 | end 7 | -------------------------------------------------------------------------------- /lib/chain_spec/presets/mainnet.ex: -------------------------------------------------------------------------------- 1 | defmodule MainnetPreset do 2 | @moduledoc """ 3 | Mainnet preset constants. 4 | """ 5 | use ChainSpec.GenPreset, file: "config/presets/mainnet" 6 | end 7 | -------------------------------------------------------------------------------- /lib/chain_spec/presets/minimal.ex: -------------------------------------------------------------------------------- 1 | defmodule MinimalPreset do 2 | @moduledoc """ 3 | Minimal preset constants. 4 | """ 5 | use ChainSpec.GenPreset, file: "config/presets/minimal" 6 | end 7 | -------------------------------------------------------------------------------- /lib/chain_spec/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule ConfigUtils do 2 | @moduledoc """ 3 | Utilities for parsing configs and presets. 4 | """ 5 | @forks ["phase0", "altair", "bellatrix", "capella", "deneb", "electra"] 6 | 7 | def load_config_from_file!(path) do 8 | path 9 | |> File.read!() 10 | |> String.replace(~r/ (0x[0-9a-fA-F]+)/, " '\\g{1}'") 11 | |> YamlElixir.read_from_string!() 12 | |> Stream.map(fn 13 | {k, "0x" <> hash} -> {k, Base.decode16!(hash, case: :mixed)} 14 | e -> e 15 | end) 16 | |> Enum.into(%{}) 17 | end 18 | 19 | def load_preset_from_dir!(path) do 20 | # TODO: we should return the merged preset for each fork here 21 | @forks 22 | |> Stream.map(&Path.join([path, "#{&1}.yaml"])) 23 | |> Stream.map(&YamlElixir.read_from_file!/1) 24 | # The order is to ensure that the later forks override the earlier ones. 25 | |> Enum.reduce(&Map.merge(&2, &1)) 26 | end 27 | 28 | def parse_config("mainnet"), do: MainnetConfig 29 | def parse_config("sepolia"), do: SepoliaConfig 30 | def parse_config("holesky"), do: HoleskyConfig 31 | def parse_config("minimal"), do: MinimalConfig 32 | def parse_config("gnosis"), do: GnosisConfig 33 | def parse_config("hoodi"), do: HoodiConfig 34 | def parse_config(_), do: :unknown 35 | 36 | def parse_config!(config) do 37 | with :unknown <- parse_config(config) do 38 | raise("Unknown config: #{config}") 39 | end 40 | end 41 | 42 | def parse_preset("mainnet"), do: MainnetPreset 43 | def parse_preset("minimal"), do: MinimalPreset 44 | def parse_preset("gnosis"), do: GnosisPreset 45 | def parse_preset(other), do: raise("Unknown preset: #{other}") 46 | 47 | def load_testnet_bootnodes(testnet_dir) do 48 | bootnodes_file = Path.join(testnet_dir, "boot_enr.yaml") 49 | 50 | if File.exists?(bootnodes_file) do 51 | YamlElixir.read_from_file!(bootnodes_file) 52 | else 53 | [] 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/container.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Container do 2 | @moduledoc """ 3 | Container for SSZ 4 | """ 5 | 6 | @doc """ 7 | Returns a keyword list, where the keys are attribute names, and the values are schemas. 8 | It specifies both the de/serialization order and the schema for each key in the map. 9 | """ 10 | @callback schema() :: Keyword.t(SszEx.schema()) 11 | 12 | @doc """ 13 | Marks the module as implementing the `Container` behaviour, 14 | and adds some compile-time callback checks. 15 | """ 16 | defmacro __using__(_opts) do 17 | quote do 18 | @behaviour unquote(__MODULE__) 19 | 20 | @after_compile unquote(__MODULE__) 21 | end 22 | end 23 | 24 | @doc """ 25 | Called after compilation. Checks if the current module is a valid schema. 26 | """ 27 | def __after_compile__(env, _bytecode) do 28 | SszEx.validate_schema!(env.module) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/key_store_api/api_spec.ex: -------------------------------------------------------------------------------- 1 | defmodule KeyStoreApi.ApiSpec do 2 | @moduledoc false 3 | alias OpenApiSpex.OpenApi 4 | @behaviour OpenApi 5 | 6 | file = "keymanager-oapi.yaml" 7 | @external_resource file 8 | @ethspec YamlElixir.read_from_file!(file) 9 | |> OpenApiSpex.OpenApi.Decode.decode() 10 | 11 | @impl OpenApi 12 | def spec(), do: @ethspec 13 | end 14 | -------------------------------------------------------------------------------- /lib/key_store_api/controllers/error_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule KeyStoreApi.ErrorController do 2 | use KeyStoreApi, :controller 3 | 4 | @spec bad_request(Plug.Conn.t(), binary()) :: Plug.Conn.t() 5 | def bad_request(conn, message) do 6 | conn 7 | |> put_status(400) 8 | |> json(%{ 9 | code: 400, 10 | message: "#{message}" 11 | }) 12 | end 13 | 14 | @spec not_found(Plug.Conn.t(), any) :: Plug.Conn.t() 15 | def not_found(conn, _params) do 16 | conn 17 | |> put_status(404) 18 | |> json(%{ 19 | code: 404, 20 | message: "Resource not found" 21 | }) 22 | end 23 | 24 | @spec internal_error(Plug.Conn.t(), any) :: Plug.Conn.t() 25 | def internal_error(conn, _params) do 26 | conn 27 | |> put_status(500) 28 | |> json(%{ 29 | code: 500, 30 | message: "Internal server error" 31 | }) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/key_store_api/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule KeyStoreApi.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :lambda_ethereum_consensus 3 | 4 | plug(Plug.Parsers, 5 | parsers: [:urlencoded, :multipart, :json], 6 | pass: ["*/*"], 7 | json_decoder: Phoenix.json_library() 8 | ) 9 | 10 | plug(KeyStoreApi.Router) 11 | end 12 | -------------------------------------------------------------------------------- /lib/key_store_api/error_json.ex: -------------------------------------------------------------------------------- 1 | defmodule KeyStoreApi.ErrorJSON do 2 | use KeyStoreApi, :controller 3 | 4 | @spec render(any, any) :: %{message: String.t()} 5 | def render(_, _) do 6 | %{ 7 | message: "There has been an error" 8 | } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/key_store_api/key_store_api.ex: -------------------------------------------------------------------------------- 1 | defmodule KeyStoreApi do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, components, channels, and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use KeyStoreApi, :controller 9 | use KeyStoreApi, :html 10 | 11 | The definitions below will be executed for every controller, 12 | component, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define additional modules and import 17 | those modules here. 18 | """ 19 | 20 | def router() do 21 | quote do 22 | use Phoenix.Router, helpers: false 23 | 24 | # Import common connection and controller functions to use in pipelines 25 | import Plug.Conn 26 | import Phoenix.Controller 27 | end 28 | end 29 | 30 | def controller() do 31 | quote do 32 | use Phoenix.Controller, 33 | formats: [:json] 34 | 35 | import Plug.Conn 36 | end 37 | end 38 | 39 | @doc """ 40 | When used, dispatch to the appropriate controller/view/etc. 41 | """ 42 | defmacro __using__(which) when is_atom(which) do 43 | apply(__MODULE__, which, []) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/key_store_api/router.ex: -------------------------------------------------------------------------------- 1 | defmodule KeyStoreApi.Router do 2 | use KeyStoreApi, :router 3 | 4 | pipeline :api do 5 | plug(:accepts, ["json"]) 6 | plug(OpenApiSpex.Plug.PutApiSpec, module: KeyStoreApi.ApiSpec) 7 | end 8 | 9 | # KeyManager API Version 1 10 | scope "/eth/v1", KeyStoreApi.V1 do 11 | pipe_through(:api) 12 | 13 | scope "/keystores" do 14 | get("/", KeyStoreController, :get_keys) 15 | post("/", KeyStoreController, :add_keys) 16 | delete("/", KeyStoreController, :delete_keys) 17 | end 18 | end 19 | 20 | scope "/api" do 21 | pipe_through(:api) 22 | get("/openapi", OpenApiSpex.Plug.RenderSpec, []) 23 | end 24 | 25 | # Catch-all route outside of any scope 26 | match(:*, "/*path", KeyStoreApi.ErrorController, :not_found) 27 | end 28 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/execution/auth.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Execution.Auth do 2 | @moduledoc """ 3 | JWT generator module for authenticated Engine API communication 4 | """ 5 | use Joken.Config 6 | 7 | # Set default expiry to 60s 8 | def token_config(), do: default_claims(default_exp: 60) 9 | 10 | # JWT Authentication is necessary for the EL <> CL communication through Engine API 11 | # Following the specs here: https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md 12 | # 3 properties are required for the JWT to be valid: 13 | # 1. The execution layer and consensus layer clients SHOULD accept a configuration parameter: jwt-secret, 14 | # which designates a file containing the hex-encoded 256 bit secret key to be used for verifying/generating JWT tokens 15 | # 2. The encoding algorithm used for the JWT generation must be HMAC + SHA256 (HS256) 16 | # 3. iat (issued-at) claim. The execution layer client SHOULD only accept iat timestamps 17 | # which are within +-60 seconds from the current time 18 | 19 | @doc """ 20 | Generates a JWT token using HS256 algo and provided secret, additionaly adds an iat claim at current time. 21 | """ 22 | @spec generate_token(String.t()) :: Joken.bearer_token() 23 | def generate_token(hex_encoded_secret) do 24 | jwt_secret = 25 | hex_encoded_secret 26 | |> String.trim_leading("0x") 27 | |> Base.decode16!(case: :mixed) 28 | 29 | signer = Joken.Signer.create("HS256", jwt_secret) 30 | generate_and_sign!(%{}, signer) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/execution/engine_api.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Execution.EngineApi do 2 | @moduledoc """ 3 | Execution Layer Engine API methods with routing 4 | """ 5 | @behaviour LambdaEthereumConsensus.Execution.EngineApi.Behaviour 6 | 7 | def exchange_capabilities(), do: impl().exchange_capabilities() 8 | 9 | def new_payload(execution_payload, versioned_hashes, parent_beacon_block_root), 10 | do: impl().new_payload(execution_payload, versioned_hashes, parent_beacon_block_root) 11 | 12 | def get_payload(payload_id), do: impl().get_payload(payload_id) 13 | 14 | def forkchoice_updated(forkchoice_state, payload_attributes), 15 | do: impl().forkchoice_updated(forkchoice_state, payload_attributes) 16 | 17 | def get_block_header(block_id), do: impl().get_block_header(block_id) 18 | 19 | def get_deposit_logs(block_number_range), do: impl().get_deposit_logs(block_number_range) 20 | 21 | defp impl(), do: Application.fetch_env!(:lambda_ethereum_consensus, __MODULE__)[:implementation] 22 | end 23 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/execution/engine_api/behaviour.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Execution.EngineApi.Behaviour do 2 | @moduledoc """ 3 | Execution Layer Engine API behaviour 4 | """ 5 | 6 | alias Types.BlobsBundle 7 | alias Types.ExecutionPayload 8 | 9 | @type forkchoice_state_v1 :: %{ 10 | finalized_block_hash: Types.hash32(), 11 | head_block_hash: Types.hash32(), 12 | safe_block_hash: Types.hash32() 13 | } 14 | 15 | @type payload_attributes_v3 :: %{ 16 | timestamp: Types.uint64(), 17 | prev_randao: Types.bytes32(), 18 | suggested_fee_recipient: Types.execution_address(), 19 | withdrawals: list(Types.Withdrawal.t()), 20 | parent_beacon_block_root: Types.root() 21 | } 22 | 23 | # @type forkchoice_updated_v3_result :: %{ 24 | # "payload_id" => binary(), 25 | # "payload_status" => %{ 26 | # "latest_valid_hash" => binary() | nil, 27 | # "status" => binary(), 28 | # "validation_error" => binary() | nil 29 | # } 30 | # } 31 | 32 | @type forkchoice_updated_v3_result :: map() 33 | 34 | @callback exchange_capabilities() :: {:ok, any} | {:error, any} 35 | @callback get_payload(Types.payload_id()) :: 36 | {:ok, {ExecutionPayload.t(), BlobsBundle.t()}} | {:error, any} 37 | @callback new_payload(ExecutionPayload.t(), [Types.root()], Types.root()) :: 38 | {:ok, any} | {:error, any} 39 | @callback forkchoice_updated(forkchoice_state_v1(), payload_attributes_v3() | nil) :: 40 | {:ok, forkchoice_updated_v3_result()} | {:error, any} 41 | @callback get_block_header(nil | Types.uint64() | Types.root()) :: {:ok, any} | {:error, any} 42 | @callback get_deposit_logs(Range.t()) :: {:ok, list(any)} | {:error, any} 43 | end 44 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/execution/engine_api/mocked.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Execution.EngineApi.Mocked do 2 | @moduledoc """ 3 | Mocked Execution Layer Engine API methods 4 | """ 5 | @behaviour LambdaEthereumConsensus.Execution.EngineApi.Behaviour 6 | 7 | @doc """ 8 | Using this method Execution and consensus layer client software may 9 | exchange with a list of supported Engine API methods. 10 | """ 11 | def exchange_capabilities() do 12 | {:ok, ["engine_newPayloadV2", "engine_newPayloadV3"]} 13 | end 14 | 15 | def new_payload(_execution_payload, _versioned_hashes, _parent_beacon_block_root) do 16 | {:ok, %{"status" => "VALID"}} 17 | end 18 | 19 | def forkchoice_updated(_forkchoice_state, _payload_attributes) do 20 | {:ok, %{"payload_id" => nil, "payload_status" => %{"status" => "VALID"}}} 21 | end 22 | 23 | def get_payload(_payload), do: {:error, "not supported"} 24 | 25 | # TODO: should we mock this too? 26 | def get_block_header(_block_id), do: {:error, "not supported"} 27 | 28 | # TODO: should we mock this too? 29 | def get_deposit_logs(_range), do: {:error, "not supported"} 30 | end 31 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/hard_fork_alias_injection.ex: -------------------------------------------------------------------------------- 1 | defmodule HardForkAliasInjection do 2 | @moduledoc false 3 | is_deneb = Application.compile_env!(:lambda_ethereum_consensus, :fork) == :deneb 4 | 5 | @compile {:inline, deneb?: 0} 6 | def deneb?(), do: unquote(is_deneb) 7 | 8 | @doc """ 9 | Compiles to the first argument if on deneb, otherwise to the second argument. 10 | 11 | ## Examples 12 | 13 | iex> HardForkAliasInjection.on_deneb(do: true, else: false) 14 | #{is_deneb} 15 | 16 | iex> HardForkAliasInjection.on_deneb(do: true) 17 | #{if is_deneb, do: true, else: nil} 18 | """ 19 | if is_deneb do 20 | defmacro on_deneb(do: do_clause) do 21 | do_clause 22 | end 23 | 24 | defmacro on_deneb(do: do_clause, else: _else_clause) do 25 | do_clause 26 | end 27 | else 28 | defmacro on_deneb(do: _do_clause) do 29 | nil 30 | end 31 | 32 | defmacro on_deneb(do: _do_clause, else: else_clause) do 33 | else_clause 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/logger/custom_console_logger.ex: -------------------------------------------------------------------------------- 1 | defmodule CustomConsoleLogger do 2 | @moduledoc """ 3 | Custom logger formatter for console output. 4 | """ 5 | alias LambdaEthereumConsensus.Utils 6 | 7 | @pattern Logger.Formatter.compile(" $time $message ") 8 | 9 | def format(level, message, timestamp, metadata) do 10 | formatted_metadata = format_metadata(metadata) 11 | 12 | [ 13 | format_level(level), 14 | Logger.Formatter.format(@pattern, level, message, timestamp, []), 15 | formatted_metadata, 16 | "\n" 17 | ] 18 | |> IO.iodata_to_binary() 19 | rescue 20 | err -> 21 | inspect(err) 22 | end 23 | 24 | defp level_color(:info), do: :green 25 | defp level_color(:warning), do: :yellow 26 | defp level_color(:error), do: :red 27 | defp level_color(_), do: :default_color 28 | 29 | defp format_level(level) do 30 | upcased = level |> Atom.to_string() |> String.upcase() 31 | IO.ANSI.format([level_color(level), upcased]) 32 | end 33 | 34 | def format_metadata(metadata) do 35 | Enum.map_join( 36 | metadata, 37 | " ", 38 | fn {key, value} -> 39 | IO.ANSI.format([ 40 | :bright, 41 | Atom.to_string(key), 42 | :reset, 43 | "=" <> format_metadata_value(key, value) 44 | ]) 45 | end 46 | ) 47 | end 48 | 49 | def format_metadata_value(:root, root) do 50 | Utils.format_shorten_binary(root) 51 | end 52 | 53 | def format_metadata_value(:bits, slot) do 54 | Utils.format_bitstring(slot) 55 | end 56 | 57 | def format_metadata_value(:slot, slot) do 58 | Integer.to_string(slot) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/logger/custom_logfmt_ex.ex: -------------------------------------------------------------------------------- 1 | defmodule CustomLogfmtEx do 2 | @moduledoc """ 3 | Custom logger formatter for logfmt output. 4 | """ 5 | 6 | alias LambdaEthereumConsensus.Utils 7 | 8 | def format(level, message, timestamp, metadata) do 9 | formatted_metadata = format_metadata(metadata) 10 | 11 | LogfmtEx.format(level, message, timestamp, formatted_metadata) 12 | end 13 | 14 | defp format_metadata(metadata) do 15 | metadata 16 | |> Keyword.replace_lazy(:root, &Utils.format_shorten_binary(&1)) 17 | |> Keyword.replace_lazy(:bits, &Utils.format_bitstring(&1)) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/p2p/gossip/handler.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.P2P.Gossip.Handler do 2 | @moduledoc """ 3 | Gossip handler behaviour 4 | """ 5 | alias Types.Store 6 | 7 | @callback handle_gossip_message(Store.t(), binary(), binary(), iodata()) :: 8 | Store.t() | {:error, any} 9 | end 10 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/p2p/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.P2P.Utils do 2 | @moduledoc """ 3 | Functions for encoding and decoding requests and responses. 4 | """ 5 | import Bitwise 6 | 7 | @spec encode_varint(integer()) :: binary() 8 | def encode_varint(int) do 9 | # PERF: use IO lists 10 | Protobuf.Wire.Varint.encode(int) 11 | |> IO.iodata_to_binary() 12 | end 13 | 14 | @spec decode_varint(binary()) :: {non_neg_integer(), binary()} 15 | # PERF: use `Protobuf.Wire.Varint.defdecoderp` macro for this 16 | # TODO: limit amount of recursive calls 17 | def decode_varint(bin), do: decode_varint(bin, 0, 0) 18 | 19 | defp decode_varint("", acc, _), do: {acc, ""} 20 | 21 | defp decode_varint(<<0::1, int::7, rest::binary>>, acc, shift) do 22 | {acc + (int <<< shift), rest} 23 | end 24 | 25 | defp decode_varint(<<1::1, int::7, rest::binary>>, acc, shift) do 26 | decode_varint(rest, acc + (int <<< shift), shift + 7) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/prom_ex.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.PromEx do 2 | @moduledoc """ 3 | This module integrates the PromEx library. It sets up PromEx plugins and pre-built dashboards for the node. 4 | """ 5 | use PromEx, otp_app: :lambda_ethereum_consensus 6 | 7 | @impl true 8 | def plugins() do 9 | [ 10 | PromEx.Plugins.Beam, 11 | LambdaEthereumConsensus.PromExPlugin 12 | ] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/state_transition/math.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.StateTransition.Math do 2 | @moduledoc """ 3 | Math functions 4 | """ 5 | 6 | @uint64_max 2 ** 64 - 1 7 | @uint32_max 2 ** 32 - 1 8 | 9 | @doc """ 10 | Return the largest integer ``x`` such that ``x**2 <= n``. 11 | """ 12 | @spec integer_squareroot(Types.uint64()) :: Types.uint64() 13 | def integer_squareroot(@uint64_max), do: @uint32_max 14 | 15 | def integer_squareroot(n) when is_integer(n) do 16 | compute_root(n, n, div(n + 1, 2)) 17 | end 18 | 19 | defp compute_root(n, x, y) when y < x do 20 | compute_root(n, y, div(y + div(n, y), 2)) 21 | end 22 | 23 | defp compute_root(_n, x, _y), do: x 24 | end 25 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/store/blobs.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Store.Blobs do 2 | @moduledoc """ 3 | Interface to `Store.Blobs`. 4 | """ 5 | require Logger 6 | 7 | alias LambdaEthereumConsensus.Store.BlobDb 8 | alias Types.BlobSidecar 9 | alias Types.BlockInfo 10 | 11 | @doc """ 12 | To be used when a series of blobs are downloaded. Stores each blob. 13 | """ 14 | @spec add_blobs([BlobSidecar.t()]) :: [Types.root()] 15 | def add_blobs(blobs) do 16 | blobs 17 | |> Enum.map(&BlobDb.store_blob/1) 18 | |> Enum.uniq() 19 | end 20 | 21 | @spec missing_for_block(BlockInfo.t()) :: [Types.BlobIdentifier.t()] 22 | def missing_for_block(%BlockInfo{root: root, signed_block: signed_block}) do 23 | signed_block.message.body.blob_kzg_commitments 24 | |> Stream.with_index() 25 | |> Enum.filter(&present?(&1, root)) 26 | |> Enum.map(&%Types.BlobIdentifier{block_root: root, index: elem(&1, 1)}) 27 | end 28 | 29 | defp present?({commitment, index}, block_root) do 30 | case BlobDb.get_blob_sidecar(block_root, index) do 31 | {:ok, %{kzg_commitment: ^commitment}} -> 32 | false 33 | 34 | _ -> 35 | true 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/store/block_states.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Store.BlockStates do 2 | @moduledoc """ 3 | Interface to `Store.block_states`. 4 | """ 5 | alias LambdaEthereumConsensus.Store.LRUCache 6 | alias LambdaEthereumConsensus.Store.StateDb 7 | alias Types.StateInfo 8 | 9 | @table :states_by_block_hash 10 | @max_entries 128 11 | @batch_prune_size 16 12 | 13 | ########################## 14 | ### Public API 15 | ########################## 16 | 17 | @spec start_link(any()) :: GenServer.on_start() 18 | def start_link(_opts) do 19 | LRUCache.start_link( 20 | table: @table, 21 | max_entries: @max_entries, 22 | batch_prune_size: @batch_prune_size, 23 | store_func: fn _k, v -> StateDb.store_state_info(v) end 24 | ) 25 | end 26 | 27 | def child_spec(opts) do 28 | %{ 29 | id: __MODULE__, 30 | start: {__MODULE__, :start_link, [opts]} 31 | } 32 | end 33 | 34 | @spec store_state_info(StateInfo.t()) :: :ok 35 | def store_state_info(state_info), do: LRUCache.put(@table, state_info.root, state_info) 36 | 37 | @spec get_state_info(Types.root()) :: StateInfo.t() | nil 38 | def get_state_info(block_root), do: LRUCache.get(@table, block_root, &fetch_state/1) 39 | 40 | @spec get_state_info!(Types.root()) :: StateInfo.t() 41 | def get_state_info!(block_root) do 42 | case get_state_info(block_root) do 43 | nil -> raise "State not found: 0x#{Base.encode16(block_root, case: :lower)}" 44 | v -> v 45 | end 46 | end 47 | 48 | ########################## 49 | ### Private Functions 50 | ########################## 51 | 52 | defp fetch_state(key) do 53 | case StateDb.get_state_by_block_root(key) do 54 | {:ok, value} -> value 55 | :not_found -> nil 56 | # TODO: handle this somehow? 57 | {:error, error} -> raise "database error #{inspect(error)}" 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/store/state_db/block_root_by_slot.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Store.StateDb.BlockRootBySlot do 2 | @moduledoc """ 3 | KvSchema that stores block roots indexed by slots. 4 | """ 5 | alias LambdaEthereumConsensus.Store.KvSchema 6 | require Logger 7 | use KvSchema, prefix: "statedb_block_root_by_slot" 8 | 9 | @impl KvSchema 10 | @spec encode_key(Types.slot()) :: {:ok, binary()} | {:error, binary()} 11 | def encode_key(slot), do: {:ok, <>} 12 | 13 | @impl KvSchema 14 | @spec decode_key(binary()) :: {:ok, integer()} | {:error, binary()} 15 | def decode_key(<>), do: {:ok, slot} 16 | 17 | def decode_key(other) do 18 | {:error, "[Block by slot] Could not decode slot, not 64 bit integer: #{other}"} 19 | end 20 | 21 | @impl KvSchema 22 | @spec encode_value(Types.root()) :: {:ok, Types.root()} | {:error, binary()} 23 | def encode_value(<<_::256>> = root), do: {:ok, root} 24 | 25 | @impl KvSchema 26 | @spec decode_value(Types.root()) :: {:ok, Types.root()} | {:error, binary()} 27 | def decode_value(<<_::256>> = root), do: {:ok, root} 28 | 29 | @spec get_last_slot_block_root() :: {:ok, Types.root()} | :not_found 30 | def get_last_slot_block_root() do 31 | with {:ok, first_slot} <- first_key() do 32 | fold_keys( 33 | first_slot, 34 | nil, 35 | fn slot, _acc -> 36 | case get(slot) do 37 | {:ok, block_root} -> 38 | block_root 39 | 40 | other -> 41 | Logger.error( 42 | "[Block pruning] Failed to find last slot root #{inspect(slot)}. Reason: #{inspect(other)}" 43 | ) 44 | end 45 | end, 46 | direction: :next, 47 | include_first: true 48 | ) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/store/state_db/state_info_by_root.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Store.StateDb.StateInfoByRoot do 2 | @moduledoc """ 3 | KvSchema that stores states indexed by their roots. 4 | """ 5 | 6 | alias LambdaEthereumConsensus.Store.KvSchema 7 | alias Types.StateInfo 8 | use KvSchema, prefix: "statedb_state_by_root" 9 | 10 | @impl KvSchema 11 | @spec encode_key(Types.root()) :: {:ok, binary()} 12 | def encode_key(root) when is_binary(root), do: {:ok, root} 13 | 14 | @impl KvSchema 15 | @spec decode_key(binary()) :: {:ok, Types.root()} 16 | def decode_key(root) when is_binary(root), do: {:ok, root} 17 | 18 | @impl KvSchema 19 | @spec encode_value(StateInfo.t()) :: {:ok, binary()} | {:error, binary()} 20 | def encode_value(%StateInfo{} = state_info), do: {:ok, StateInfo.encode(state_info)} 21 | 22 | @impl KvSchema 23 | @spec decode_value(binary()) :: {:ok, StateInfo.t()} | {:error, binary()} 24 | def decode_value(encoded_state) when is_binary(encoded_state), 25 | do: StateInfo.decode(encoded_state) 26 | end 27 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/store/state_db/state_root_by_block_root.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Store.StateDb.StateRootByBlockRoot do 2 | @moduledoc """ 3 | KvSchema that stores state roots indexed by BeaconBlock roots. 4 | """ 5 | 6 | alias LambdaEthereumConsensus.Store.KvSchema 7 | use KvSchema, prefix: "statedb_state_root_by_block_root" 8 | 9 | @impl KvSchema 10 | @spec encode_key(Types.root()) :: {:ok, binary()} 11 | def encode_key(<<_::256>> = root), do: {:ok, root} 12 | 13 | @impl KvSchema 14 | @spec decode_key(Types.root()) :: {:ok, Types.root()} 15 | def decode_key(<<_::256>> = root), do: {:ok, root} 16 | 17 | @impl KvSchema 18 | @spec encode_value(Types.root()) :: {:ok, binary()} 19 | def encode_value(<<_::256>> = root), do: {:ok, root} 20 | 21 | @impl KvSchema 22 | @spec decode_value(Types.root()) :: {:ok, Types.root()} | {:error, binary()} 23 | def decode_value(<<_::256>> = root), do: {:ok, root} 24 | end 25 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/store/store_db.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Store.StoreDb do 2 | @moduledoc """ 3 | Beacon node store storage. 4 | """ 5 | alias LambdaEthereumConsensus.Store.Db 6 | alias Types.Store 7 | 8 | @store_prefix "store" 9 | @snapshot_prefix "snapshot" 10 | 11 | @spec fetch_store() :: {:ok, Types.Store.t()} | :not_found 12 | def fetch_store() do 13 | :telemetry.span([:db, :latency], %{}, fn -> 14 | {get(@store_prefix), %{module: "fork_choice", action: "fetch"}} 15 | end) 16 | end 17 | 18 | @spec persist_store(Types.Store.t()) :: :ok 19 | def persist_store(%Types.Store{} = store) do 20 | :telemetry.span([:db, :latency], %{}, fn -> 21 | {put(@store_prefix, Store.remove_cache(store)), %{module: "fork_choice", action: "persist"}} 22 | end) 23 | end 24 | 25 | @spec fetch_genesis_time() :: {:ok, Types.uint64()} | :not_found 26 | def fetch_genesis_time() do 27 | with {:ok, store} <- fetch_store() do 28 | store.genesis_time 29 | end 30 | end 31 | 32 | @spec fetch_genesis_time!() :: Types.uint64() 33 | def fetch_genesis_time!() do 34 | {:ok, %{genesis_time: genesis_time}} = fetch_store() 35 | genesis_time 36 | end 37 | 38 | @spec fetch_deposits_snapshot() :: {:ok, Types.DepositTreeSnapshot.t()} | :not_found 39 | def fetch_deposits_snapshot(), do: get(@snapshot_prefix) 40 | 41 | @spec persist_deposits_snapshot(Types.DepositTreeSnapshot.t()) :: :ok 42 | def persist_deposits_snapshot(%Types.DepositTreeSnapshot{} = snapshot) do 43 | put(@snapshot_prefix, snapshot) 44 | end 45 | 46 | defp get(key) do 47 | with {:ok, value} <- Db.get(key) do 48 | {:ok, :erlang.binary_to_term(value)} 49 | end 50 | end 51 | 52 | defp put(key, value) do 53 | # Compress before storing. This doubles the time it takes to dump, but reduces size by 5 times. 54 | Db.put(key, :erlang.term_to_binary(value, [{:compressed, 1}])) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/lambda_ethereum_consensus/store/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Store.Utils do 2 | @moduledoc """ 3 | This module contains utility functions for interacting with storage. 4 | """ 5 | @spec get_key(binary, non_neg_integer | binary) :: binary 6 | def get_key(prefix, suffix) when is_integer(suffix) do 7 | # NOTE: this uses the last 64 bits of the suffix only 8 | prefix <> <> 9 | end 10 | 11 | def get_key(prefix, suffix) when is_binary(suffix) do 12 | prefix <> suffix 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/snappy.ex: -------------------------------------------------------------------------------- 1 | defmodule Snappy do 2 | @moduledoc """ 3 | Snappy frame compression and decompression. 4 | """ 5 | use Rustler, otp_app: :lambda_ethereum_consensus, crate: "snappy_nif" 6 | 7 | @spec decompress(binary) :: {:ok | :error, binary} 8 | def decompress(_bin), do: :erlang.nif_error(:nif_not_loaded) 9 | 10 | @spec compress(binary) :: {:ok | :error, binary} 11 | def compress(_bin), do: :erlang.nif_error(:nif_not_loaded) 12 | end 13 | -------------------------------------------------------------------------------- /lib/ssz_ex/error.ex: -------------------------------------------------------------------------------- 1 | defmodule SszEx.Error do 2 | @moduledoc """ 3 | Error messages for SszEx domain. 4 | """ 5 | alias SszEx.Error 6 | defstruct [:message, stacktrace: []] 7 | @type t :: %__MODULE__{message: String.t(), stacktrace: list()} 8 | 9 | def format(%Error{message: message, stacktrace: []}), do: "#{message}\n" 10 | 11 | def format(%Error{message: message, stacktrace: stacktrace}) do 12 | formatted_stacktrace = stacktrace |> Enum.join(".") 13 | "#{message}\nStacktrace: #{formatted_stacktrace}" 14 | end 15 | 16 | def add_container(%Error{message: message, stacktrace: stacktrace}, value) 17 | when is_struct(value) do 18 | new_trace = 19 | value.__struct__ |> Module.split() |> List.last() 20 | 21 | %Error{message: message, stacktrace: [new_trace | stacktrace]} 22 | end 23 | 24 | def add_container(%Error{} = error, :bool), do: error 25 | 26 | def add_container(%Error{message: message, stacktrace: stacktrace}, value) 27 | when is_atom(value) do 28 | new_trace = 29 | value |> Module.split() |> List.last() 30 | 31 | %Error{message: message, stacktrace: [new_trace | stacktrace]} 32 | end 33 | 34 | def add_container(%Error{message: message, stacktrace: stacktrace}, new_trace) do 35 | %Error{message: message, stacktrace: [new_trace | stacktrace]} 36 | end 37 | 38 | def add_container({:error, %Error{} = error}, new_trace), 39 | do: {:error, Error.add_container(error, new_trace)} 40 | 41 | def add_container(value, _module), do: value 42 | 43 | def add_trace(%Error{message: message, stacktrace: stacktrace}, new_trace) do 44 | %Error{message: message, stacktrace: [new_trace | stacktrace]} 45 | end 46 | 47 | def add_trace({:error, %Error{} = error}, new_trace), 48 | do: {:error, Error.add_trace(error, new_trace)} 49 | 50 | def add_trace(value, _module), do: value 51 | 52 | defimpl String.Chars, for: __MODULE__ do 53 | def to_string(error), do: Error.format(error) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/ssz_ex/hash.ex: -------------------------------------------------------------------------------- 1 | defmodule SszEx.Hash do 2 | @moduledoc """ 3 | The `Hash` module provides functions for computing cryptographic hashes, specifically for Merkle tree operations, according to the Ethereum Simple Serialize (SSZ) specifications. 4 | This module uses the SHA-256 hash algorithm. 5 | """ 6 | 7 | @bytes_per_chunk 32 8 | @max_merkle_tree_depth 64 9 | 10 | @doc """ 11 | Compute the roots of Merkle trees with all zero leaves and lengths from 0 to 64. 12 | """ 13 | def compute_zero_hashes() do 14 | Stream.iterate(<<0::size(8 * @bytes_per_chunk)>>, &hash_nodes(&1, &1)) 15 | |> Stream.take(@max_merkle_tree_depth + 1) 16 | |> Enum.join() 17 | end 18 | 19 | @doc """ 20 | Given the output of `compute_zero_hashes` as second argument, return the root of 21 | an all-zero Merkle tree of the given depth. 22 | """ 23 | def get_zero_hash(depth, zero_hashes) when depth in 0..@max_merkle_tree_depth do 24 | offset = (depth + 1) * @bytes_per_chunk - @bytes_per_chunk 25 | <<_::binary-size(offset), hash::binary-size(@bytes_per_chunk), _::binary>> = zero_hashes 26 | hash 27 | end 28 | 29 | @compile {:inline, hash: 1} 30 | @spec hash(iodata()) :: binary() 31 | def hash(data), do: :crypto.hash(:sha256, data) 32 | 33 | @spec hash_nodes(binary(), binary()) :: binary() 34 | def hash_nodes(left, right), do: hash(left <> right) 35 | end 36 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/attestation.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Attestation do 2 | @moduledoc """ 3 | Struct definition for `Attestation`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | 6 | aggregation_bits is a bit list that has the size of a committee. Each individual bit is set if 7 | the validator corresponding to that bit participated in attesting. 8 | """ 9 | alias LambdaEthereumConsensus.Utils.BitList 10 | alias LambdaEthereumConsensus.Utils.BitVector 11 | 12 | use LambdaEthereumConsensus.Container 13 | 14 | fields = [ 15 | :aggregation_bits, 16 | :data, 17 | :signature, 18 | :committee_bits 19 | ] 20 | 21 | @enforce_keys fields 22 | defstruct fields 23 | 24 | @type t :: %__MODULE__{ 25 | # [Modified in Electra:EIP7549] 26 | aggregation_bits: Types.bitlist(), 27 | data: Types.AttestationData.t(), 28 | signature: Types.bls_signature(), 29 | # [New in Electra:EIP7549] 30 | committee_bits: BitVector.t() 31 | } 32 | 33 | @impl LambdaEthereumConsensus.Container 34 | def schema() do 35 | [ 36 | {:aggregation_bits, 37 | {:bitlist, 38 | ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE") * ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}}, 39 | {:data, Types.AttestationData}, 40 | {:signature, TypeAliases.bls_signature()}, 41 | {:committee_bits, {:bitvector, ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}} 42 | ] 43 | end 44 | 45 | def encode(%__MODULE__{} = map) do 46 | map 47 | |> Map.update!(:aggregation_bits, &BitList.to_bytes/1) 48 | |> Map.update!(:committee_bits, &BitVector.to_bytes/1) 49 | end 50 | 51 | def decode(%__MODULE__{} = map) do 52 | map 53 | |> Map.update!(:aggregation_bits, &BitList.new/1) 54 | |> Map.update!(:committee_bits, fn bits -> 55 | BitVector.new(bits, ChainSpec.get("MAX_COMMITTEES_PER_SLOT")) 56 | end) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/attestation_data.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.AttestationData do 2 | @moduledoc """ 3 | Struct definition for `AttestationData`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :slot, 11 | :index, 12 | :beacon_block_root, 13 | :source, 14 | :target 15 | ] 16 | 17 | @enforce_keys fields 18 | defstruct fields 19 | 20 | @type t :: %__MODULE__{ 21 | slot: Types.slot(), 22 | index: Types.commitee_index(), 23 | beacon_block_root: Types.root(), 24 | source: Types.Checkpoint.t(), 25 | target: Types.Checkpoint.t() 26 | } 27 | 28 | @impl LambdaEthereumConsensus.Container 29 | def schema() do 30 | [ 31 | {:slot, TypeAliases.slot()}, 32 | {:index, TypeAliases.commitee_index()}, 33 | {:beacon_block_root, TypeAliases.root()}, 34 | {:source, Types.Checkpoint}, 35 | {:target, Types.Checkpoint} 36 | ] 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/attester_slashing.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.AttesterSlashing do 2 | @moduledoc """ 3 | Struct definition for `AttesterSlashing`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :attestation_1, 10 | :attestation_2 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | attestation_1: Types.IndexedAttestation.t(), 18 | attestation_2: Types.IndexedAttestation.t() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:attestation_1, Types.IndexedAttestation}, 25 | {:attestation_2, Types.IndexedAttestation} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/beacon_block.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.BeaconBlock do 2 | @moduledoc """ 3 | Struct definition for `BeaconBlock`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :slot, 10 | :proposer_index, 11 | :parent_root, 12 | :state_root, 13 | :body 14 | ] 15 | 16 | @enforce_keys fields 17 | defstruct fields 18 | 19 | @type t :: %__MODULE__{ 20 | slot: Types.slot(), 21 | proposer_index: Types.validator_index(), 22 | parent_root: Types.root(), 23 | state_root: Types.root(), 24 | body: Types.BeaconBlockBody.t() 25 | } 26 | 27 | @impl LambdaEthereumConsensus.Container 28 | def schema() do 29 | [ 30 | {:slot, TypeAliases.slot()}, 31 | {:proposer_index, TypeAliases.validator_index()}, 32 | {:parent_root, TypeAliases.root()}, 33 | {:state_root, TypeAliases.root()}, 34 | {:body, Types.BeaconBlockBody} 35 | ] 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/beacon_block_header.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.BeaconBlockHeader do 2 | @moduledoc """ 3 | Struct definition for `BeaconBlockHeader`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :slot, 10 | :proposer_index, 11 | :parent_root, 12 | :state_root, 13 | :body_root 14 | ] 15 | 16 | @enforce_keys fields 17 | defstruct fields 18 | 19 | @type t :: %__MODULE__{ 20 | slot: Types.slot(), 21 | proposer_index: Types.validator_index(), 22 | parent_root: Types.root(), 23 | state_root: Types.root(), 24 | body_root: Types.root() 25 | } 26 | 27 | @impl LambdaEthereumConsensus.Container 28 | def schema() do 29 | [ 30 | {:slot, TypeAliases.slot()}, 31 | {:proposer_index, TypeAliases.validator_index()}, 32 | {:parent_root, TypeAliases.root()}, 33 | {:state_root, TypeAliases.root()}, 34 | {:body_root, TypeAliases.root()} 35 | ] 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/blob_identifier.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.BlobIdentifier do 2 | @moduledoc """ 3 | Struct definition for `BlobIdentifier`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :block_root, 10 | :index 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | block_root: Types.root(), 18 | index: Types.blob_index() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:block_root, TypeAliases.root()}, 25 | {:index, TypeAliases.blob_index()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/blob_sidecar.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.BlobSidecar do 2 | @moduledoc """ 3 | Struct definition for `BlobSidecar`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :index, 10 | :blob, 11 | :kzg_commitment, 12 | :kzg_proof, 13 | :signed_block_header, 14 | :kzg_commitment_inclusion_proof 15 | ] 16 | 17 | @enforce_keys fields 18 | defstruct fields 19 | 20 | @type t :: %__MODULE__{ 21 | index: Types.blob_index(), 22 | blob: Types.blob(), 23 | kzg_commitment: Types.kzg_commitment(), 24 | kzg_proof: Types.kzg_proof(), 25 | signed_block_header: Types.SignedBeaconBlockHeader.t(), 26 | # Max size: KZG_COMMITMENT_INCLUSION_PROOF_DEPTH 27 | kzg_commitment_inclusion_proof: list(Types.bytes32()) 28 | } 29 | 30 | @impl LambdaEthereumConsensus.Container 31 | def schema() do 32 | [ 33 | index: TypeAliases.blob_index(), 34 | blob: TypeAliases.blob(), 35 | kzg_commitment: TypeAliases.kzg_commitment(), 36 | kzg_proof: TypeAliases.kzg_proof(), 37 | signed_block_header: Types.SignedBeaconBlockHeader, 38 | kzg_commitment_inclusion_proof: 39 | {:vector, TypeAliases.bytes32(), ChainSpec.get("KZG_COMMITMENT_INCLUSION_PROOF_DEPTH")} 40 | ] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/bls_to_execution_change.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.BLSToExecutionChange do 2 | @moduledoc """ 3 | Struct definition for `BLSToExecutionChange`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :validator_index, 10 | :from_bls_pubkey, 11 | :to_execution_address 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | validator_index: Types.validator_index(), 19 | from_bls_pubkey: Types.bls_pubkey(), 20 | to_execution_address: Types.execution_address() 21 | } 22 | 23 | @impl LambdaEthereumConsensus.Container 24 | def schema() do 25 | [ 26 | {:validator_index, TypeAliases.validator_index()}, 27 | {:from_bls_pubkey, TypeAliases.bls_pubkey()}, 28 | {:to_execution_address, TypeAliases.execution_address()} 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/checkpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Checkpoint do 2 | @moduledoc """ 3 | Struct definition for `Checkpoint`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :root, 11 | :epoch 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | epoch: Types.epoch(), 19 | root: Types.root() 20 | } 21 | 22 | @impl LambdaEthereumConsensus.Container 23 | def schema() do 24 | [ 25 | {:epoch, TypeAliases.epoch()}, 26 | {:root, TypeAliases.root()} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/consolidation_request.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.ConsolidationRequest do 2 | @moduledoc """ 3 | Struct definition for `ConsolidationRequest`. 4 | Added in Electra fork (EIP7251). 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [:source_address, :source_pubkey, :target_pubkey] 10 | @enforce_keys fields 11 | defstruct fields 12 | 13 | @type t :: %__MODULE__{ 14 | source_address: Types.execution_address(), 15 | source_pubkey: Types.bls_pubkey(), 16 | target_pubkey: Types.bls_pubkey() 17 | } 18 | 19 | @impl LambdaEthereumConsensus.Container 20 | def schema() do 21 | [ 22 | {:source_address, TypeAliases.execution_address()}, 23 | {:source_pubkey, TypeAliases.bls_pubkey()}, 24 | {:target_pubkey, TypeAliases.bls_pubkey()} 25 | ] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/deposit.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Deposit do 2 | @moduledoc """ 3 | Struct definition for `Deposit`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :proof, 10 | :data 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | # max size is 33 18 | proof: list(Types.bytes32()), 19 | data: Types.DepositData.t() 20 | } 21 | 22 | @spec get_validator_from_deposit(Types.bls_pubkey(), Types.bytes32(), Types.uint64()) :: 23 | Types.Validator.t() 24 | def get_validator_from_deposit(pubkey, withdrawal_credentials, amount) do 25 | far_future_epoch = Constants.far_future_epoch() 26 | 27 | validator = %Types.Validator{ 28 | pubkey: pubkey, 29 | withdrawal_credentials: withdrawal_credentials, 30 | activation_eligibility_epoch: far_future_epoch, 31 | activation_epoch: far_future_epoch, 32 | exit_epoch: far_future_epoch, 33 | withdrawable_epoch: far_future_epoch, 34 | effective_balance: 0, 35 | slashed: false 36 | } 37 | 38 | effective_balance = 39 | min( 40 | amount - rem(amount, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")), 41 | Types.Validator.get_max_effective_balance(validator) 42 | ) 43 | 44 | %Types.Validator{ 45 | validator 46 | | effective_balance: effective_balance 47 | } 48 | end 49 | 50 | @impl LambdaEthereumConsensus.Container 51 | def schema() do 52 | [ 53 | {:proof, {:vector, TypeAliases.bytes32(), 33}}, 54 | {:data, Types.DepositData} 55 | ] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/deposit_data.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.DepositData do 2 | @moduledoc """ 3 | Struct definition for `DepositData`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :pubkey, 10 | :withdrawal_credentials, 11 | :amount, 12 | :signature 13 | ] 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | pubkey: Types.bls_pubkey(), 20 | withdrawal_credentials: Types.bytes32(), 21 | amount: Types.gwei(), 22 | # Signing over DepositMessage 23 | signature: Types.bls_signature() 24 | } 25 | 26 | @impl LambdaEthereumConsensus.Container 27 | def schema() do 28 | [ 29 | {:pubkey, TypeAliases.bls_pubkey()}, 30 | {:withdrawal_credentials, TypeAliases.bytes32()}, 31 | {:amount, TypeAliases.gwei()}, 32 | {:signature, TypeAliases.bls_signature()} 33 | ] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/deposit_message.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.DepositMessage do 2 | @moduledoc """ 3 | Struct definition for `DepositMessage`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | alias LambdaEthereumConsensus.StateTransition.Misc 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :pubkey, 11 | :withdrawal_credentials, 12 | :amount 13 | ] 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | pubkey: Types.bls_pubkey(), 20 | withdrawal_credentials: Types.bytes32(), 21 | amount: Types.gwei() 22 | } 23 | 24 | @impl LambdaEthereumConsensus.Container 25 | def schema() do 26 | [ 27 | {:pubkey, TypeAliases.bls_pubkey()}, 28 | {:withdrawal_credentials, TypeAliases.bytes32()}, 29 | {:amount, TypeAliases.gwei()} 30 | ] 31 | end 32 | 33 | @spec valid_deposit_signature?( 34 | Types.bls_pubkey(), 35 | Types.bytes32(), 36 | Types.gwei(), 37 | Types.bls_signature() 38 | ) :: boolean() 39 | def valid_deposit_signature?(pubkey, withdrawal_credentials, amount, signature) do 40 | deposit_message = %__MODULE__{ 41 | pubkey: pubkey, 42 | withdrawal_credentials: withdrawal_credentials, 43 | amount: amount 44 | } 45 | 46 | domain = Misc.compute_domain(Constants.domain_deposit()) 47 | signing_root = Misc.compute_signing_root(deposit_message, domain) 48 | 49 | Bls.valid?(pubkey, signing_root, signature) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/deposit_request.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.DepositRequest do 2 | @moduledoc """ 3 | Struct definition for `DepositRequest`. 4 | Added in Electra fork (EIP6110). 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [:pubkey, :withdrawal_credentials, :amount, :signature, :index] 10 | @enforce_keys fields 11 | defstruct fields 12 | 13 | @type t :: %__MODULE__{ 14 | pubkey: Types.bls_pubkey(), 15 | withdrawal_credentials: Types.bytes32(), 16 | amount: Types.gwei(), 17 | signature: Types.bls_signature(), 18 | index: Types.uint64() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:pubkey, TypeAliases.bls_pubkey()}, 25 | {:withdrawal_credentials, TypeAliases.bytes32()}, 26 | {:amount, TypeAliases.gwei()}, 27 | {:signature, TypeAliases.bls_signature()}, 28 | {:index, TypeAliases.uint64()} 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/eth1_block.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Eth1Block do 2 | @moduledoc """ 3 | Struct definition for `Eth1Block`. 4 | """ 5 | use LambdaEthereumConsensus.Container 6 | 7 | fields = [ 8 | :timestamp, 9 | :deposit_root, 10 | :deposit_count 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | timestamp: Types.uint64(), 18 | deposit_root: Types.root(), 19 | deposit_count: Types.uint64() 20 | } 21 | 22 | @impl LambdaEthereumConsensus.Container 23 | def schema() do 24 | [ 25 | timestamp: TypeAliases.uint64(), 26 | deposit_root: TypeAliases.root(), 27 | deposit_count: TypeAliases.uint64() 28 | ] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/eth1_data.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Eth1Data do 2 | @moduledoc """ 3 | Struct definition for `Eth1Data`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :deposit_root, 10 | :deposit_count, 11 | :block_hash 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | deposit_root: Types.root(), 19 | deposit_count: Types.uint64(), 20 | block_hash: Types.hash32() 21 | } 22 | 23 | @impl LambdaEthereumConsensus.Container 24 | def schema() do 25 | [ 26 | deposit_root: TypeAliases.root(), 27 | deposit_count: TypeAliases.uint64(), 28 | block_hash: TypeAliases.hash32() 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/execution_requests.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.ExecutionRequests do 2 | @moduledoc """ 3 | Struct definition for `ExecutionRequests`. 4 | Added in Electra fork. 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [:deposits, :withdrawals, :consolidations] 10 | @enforce_keys fields 11 | defstruct fields 12 | 13 | @type t :: %__MODULE__{ 14 | deposits: list(Types.DepositRequest.t()), 15 | withdrawals: list(Types.WithdrawalRequest.t()), 16 | consolidations: list(Types.ConsolidationRequest.t()) 17 | } 18 | 19 | @impl LambdaEthereumConsensus.Container 20 | def schema() do 21 | [ 22 | {:deposits, 23 | {:list, Types.DepositRequest, ChainSpec.get("MAX_DEPOSIT_REQUESTS_PER_PAYLOAD")}}, 24 | {:withdrawals, 25 | {:list, Types.WithdrawalRequest, ChainSpec.get("MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD")}}, 26 | {:consolidations, 27 | {:list, Types.ConsolidationRequest, 28 | ChainSpec.get("MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD")}} 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/fork.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Fork do 2 | @moduledoc """ 3 | Struct definition for `Fork`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :previous_version, 11 | :current_version, 12 | :epoch 13 | ] 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | previous_version: Types.version(), 20 | current_version: Types.version(), 21 | epoch: Types.epoch() 22 | } 23 | 24 | @impl LambdaEthereumConsensus.Container 25 | def schema() do 26 | [ 27 | {:previous_version, TypeAliases.version()}, 28 | {:current_version, TypeAliases.version()}, 29 | {:epoch, TypeAliases.epoch()} 30 | ] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/fork_data.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.ForkData do 2 | @moduledoc """ 3 | Struct definition for `ForkData`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :current_version, 11 | :genesis_validators_root 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | current_version: Types.version(), 19 | genesis_validators_root: Types.root() 20 | } 21 | 22 | @impl LambdaEthereumConsensus.Container 23 | def schema() do 24 | [ 25 | {:current_version, TypeAliases.version()}, 26 | {:genesis_validators_root, TypeAliases.root()} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/historical_batch.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.HistoricalBatch do 2 | @moduledoc """ 3 | Struct definition for `HistoricalBatch`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :block_roots, 10 | :state_roots 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | # size SLOTS_PER_HISTORICAL_ROOT 8192 18 | block_roots: list(Types.root()), 19 | state_roots: list(Types.root()) 20 | } 21 | 22 | @impl LambdaEthereumConsensus.Container 23 | def schema() do 24 | [ 25 | {:block_roots, {:vector, TypeAliases.root(), ChainSpec.get("SLOTS_PER_HISTORICAL_ROOT")}}, 26 | {:state_roots, {:vector, TypeAliases.root(), ChainSpec.get("SLOTS_PER_HISTORICAL_ROOT")}} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/historical_summary.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.HistoricalSummary do 2 | @moduledoc """ 3 | Struct definition for `HistoricalSummary`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | `HistoricalSummary` matches the components of the phase0 `HistoricalBatch` 6 | making the two hash_tree_root-compatible. 7 | """ 8 | use LambdaEthereumConsensus.Container 9 | 10 | fields = [ 11 | :block_summary_root, 12 | :state_summary_root 13 | ] 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | block_summary_root: Types.root(), 20 | state_summary_root: Types.root() 21 | } 22 | 23 | @impl LambdaEthereumConsensus.Container 24 | def schema() do 25 | [ 26 | {:block_summary_root, TypeAliases.root()}, 27 | {:state_summary_root, TypeAliases.root()} 28 | ] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/indexed_attestation.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.IndexedAttestation do 2 | @moduledoc """ 3 | Struct definition for `IndexedAttestation`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | 6 | attesting_indices is a list of indices, each one of them spanning from 0 to the amount of 7 | validators in the chain - 1 (it's a global index). Only the validators that participated 8 | are included, so not the full committee is present in the list, and they should be sorted. This 9 | field is the only difference with respect to Types.Attestation. 10 | 11 | To verify an attestation, it needs to be converted to an indexed one (get_indexed_attestation), 12 | with the attesting indices sorted. The bls signature can then be used to verify for the result. 13 | """ 14 | use LambdaEthereumConsensus.Container 15 | 16 | fields = [ 17 | :attesting_indices, 18 | :data, 19 | :signature 20 | ] 21 | 22 | @enforce_keys fields 23 | defstruct fields 24 | 25 | @type t :: %__MODULE__{ 26 | # [Modified in Electra:EIP7549] 27 | attesting_indices: list(Types.validator_index()), 28 | data: Types.AttestationData.t(), 29 | signature: Types.bls_signature() 30 | } 31 | 32 | @impl LambdaEthereumConsensus.Container 33 | def schema() do 34 | [ 35 | {:attesting_indices, 36 | {:list, TypeAliases.validator_index(), 37 | ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE") * ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}}, 38 | {:data, Types.AttestationData}, 39 | {:signature, TypeAliases.bls_signature()} 40 | ] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/pending_attestation.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.PendingAttestation do 2 | @moduledoc """ 3 | Struct definition for `PendingAttestation`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | alias LambdaEthereumConsensus.Utils.BitList 7 | 8 | use LambdaEthereumConsensus.Container 9 | 10 | fields = [ 11 | :aggregation_bits, 12 | :data, 13 | :inclusion_delay, 14 | :proposer_index 15 | ] 16 | 17 | @enforce_keys fields 18 | defstruct fields 19 | 20 | @type t :: %__MODULE__{ 21 | # max size is MAX_VALIDATORS_PER_COMMITTEE 22 | aggregation_bits: Types.bitlist(), 23 | data: Types.AttestationData.t(), 24 | inclusion_delay: Types.slot(), 25 | proposer_index: Types.validator_index() 26 | } 27 | 28 | @impl LambdaEthereumConsensus.Container 29 | def schema() do 30 | [ 31 | {:aggregation_bits, {:bitlist, ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE")}}, 32 | {:data, Types.AttestationData}, 33 | {:inclusion_delay, TypeAliases.slot()}, 34 | {:proposer_index, TypeAliases.validator_index()} 35 | ] 36 | end 37 | 38 | def encode(%__MODULE__{} = map) do 39 | Map.update!(map, :aggregation_bits, &BitList.to_bytes/1) 40 | end 41 | 42 | def decode(%__MODULE__{} = map) do 43 | Map.update!(map, :aggregation_bits, &BitList.new/1) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/pending_consolidation.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.PendingConsolidation do 2 | @moduledoc """ 3 | Struct definition for `PendingConsolidation`. 4 | Added in Electra fork (EIP7251). 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [:source_index, :target_index] 10 | @enforce_keys fields 11 | defstruct fields 12 | 13 | @type t :: %__MODULE__{ 14 | source_index: Types.validator_index(), 15 | target_index: Types.validator_index() 16 | } 17 | 18 | @impl LambdaEthereumConsensus.Container 19 | def schema() do 20 | [ 21 | {:source_index, TypeAliases.validator_index()}, 22 | {:target_index, TypeAliases.validator_index()} 23 | ] 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/pending_deposit.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.PendingDeposit do 2 | @moduledoc """ 3 | Struct definition for `PendingDeposit`. 4 | Added in Electra fork (EIP7251). 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [:pubkey, :withdrawal_credentials, :amount, :signature, :slot] 10 | @enforce_keys fields 11 | defstruct fields 12 | 13 | @type t :: %__MODULE__{ 14 | pubkey: Types.bls_pubkey(), 15 | withdrawal_credentials: Types.bytes32(), 16 | amount: Types.gwei(), 17 | signature: Types.bls_signature(), 18 | slot: Types.slot() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:pubkey, TypeAliases.bls_pubkey()}, 25 | {:withdrawal_credentials, TypeAliases.bytes32()}, 26 | {:amount, TypeAliases.gwei()}, 27 | {:signature, TypeAliases.bls_signature()}, 28 | {:slot, TypeAliases.slot()} 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/pending_partial_withdrawal.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.PendingPartialWithdrawal do 2 | @moduledoc """ 3 | Struct definition for `PendingPartialWithdrawal`. 4 | Added in Electra fork (EIP7251). 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [:validator_index, :amount, :withdrawable_epoch] 10 | @enforce_keys fields 11 | defstruct fields 12 | 13 | @type t :: %__MODULE__{ 14 | validator_index: Types.validator_index(), 15 | amount: Types.gwei(), 16 | withdrawable_epoch: Types.epoch() 17 | } 18 | 19 | @impl LambdaEthereumConsensus.Container 20 | def schema() do 21 | [ 22 | {:validator_index, TypeAliases.validator_index()}, 23 | {:amount, TypeAliases.gwei()}, 24 | {:withdrawable_epoch, TypeAliases.epoch()} 25 | ] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/proposer_slashing.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.ProposerSlashing do 2 | @moduledoc """ 3 | Struct definition for `ProposerSlashing`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :signed_header_1, 10 | :signed_header_2 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | signed_header_1: Types.SignedBeaconBlockHeader.t(), 18 | signed_header_2: Types.SignedBeaconBlockHeader.t() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:signed_header_1, Types.SignedBeaconBlockHeader}, 25 | {:signed_header_2, Types.SignedBeaconBlockHeader} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/signed_beacon_block.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SignedBeaconBlock do 2 | @moduledoc """ 3 | Struct definition for `SignedBeaconBlock`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :message, 10 | :signature 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | message: Types.BeaconBlock.t(), 18 | signature: Types.bls_signature() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:message, Types.BeaconBlock}, 25 | {:signature, TypeAliases.bls_signature()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/signed_beacon_block_header.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SignedBeaconBlockHeader do 2 | @moduledoc """ 3 | Struct definition for `SignedBeaconBlockHeader`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :message, 10 | :signature 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | message: Types.BeaconBlockHeader.t(), 18 | signature: Types.bls_signature() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:message, Types.BeaconBlockHeader}, 25 | {:signature, TypeAliases.bls_signature()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/signed_bls_to_execution_change.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SignedBLSToExecutionChange do 2 | @moduledoc """ 3 | Struct definition for `SignedBLSToExecutionChange`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :message, 10 | :signature 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | message: Types.BLSToExecutionChange.t(), 18 | signature: Types.bls_signature() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:message, Types.BLSToExecutionChange}, 25 | {:signature, TypeAliases.bls_signature()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/signed_voluntary_exit.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SignedVoluntaryExit do 2 | @moduledoc """ 3 | Struct definition for `SignedVoluntaryExit`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :message, 10 | :signature 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | message: Types.VoluntaryExit.t(), 18 | signature: Types.bls_signature() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:message, Types.VoluntaryExit}, 25 | {:signature, TypeAliases.bls_signature()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/signing_data.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SigningData do 2 | @moduledoc """ 3 | Struct definition for `SigningData`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :object_root, 10 | :domain 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | object_root: Types.root(), 18 | domain: Types.domain() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:object_root, TypeAliases.root()}, 25 | {:domain, TypeAliases.domain()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/single_attestation.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SingleAttestation do 2 | @moduledoc """ 3 | Struct definition for `SingleAttestation`. Added in Electra. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :committee_index, 11 | :attester_index, 12 | :data, 13 | :signature 14 | ] 15 | 16 | @enforce_keys fields 17 | defstruct fields 18 | 19 | @type t :: %__MODULE__{ 20 | committee_index: Types.commitee_index(), 21 | attester_index: Types.validator_index(), 22 | data: Types.AttestationData.t(), 23 | signature: Types.bls_signature() 24 | } 25 | 26 | @impl LambdaEthereumConsensus.Container 27 | def schema() do 28 | [ 29 | {:committee_index, TypeAliases.commitee_index()}, 30 | {:attester_index, TypeAliases.validator_index()}, 31 | {:data, Types.AttestationData}, 32 | {:signature, TypeAliases.bls_signature()} 33 | ] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/sync_aggregate.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SyncAggregate do 2 | @moduledoc """ 3 | Struct definition for `SyncAggregate`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | alias LambdaEthereumConsensus.Utils.BitVector 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :sync_committee_bits, 11 | :sync_committee_signature 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | # max size SYNC_COMMITTEE_SIZE 19 | sync_committee_bits: BitVector.t(), 20 | sync_committee_signature: Types.bls_signature() 21 | } 22 | 23 | def encode(%__MODULE__{} = map) do 24 | Map.update!(map, :sync_committee_bits, &BitVector.to_bytes/1) 25 | end 26 | 27 | def decode(%__MODULE__{} = map) do 28 | Map.update!(map, :sync_committee_bits, fn bits -> 29 | BitVector.new(bits, ChainSpec.get("SYNC_COMMITTEE_SIZE")) 30 | end) 31 | end 32 | 33 | @impl LambdaEthereumConsensus.Container 34 | def schema() do 35 | [ 36 | {:sync_committee_bits, {:bitvector, ChainSpec.get("SYNC_COMMITTEE_SIZE")}}, 37 | {:sync_committee_signature, TypeAliases.bls_signature()} 38 | ] 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/sync_aggregator_selection_data.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SyncAggregatorSelectionData do 2 | @moduledoc """ 3 | Struct definition for `SyncAggregatorSelectionData`. 4 | """ 5 | use LambdaEthereumConsensus.Container 6 | 7 | fields = [:slot, :subcommittee_index] 8 | 9 | @enforce_keys fields 10 | defstruct fields 11 | 12 | @type t :: %__MODULE__{ 13 | slot: Types.slot(), 14 | subcommittee_index: Types.uint64() 15 | } 16 | 17 | @impl LambdaEthereumConsensus.Container 18 | def schema() do 19 | [ 20 | slot: TypeAliases.slot(), 21 | subcommittee_index: TypeAliases.uint64() 22 | ] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/sync_committee.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SyncCommittee do 2 | @moduledoc """ 3 | Struct definition for `SyncCommittee`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :pubkeys, 11 | :aggregate_pubkey 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | # max size SYNC_COMMITTEE_SIZE 19 | pubkeys: list(Types.bls_pubkey()), 20 | aggregate_pubkey: Types.bls_pubkey() 21 | } 22 | 23 | @impl LambdaEthereumConsensus.Container 24 | def schema() do 25 | [ 26 | {:pubkeys, {:vector, TypeAliases.bls_pubkey(), ChainSpec.get("SYNC_COMMITTEE_SIZE")}}, 27 | {:aggregate_pubkey, TypeAliases.bls_pubkey()} 28 | ] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/voluntary_exit.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.VoluntaryExit do 2 | @moduledoc """ 3 | Struct definition for `VoluntaryExit`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :epoch, 10 | :validator_index 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | epoch: Types.epoch(), 18 | validator_index: Types.validator_index() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:epoch, TypeAliases.epoch()}, 25 | {:validator_index, TypeAliases.validator_index()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/withdrawal.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Withdrawal do 2 | @moduledoc """ 3 | Struct definition for `Withdrawal`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :index, 10 | :validator_index, 11 | :address, 12 | :amount 13 | ] 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | index: Types.withdrawal_index(), 20 | validator_index: Types.validator_index(), 21 | address: Types.execution_address(), 22 | amount: Types.gwei() 23 | } 24 | 25 | @impl LambdaEthereumConsensus.Container 26 | def schema() do 27 | [ 28 | {:index, TypeAliases.withdrawal_index()}, 29 | {:validator_index, TypeAliases.validator_index()}, 30 | {:address, TypeAliases.execution_address()}, 31 | {:amount, TypeAliases.gwei()} 32 | ] 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/types/beacon_chain/withdrawal_request.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.WithdrawalRequest do 2 | @moduledoc """ 3 | Struct definition for `WithdrawalRequest`. 4 | Added in Electra fork (EIP7251:EIP7002). 5 | """ 6 | 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [:source_address, :validator_pubkey, :amount] 10 | @enforce_keys fields 11 | defstruct fields 12 | 13 | @type t :: %__MODULE__{ 14 | source_address: Types.execution_address(), 15 | validator_pubkey: Types.bls_pubkey(), 16 | amount: Types.gwei() 17 | } 18 | 19 | @impl LambdaEthereumConsensus.Container 20 | def schema() do 21 | [ 22 | {:source_address, TypeAliases.execution_address()}, 23 | {:validator_pubkey, TypeAliases.bls_pubkey()}, 24 | {:amount, TypeAliases.gwei()} 25 | ] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/types/blobdata.ex: -------------------------------------------------------------------------------- 1 | # TODO: maybe allow doing this without declaring a new struct? 2 | defmodule Types.Blobdata do 3 | @moduledoc """ 4 | BlobSidecar data optimized for usage in `on_block`. 5 | This is needed to run the spectests. 6 | """ 7 | @behaviour LambdaEthereumConsensus.Container 8 | 9 | fields = [:blob, :proof] 10 | @enforce_keys fields 11 | defstruct fields 12 | 13 | @type t :: %__MODULE__{blob: Types.blob(), proof: Types.kzg_proof()} 14 | 15 | @impl LambdaEthereumConsensus.Container 16 | def schema() do 17 | [ 18 | blob: TypeAliases.blob(), 19 | proof: TypeAliases.kzg_proof() 20 | ] 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/types/deposit_tree_snapshot.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.DepositTreeSnapshot do 2 | @moduledoc """ 3 | Struct definition for a deposit snapshot, as defined in EIP-4881. 4 | """ 5 | 6 | fields = [ 7 | :finalized, 8 | :deposit_root, 9 | :deposit_count, 10 | :execution_block_hash, 11 | :execution_block_height 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | # Max size is 33 19 | finalized: list(Types.hash32()), 20 | deposit_root: Types.hash32(), 21 | deposit_count: Types.uint64(), 22 | execution_block_hash: Types.hash32(), 23 | execution_block_height: Types.uint64() 24 | } 25 | 26 | def for_empty_tree(block_hash, block_height) do 27 | empty_root = Types.DepositTree.new() |> Types.DepositTree.get_root() 28 | 29 | %__MODULE__{ 30 | finalized: [], 31 | deposit_root: empty_root, 32 | deposit_count: 0, 33 | execution_block_hash: block_hash, 34 | execution_block_height: block_height 35 | } 36 | end 37 | 38 | def get_eth1_data(%__MODULE__{} = snapshot) do 39 | %Types.Eth1Data{ 40 | deposit_root: snapshot.deposit_root, 41 | deposit_count: snapshot.deposit_count, 42 | block_hash: snapshot.execution_block_hash 43 | } 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/types/epoch.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Epoch do 2 | @moduledoc """ 3 | Alias for `Types.epoch`. Is used when explicit typing is needed. 4 | """ 5 | end 6 | -------------------------------------------------------------------------------- /lib/types/execution_chain/blobs_bundle.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.BlobsBundle do 2 | @moduledoc """ 3 | Struct received by `ExecutionClient.get_payload`. 4 | """ 5 | 6 | @enforce_keys [:blobs, :commitments, :proofs] 7 | defstruct [:blobs, :commitments, :proofs] 8 | 9 | @type t :: %__MODULE__{ 10 | blobs: list(Types.blob()), 11 | commitments: list(Types.kzg_commitment()), 12 | proofs: list(Types.kzg_proof()) 13 | } 14 | end 15 | -------------------------------------------------------------------------------- /lib/types/execution_chain/new_payload_request.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.NewPayloadRequest do 2 | @moduledoc """ 3 | Struct received by `ExecutionClient.verify_and_notify_new_payload`. 4 | """ 5 | alias Types.ExecutionPayload 6 | 7 | @enforce_keys [:execution_payload] 8 | defstruct [:execution_payload, :versioned_hashes, :parent_beacon_block_root] 9 | 10 | @type t :: %__MODULE__{ 11 | execution_payload: ExecutionPayload.t(), 12 | versioned_hashes: list(Types.bytes32()) | nil, 13 | parent_beacon_block_root: Types.root() | nil 14 | } 15 | end 16 | -------------------------------------------------------------------------------- /lib/types/p2p/beacon_blocks_by_range_request.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.BeaconBlocksByRangeRequest do 2 | @moduledoc """ 3 | Struct definition for `BeaconBlocksByRangeRequest`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | @enforce_keys [:start_slot, :count] 9 | defstruct [ 10 | :start_slot, 11 | :count, 12 | step: 1 13 | ] 14 | 15 | @type t :: %__MODULE__{ 16 | start_slot: Types.slot(), 17 | count: Types.uint64(), 18 | # Deprecated, must be set to 1 19 | step: Types.uint64() 20 | } 21 | 22 | @impl LambdaEthereumConsensus.Container 23 | def schema() do 24 | [ 25 | start_slot: TypeAliases.slot(), 26 | count: TypeAliases.uint64(), 27 | step: TypeAliases.uint64() 28 | ] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/types/p2p/enr_fork_id.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.EnrForkId do 2 | @moduledoc """ 3 | Struct definition for `ENRForkID`. 4 | """ 5 | alias LambdaEthereumConsensus.Container 6 | use Container 7 | 8 | fields = [ 9 | :fork_digest, 10 | :next_fork_version, 11 | :next_fork_epoch 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | fork_digest: Types.fork_digest(), 19 | next_fork_version: Types.version(), 20 | next_fork_epoch: Types.epoch() 21 | } 22 | 23 | @impl Container 24 | def schema() do 25 | [ 26 | fork_digest: TypeAliases.fork_digest(), 27 | next_fork_version: TypeAliases.version(), 28 | next_fork_epoch: TypeAliases.epoch() 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/p2p/metadata.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Metadata do 2 | @moduledoc """ 3 | Struct definition for `Metadata`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | 7 | alias LambdaEthereumConsensus.Utils.BitVector 8 | 9 | fields = [ 10 | :seq_number, 11 | :attnets, 12 | :syncnets 13 | ] 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | seq_number: Types.uint64(), 20 | attnets: Types.bitvector(), 21 | syncnets: Types.bitvector() 22 | } 23 | 24 | def schema(), 25 | do: [ 26 | seq_number: TypeAliases.uint64(), 27 | attnets: {:bitvector, ChainSpec.get("ATTESTATION_SUBNET_COUNT")}, 28 | syncnets: {:bitvector, Constants.sync_committee_subnet_count()} 29 | ] 30 | 31 | def empty() do 32 | attnets = ChainSpec.get("ATTESTATION_SUBNET_COUNT") |> BitVector.new() 33 | syncnets = Constants.sync_committee_subnet_count() |> BitVector.new() 34 | %__MODULE__{seq_number: 0, attnets: attnets, syncnets: syncnets} 35 | end 36 | 37 | def encode(%__MODULE__{} = map) do 38 | # NOTE: we do this because the SSZ NIF cannot decode bitstrings 39 | # TODO: remove when migrating to the new SSZ lib 40 | map 41 | |> Map.update!(:attnets, &BitVector.to_bytes/1) 42 | |> Map.update!(:syncnets, &BitVector.to_bytes/1) 43 | end 44 | 45 | def decode(%__MODULE__{} = map) do 46 | # NOTE: this isn't really needed 47 | subnet_count = ChainSpec.get("ATTESTATION_SUBNET_COUNT") 48 | syncnet_count = Constants.sync_committee_subnet_count() 49 | 50 | map 51 | |> Map.update!(:attnets, &BitVector.new(&1, subnet_count)) 52 | |> Map.update!(:syncnets, &BitVector.new(&1, syncnet_count)) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/types/p2p/status_message.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.StatusMessage do 2 | @moduledoc """ 3 | Struct definition for `StatusMessage`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :fork_digest, 10 | :finalized_root, 11 | :finalized_epoch, 12 | :head_root, 13 | :head_slot 14 | ] 15 | 16 | @enforce_keys fields 17 | defstruct fields 18 | 19 | @type t :: %__MODULE__{ 20 | fork_digest: Types.fork_digest(), 21 | finalized_root: Types.root(), 22 | finalized_epoch: Types.epoch(), 23 | head_root: Types.root(), 24 | head_slot: Types.slot() 25 | } 26 | 27 | @impl LambdaEthereumConsensus.Container 28 | def schema() do 29 | [ 30 | fork_digest: TypeAliases.fork_digest(), 31 | finalized_root: TypeAliases.root(), 32 | finalized_epoch: TypeAliases.epoch(), 33 | head_root: TypeAliases.root(), 34 | head_slot: TypeAliases.slot() 35 | ] 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/types/root.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Root do 2 | @moduledoc """ 3 | Alias for `Types.root`. Is used when explicit typing is needed. 4 | """ 5 | end 6 | -------------------------------------------------------------------------------- /lib/types/transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.Transaction do 2 | @moduledoc """ 3 | Alias for `Types.transaction`. Is used when explicit typing is needed. 4 | """ 5 | end 6 | -------------------------------------------------------------------------------- /lib/types/type_aliases.ex: -------------------------------------------------------------------------------- 1 | defmodule TypeAliases do 2 | @moduledoc """ 3 | Type aliases for ssz schemas 4 | """ 5 | 6 | def root(), do: {:byte_vector, 32} 7 | def epoch(), do: {:int, 64} 8 | def bls_signature(), do: {:byte_vector, 96} 9 | def slot(), do: {:int, 64} 10 | def commitee_index(), do: {:int, 64} 11 | def validator_index(), do: {:int, 64} 12 | def gwei(), do: {:int, 64} 13 | def participation_flags(), do: {:int, 8} 14 | def withdrawal_index(), do: {:int, 64} 15 | def bls_pubkey(), do: {:byte_vector, 48} 16 | def execution_address(), do: {:byte_vector, 20} 17 | def version(), do: {:byte_vector, 4} 18 | def domain(), do: {:byte_vector, 32} 19 | def domain_type(), do: {:byte_vector, 4} 20 | def fork_digest(), do: {:byte_vector, 4} 21 | def blob_index(), do: uint64() 22 | 23 | def blob(), 24 | do: 25 | {:byte_vector, 26 | Constants.bytes_per_field_element() * ChainSpec.get("FIELD_ELEMENTS_PER_BLOB")} 27 | 28 | def kzg_commitment(), do: {:byte_vector, 48} 29 | def kzg_proof(), do: {:byte_vector, 48} 30 | 31 | def bytes32(), do: {:byte_vector, 32} 32 | def uint64(), do: {:int, 64} 33 | def hash32(), do: {:byte_vector, 32} 34 | def uint256(), do: {:int, 256} 35 | 36 | def transactions() do 37 | transaction = {:byte_list, ChainSpec.get("MAX_BYTES_PER_TRANSACTION")} 38 | {:list, transaction, ChainSpec.get("MAX_TRANSACTIONS_PER_PAYLOAD")} 39 | end 40 | 41 | def beacon_blocks_by_root_request(), 42 | do: {:list, TypeAliases.root(), ChainSpec.get("MAX_REQUEST_BLOCKS")} 43 | 44 | def blob_sidecars_by_root_request(), 45 | do: {:list, Types.BlobIdentifier, ChainSpec.get("MAX_REQUEST_BLOB_SIDECARS")} 46 | 47 | def error_message(), do: {:byte_list, 256} 48 | end 49 | -------------------------------------------------------------------------------- /lib/types/types.ex: -------------------------------------------------------------------------------- 1 | defmodule Types do 2 | @moduledoc """ 3 | Lists some types used in SSZ structs. 4 | """ 5 | 6 | # Primitive types 7 | ## Integer types 8 | @type uint8 :: 0..unquote(2 ** 8 - 1) 9 | @type uint16 :: 0..unquote(2 ** 16 - 1) 10 | @type uint32 :: 0..unquote(2 ** 32 - 1) 11 | @type uint64 :: 0..unquote(2 ** 64 - 1) 12 | @type uint256 :: 0..unquote(2 ** 256 - 1) 13 | 14 | ## Binary types 15 | @type bytes1 :: <<_::8>> 16 | @type bytes4 :: <<_::32>> 17 | @type bytes8 :: <<_::64>> 18 | @type bytes20 :: <<_::160>> 19 | @type bytes32 :: <<_::256>> 20 | @type bytes48 :: <<_::384>> 21 | @type bytes96 :: <<_::768>> 22 | # bitlists are stored in SSZ format 23 | @type bitlist :: binary 24 | @type bitvector :: binary 25 | 26 | ## Aliases 27 | @type slot :: uint64 28 | @type epoch :: uint64 29 | @type commitee_index :: uint64 30 | @type validator_index :: uint64 31 | @type gwei :: uint64 32 | @type root :: bytes32 33 | @type hash32 :: bytes32 34 | @type version :: bytes4 35 | @type domain_type :: bytes4 36 | @type fork_digest :: bytes4 37 | @type domain :: bytes32 38 | @type participation_flags :: uint8 39 | # Max size: 2**30 40 | @type transaction :: binary 41 | @type execution_address :: bytes20 42 | @type withdrawal_index :: uint64 43 | @type payload_id :: bytes8 44 | @type blob_index :: uint64 45 | # Max size: BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB 46 | @type blob :: binary 47 | @type kzg_commitment :: Kzg.commitment() 48 | @type kzg_proof :: Kzg.proof() 49 | @type bls_signature :: Bls.signature() 50 | @type bls_pubkey :: Bls.pubkey() 51 | 52 | defmodule Guards do 53 | @moduledoc """ 54 | Module defining guards for some types. Added as needed. 55 | """ 56 | 57 | defguard is_root(binary) when is_binary(binary) and byte_size(binary) == 32 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/types/validator/aggregate_and_proof.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.AggregateAndProof do 2 | @moduledoc """ 3 | Struct definition for `AggregateAndProof`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :aggregator_index, 10 | :aggregate, 11 | :selection_proof 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | aggregator_index: Types.validator_index(), 19 | aggregate: Types.Attestation.t(), 20 | selection_proof: Types.bls_signature() 21 | } 22 | 23 | @impl LambdaEthereumConsensus.Container 24 | def schema() do 25 | [ 26 | {:aggregator_index, TypeAliases.validator_index()}, 27 | {:aggregate, Types.Attestation}, 28 | {:selection_proof, TypeAliases.bls_signature()} 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/validator/contribution_and_proof.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.ContributionAndProof do 2 | @moduledoc """ 3 | Struct definition for `ContributionAndProof`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :aggregator_index, 10 | :contribution, 11 | :selection_proof 12 | ] 13 | 14 | @enforce_keys fields 15 | defstruct fields 16 | 17 | @type t :: %__MODULE__{ 18 | aggregator_index: Types.validator_index(), 19 | contribution: Types.SyncCommitteeContribution.t(), 20 | selection_proof: Types.bls_signature() 21 | } 22 | 23 | @impl LambdaEthereumConsensus.Container 24 | def schema() do 25 | [ 26 | {:aggregator_index, TypeAliases.validator_index()}, 27 | {:contribution, Types.SyncCommitteeContribution}, 28 | {:selection_proof, TypeAliases.bls_signature()} 29 | ] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/types/validator/signed_aggregate_and_proof.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SignedAggregateAndProof do 2 | @moduledoc """ 3 | Struct definition for `SignedAggregateAndProof`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :message, 10 | :signature 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | message: Types.AggregateAndProof.t(), 18 | signature: Types.bls_signature() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:message, Types.AggregateAndProof}, 25 | {:signature, TypeAliases.bls_signature()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/validator/signed_contribution_and_proof.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SignedContributionAndProof do 2 | @moduledoc """ 3 | Struct definition for `SignedContributionAndProof`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :message, 10 | :signature 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | message: Types.ContributionAndProof.t(), 18 | signature: Types.bls_signature() 19 | } 20 | 21 | @impl LambdaEthereumConsensus.Container 22 | def schema() do 23 | [ 24 | {:message, Types.ContributionAndProof}, 25 | {:signature, TypeAliases.bls_signature()} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/types/validator/sync_committee_contribution.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SyncCommitteeContribution do 2 | @moduledoc """ 3 | Struct definition for `SyncCommitteeContribution`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | alias LambdaEthereumConsensus.StateTransition.Misc 8 | alias LambdaEthereumConsensus.Utils.BitVector 9 | 10 | fields = [ 11 | :slot, 12 | :beacon_block_root, 13 | :subcommittee_index, 14 | :aggregation_bits, 15 | :signature 16 | ] 17 | 18 | @enforce_keys fields 19 | defstruct fields 20 | 21 | @type t :: %__MODULE__{ 22 | slot: Types.slot(), 23 | beacon_block_root: Types.root(), 24 | subcommittee_index: Types.uint64(), 25 | aggregation_bits: Types.bitvector(), 26 | signature: Types.bls_signature() 27 | } 28 | 29 | @impl LambdaEthereumConsensus.Container 30 | def schema() do 31 | [ 32 | {:slot, TypeAliases.slot()}, 33 | {:beacon_block_root, TypeAliases.root()}, 34 | {:subcommittee_index, TypeAliases.uint64()}, 35 | {:aggregation_bits, {:bitvector, Misc.sync_subcommittee_size()}}, 36 | {:signature, TypeAliases.bls_signature()} 37 | ] 38 | end 39 | 40 | def encode(%__MODULE__{} = map) do 41 | # NOTE: we do this because the SSZ NIF cannot decode bitstrings 42 | # TODO: remove when migrating to the new SSZ lib 43 | map 44 | |> Map.update!(:aggregation_bits, &BitVector.to_bytes/1) 45 | end 46 | 47 | def decode(%__MODULE__{} = map) do 48 | # NOTE: this isn't really needed 49 | aggregation_bits_count = Misc.sync_subcommittee_size() 50 | 51 | map 52 | |> Map.update!(:aggregation_bits, &BitVector.new(&1, aggregation_bits_count)) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/types/validator/sync_committee_message.ex: -------------------------------------------------------------------------------- 1 | defmodule Types.SyncCommitteeMessage do 2 | @moduledoc """ 3 | Struct definition for `SyncCommitteeMessage`. 4 | Related definitions in `native/ssz_nif/src/types/`. 5 | """ 6 | use LambdaEthereumConsensus.Container 7 | 8 | fields = [ 9 | :slot, 10 | :beacon_block_root, 11 | :validator_index, 12 | :signature 13 | ] 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | slot: Types.slot(), 20 | beacon_block_root: Types.root(), 21 | validator_index: Types.validator_index(), 22 | signature: Types.bls_signature() 23 | } 24 | 25 | @impl LambdaEthereumConsensus.Container 26 | def schema() do 27 | [ 28 | {:slot, TypeAliases.slot()}, 29 | {:beacon_block_root, TypeAliases.root()}, 30 | {:validator_index, TypeAliases.validator_index()}, 31 | {:signature, TypeAliases.bls_signature()} 32 | ] 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/utils/date.ex: -------------------------------------------------------------------------------- 1 | defmodule Utils.Date do 2 | @moduledoc """ 3 | Module with date utilities to be shared across scripts and utilities. 4 | """ 5 | alias Timex.Format.DateTime.Formatter 6 | 7 | @doc """ 8 | Returns a human readable string representing the current UTC datetime. Specially useful to 9 | name auto-generated files. 10 | """ 11 | def now_str() do 12 | DateTime.utc_now() 13 | |> Formatter.format!("{YYYY}_{0M}_{0D}__{h24}_{m}_{s}_{ss}") 14 | |> String.replace(".", "") 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/utils/profile.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Profile do 2 | @moduledoc """ 3 | Wrappers for profiling using EEP, with easy defaults. 4 | """ 5 | alias Utils.Date 6 | 7 | @default_profile_time_millis 300 8 | 9 | @doc """ 10 | Builds a simple profile trace. Always returns :ok. The trace output is in qcachegrind format. 11 | 12 | Optional arguments: 13 | - trace_name: the output file will be "callgrind.out.". If no name is given 14 | then the date (e.g. 2023_09_07__12_00_34_652201) will be used by default. 15 | - profile_time_millis: the amount of milliseconds in which the app will be instrumented and 16 | the profile will be taken. The more time, the more information in the trace, but the more 17 | pressure applied to the app (instrumentation makes everything slower). If the node is in 18 | production and already at a CPU limit, then the best is to take many short (e.g. 300ms) 19 | traces instead of a long one and to inspect them separately. 20 | """ 21 | def build(opts \\ []) do 22 | trace_name = Keyword.get(opts, :trace_name, Date.now_str()) 23 | erlang_trace_name = String.to_charlist(trace_name) 24 | profile_time_millis = Keyword.get(opts, :profile_time_millis, @default_profile_time_millis) 25 | 26 | :eep.start_file_tracing(erlang_trace_name) 27 | :timer.sleep(profile_time_millis) 28 | :eep.stop_tracing() 29 | :eep.convert_tracing(erlang_trace_name) 30 | 31 | File.rm(trace_name <> ".trace") 32 | :ok 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/utils/randao.ex: -------------------------------------------------------------------------------- 1 | defmodule LambdaEthereumConsensus.Utils.Randao do 2 | @moduledoc """ 3 | This module provides utility functions for randao mixes 4 | """ 5 | 6 | @spec get_randao_mix_index(Types.epoch()) :: Types.epoch() 7 | defp get_randao_mix_index(epoch), do: rem(epoch, ChainSpec.get("EPOCHS_PER_HISTORICAL_VECTOR")) 8 | 9 | @doc """ 10 | Replaces the randao mix with a new one at the given epoch. 11 | """ 12 | @spec replace_randao_mix(Aja.Vector.t(Types.bytes32()), Types.epoch(), Types.bytes32()) :: 13 | Aja.Vector.t(Types.bytes32()) 14 | def replace_randao_mix(randao_mixes, epoch, randao_mix), 15 | do: Aja.Vector.replace_at!(randao_mixes, get_randao_mix_index(epoch), randao_mix) 16 | 17 | @doc """ 18 | Return the randao mix at a recent ``epoch``. 19 | """ 20 | @spec get_randao_mix(Aja.Vector.t(Types.bytes32()), Types.epoch()) :: Types.bytes32() 21 | def get_randao_mix(randao_mixes, epoch) do 22 | index = get_randao_mix_index(epoch) 23 | Aja.Vector.at!(randao_mixes, index) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /metrics/grafana/provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'Folder' 5 | allowUiUpdates: true 6 | options: 7 | path: /etc/grafana/provisioning/dashboards 8 | -------------------------------------------------------------------------------- /metrics/grafana/provisioning/datasources/loki_ds.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | - name: Loki 3 | access: proxy 4 | type: loki 5 | url: http://loki:3100 6 | isDefault: false 7 | -------------------------------------------------------------------------------- /metrics/grafana/provisioning/datasources/prometheus_ds.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | - name: Prometheus 3 | access: proxy 4 | type: prometheus 5 | url: http://prometheus:9090 6 | isDefault: true 7 | -------------------------------------------------------------------------------- /metrics/loki/loki.yml: -------------------------------------------------------------------------------- 1 | auth_enabled: false 2 | 3 | server: 4 | http_listen_port: 3100 5 | grpc_listen_port: 9096 6 | 7 | common: 8 | instance_addr: 127.0.0.1 9 | path_prefix: /tmp/loki 10 | storage: 11 | filesystem: 12 | chunks_directory: /tmp/loki/chunks 13 | rules_directory: /tmp/loki/rules 14 | replication_factor: 1 15 | ring: 16 | kvstore: 17 | store: inmemory 18 | 19 | query_range: 20 | results_cache: 21 | cache: 22 | embedded_cache: 23 | enabled: true 24 | max_size_mb: 100 25 | 26 | schema_config: 27 | configs: 28 | - from: 2020-10-24 29 | store: boltdb-shipper 30 | object_store: filesystem 31 | schema: v11 32 | index: 33 | prefix: index_ 34 | period: 24h 35 | 36 | ruler: 37 | alertmanager_url: http://localhost:9093/alertmanager 38 | # By default, Loki will send anonymous, but uniquely-identifiable usage and configuration 39 | # analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/ 40 | # 41 | # Statistics help us better understand how Loki is used, and they show us performance 42 | # levels for most users. This helps us prioritize features and documentation. 43 | # For more information on what's sent, look at 44 | # https://github.com/grafana/loki/blob/main/pkg/usagestats/stats.go 45 | # Refer to the buildReport method to see what goes into a report. 46 | # 47 | # If you would like to disable reporting, uncomment the following lines: 48 | #analytics: 49 | # reporting_enabled: false 50 | -------------------------------------------------------------------------------- /metrics/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 1s 3 | 4 | scrape_configs: 5 | - job_name: "prom_ex" 6 | metrics_path: "/metrics" 7 | static_configs: 8 | - targets: ["host.docker.internal:9568"] 9 | -------------------------------------------------------------------------------- /metrics/promtail/promtail.yml: -------------------------------------------------------------------------------- 1 | server: 2 | http_listen_port: 9080 3 | grpc_listen_port: 0 4 | 5 | positions: 6 | filename: /tmp/promtail/positions.yaml 7 | 8 | clients: 9 | - url: http://loki:3100/loki/api/v1/push 10 | 11 | scrape_configs: 12 | - job_name: system 13 | static_configs: 14 | - targets: 15 | - localhost 16 | labels: 17 | job: lambda_ethereum_consensus 18 | __path__: /var/log/consensus/*log 19 | pipeline_stages: 20 | - logfmt: 21 | mapping: 22 | ts: 23 | level: 24 | msg: 25 | mfa: 26 | process: registered_name 27 | pid: 28 | slot: 29 | root: 30 | - timestamp: 31 | format: "2006-01-02T15:04:05.000" 32 | source: ts 33 | - labels: 34 | level: 35 | mfa: 36 | process: 37 | pid: 38 | slot: 39 | root: 40 | - output: 41 | source: msg 42 | -------------------------------------------------------------------------------- /native/bls_nif/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_os = "macos")'] 2 | rustflags = [ 3 | "-C", "link-arg=-undefined", 4 | "-C", "link-arg=dynamic_lookup", 5 | ] 6 | -------------------------------------------------------------------------------- /native/bls_nif/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /native/bls_nif/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bls_nif" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "bls_nif" 9 | path = "src/lib.rs" 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | rustler = "0.36.1" 14 | bls = { git = "https://github.com/sigp/lighthouse", package = "bls", rev = "v7.0.1" } 15 | -------------------------------------------------------------------------------- /native/bls_nif/README.md: -------------------------------------------------------------------------------- 1 | # NIF for Elixir.BLS 2 | 3 | ## To build the NIF module: 4 | 5 | - Your NIF will now build along with your project. 6 | 7 | ## To load the NIF: 8 | 9 | ```elixir 10 | defmodule BLS do 11 | use Rustler, otp_app: :lambda_ethereum_consensus, crate: "bls" 12 | 13 | # When your NIF is loaded, it will override this function. 14 | def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded) 15 | end 16 | ``` 17 | 18 | ## Examples 19 | 20 | [This](https://github.com/rusterlium/NifIo) is a complete example of a NIF written in Rust. 21 | -------------------------------------------------------------------------------- /native/kzg_nif/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_os = "macos")'] 2 | rustflags = [ 3 | "-C", "link-arg=-undefined", 4 | "-C", "link-arg=dynamic_lookup", 5 | ] -------------------------------------------------------------------------------- /native/kzg_nif/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /native/kzg_nif/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kzg_nif" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "kzg_nif" 8 | path = "src/lib.rs" 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | rustler = "0.32.1" 13 | c-kzg = "1.0.0" 14 | -------------------------------------------------------------------------------- /native/snappy_nif/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_os = "macos")'] 2 | rustflags = [ 3 | "-C", "link-arg=-undefined", 4 | "-C", "link-arg=dynamic_lookup", 5 | ] 6 | -------------------------------------------------------------------------------- /native/snappy_nif/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /native/snappy_nif/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snappy_nif" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "snappy_nif" 9 | path = "src/lib.rs" 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | rustler = "0.32.1" 14 | snap = "1.1.0" 15 | -------------------------------------------------------------------------------- /native/snappy_nif/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use rustler::{Binary, Env, NewBinary}; 4 | use snap::read; 5 | 6 | fn bytes_to_binary<'env>(env: Env<'env>, bytes: &[u8]) -> Binary<'env> { 7 | let mut binary = NewBinary::new(env, bytes.len()); 8 | // This cannot fail because bin size equals bytes len 9 | binary.as_mut_slice().write_all(bytes).unwrap(); 10 | binary.into() 11 | } 12 | 13 | #[rustler::nif] 14 | fn compress<'env>(env: Env<'env>, bin: Binary) -> Result, String> { 15 | let mut encoder = read::FrameEncoder::new(&bin[..]); 16 | let mut buffer = Vec::with_capacity(bin.len()); 17 | encoder 18 | .read_to_end(&mut buffer) 19 | .map_err(|e| e.to_string())?; 20 | Ok(bytes_to_binary(env, &buffer)) 21 | } 22 | 23 | #[rustler::nif] 24 | fn decompress<'env>(env: Env<'env>, bin: Binary) -> Result, String> { 25 | let mut decoder = read::FrameDecoder::new(&bin[..]); 26 | let mut buffer = Vec::with_capacity(bin.len()); 27 | decoder 28 | .read_to_end(&mut buffer) 29 | .map_err(|e| e.to_string())?; 30 | Ok(bytes_to_binary(env, &buffer)) 31 | } 32 | 33 | rustler::init!("Elixir.Snappy", [compress, decompress]); 34 | -------------------------------------------------------------------------------- /native/ssz_nif/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_os = "macos")'] 2 | rustflags = [ 3 | "-C", "link-arg=-undefined", 4 | "-C", "link-arg=dynamic_lookup", 5 | ] 6 | -------------------------------------------------------------------------------- /native/ssz_nif/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /native/ssz_nif/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ssz_nif" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "ssz_nif" 9 | path = "src/lib.rs" 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | rustler = "0.32.1" 14 | ethereum_ssz_derive = "0.8.3" 15 | ethereum_ssz = "0.8.3" 16 | ssz_types = "0.10.1" 17 | tree_hash = "0.9.1" 18 | tree_hash_derive = "0.9.1" 19 | -------------------------------------------------------------------------------- /native/ssz_nif/src/elx_types/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Elixir Types 2 | //! 3 | //! To add a new type, add the struct definition (with [`rustler`] types) 4 | //! in the corresponding module. You may need to add some [`FromElx`] and 5 | //! [`FromSsz`] implementations to convert between the types. 6 | 7 | mod beacon_chain; 8 | mod p2p; 9 | mod validator; 10 | 11 | pub(crate) use beacon_chain::*; 12 | pub(crate) use p2p::*; 13 | pub(crate) use validator::*; 14 | 15 | use rustler::Binary; 16 | 17 | type Bytes4<'a> = Binary<'a>; 18 | type Bytes20<'a> = Binary<'a>; 19 | type Bytes32<'a> = Binary<'a>; 20 | type Bytes48<'a> = Binary<'a>; 21 | type Bytes96<'a> = Binary<'a>; 22 | 23 | type Slot = u64; 24 | pub(crate) type Epoch = u64; 25 | type CommitteeIndex = u64; 26 | type ValidatorIndex = u64; 27 | type Gwei = u64; 28 | pub(crate) type Root<'a> = Bytes32<'a>; 29 | type Hash32<'a> = Bytes32<'a>; 30 | type Version<'a> = Bytes4<'a>; 31 | #[allow(dead_code)] 32 | type DomainType<'a> = Bytes4<'a>; 33 | #[allow(dead_code)] 34 | type ForkDigest<'a> = Bytes4<'a>; 35 | type Domain<'a> = Bytes32<'a>; 36 | type BLSPubkey<'a> = Bytes48<'a>; 37 | type BLSSignature<'a> = Bytes96<'a>; 38 | #[allow(dead_code)] 39 | type ParticipationFlags = u8; 40 | pub(crate) type Transaction<'a> = Binary<'a>; // max size: 1073741824 41 | type ExecutionAddress<'a> = Bytes20<'a>; 42 | type WithdrawalIndex = u64; 43 | type KZGCommitment<'a> = Bytes48<'a>; 44 | type KZGProof<'a> = Bytes48<'a>; 45 | type BlobIndex = u64; 46 | type Blob<'a> = Binary<'a>; 47 | 48 | // This type should be a little-endian encoded uint256. 49 | type Uint256<'a> = Binary<'a>; 50 | -------------------------------------------------------------------------------- /native/ssz_nif/src/elx_types/p2p.rs: -------------------------------------------------------------------------------- 1 | use rustler::NifStruct; 2 | 3 | use crate::utils::{gen_struct, gen_struct_with_config}; 4 | 5 | use super::*; 6 | 7 | gen_struct!( 8 | #[derive(NifStruct)] 9 | #[module = "Types.StatusMessage"] 10 | pub(crate) struct StatusMessage<'a> { 11 | fork_digest: ForkDigest<'a>, 12 | finalized_root: Root<'a>, 13 | finalized_epoch: Epoch, 14 | head_root: Root<'a>, 15 | head_slot: Slot, 16 | } 17 | ); 18 | 19 | gen_struct!( 20 | #[derive(NifStruct)] 21 | #[module = "Types.BeaconBlocksByRangeRequest"] 22 | pub(crate) struct BeaconBlocksByRangeRequest { 23 | start_slot: Slot, 24 | count: u64, 25 | step: u64, 26 | } 27 | ); 28 | 29 | gen_struct_with_config!( 30 | #[derive(NifStruct)] 31 | #[module = "Types.Metadata"] 32 | pub(crate) struct Metadata<'a> { 33 | seq_number: u64, 34 | attnets: Binary<'a>, 35 | syncnets: Binary<'a>, 36 | } 37 | ); 38 | 39 | gen_struct_with_config!( 40 | #[derive(NifStruct)] 41 | #[module = "Types.BlobSidecar"] 42 | pub(crate) struct BlobSidecar<'a> { 43 | index: BlobIndex, 44 | blob: Blob<'a>, 45 | kzg_commitment: KZGCommitment<'a>, 46 | kzg_proof: KZGProof<'a>, 47 | signed_block_header: SignedBeaconBlockHeader<'a>, 48 | kzg_commitment_inclusion_proof: Vec>, 49 | } 50 | ); 51 | 52 | gen_struct!( 53 | #[derive(NifStruct)] 54 | #[module = "Types.BlobIdentifier"] 55 | pub(crate) struct BlobIdentifier<'a> { 56 | block_root: Root<'a>, 57 | index: BlobIndex, 58 | } 59 | ); 60 | -------------------------------------------------------------------------------- /native/ssz_nif/src/elx_types/validator.rs: -------------------------------------------------------------------------------- 1 | use rustler::NifStruct; 2 | 3 | use crate::utils::{gen_struct, gen_struct_with_config}; 4 | 5 | use super::*; 6 | 7 | gen_struct_with_config!( 8 | #[derive(NifStruct)] 9 | #[module = "Types.AggregateAndProof"] 10 | pub(crate) struct AggregateAndProof<'a> { 11 | aggregator_index: ValidatorIndex, 12 | aggregate: Attestation<'a>, 13 | selection_proof: BLSSignature<'a>, 14 | } 15 | ); 16 | 17 | gen_struct_with_config!( 18 | #[derive(NifStruct)] 19 | #[module = "Types.SignedAggregateAndProof"] 20 | pub(crate) struct SignedAggregateAndProof<'a> { 21 | message: AggregateAndProof<'a>, 22 | signature: BLSSignature<'a>, 23 | } 24 | ); 25 | 26 | gen_struct!( 27 | #[derive(NifStruct)] 28 | #[module = "Types.SyncCommitteeMessage"] 29 | pub(crate) struct SyncCommitteeMessage<'a> { 30 | slot: Slot, 31 | beacon_block_root: Root<'a>, 32 | validator_index: ValidatorIndex, 33 | signature: BLSSignature<'a>, 34 | } 35 | ); 36 | 37 | gen_struct_with_config!( 38 | #[derive(NifStruct)] 39 | #[module = "Types.SyncCommitteeContribution"] 40 | pub(crate) struct SyncCommitteeContribution<'a> { 41 | slot: Slot, 42 | beacon_block_root: Root<'a>, 43 | subcommittee_index: u64, 44 | aggregation_bits: Binary<'a>, 45 | signature: BLSSignature<'a>, 46 | } 47 | ); 48 | 49 | gen_struct_with_config!( 50 | #[derive(NifStruct)] 51 | #[module = "Types.ContributionAndProof"] 52 | pub(crate) struct ContributionAndProof<'a> { 53 | aggregator_index: ValidatorIndex, 54 | contribution: SyncCommitteeContribution<'a>, 55 | selection_proof: BLSSignature<'a>, 56 | } 57 | ); 58 | 59 | gen_struct_with_config!( 60 | #[derive(NifStruct)] 61 | #[module = "Types.SignedContributionAndProof"] 62 | pub(crate) struct SignedContributionAndProof<'a> { 63 | message: ContributionAndProof<'a>, 64 | signature: BLSSignature<'a>, 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /native/ssz_nif/src/ssz_types/p2p.rs: -------------------------------------------------------------------------------- 1 | use super::{config::Config, *}; 2 | use ssz_derive::{Decode, Encode}; 3 | use ssz_types::BitVector; 4 | use tree_hash_derive::TreeHash; 5 | 6 | #[derive(Encode, Decode, TreeHash)] 7 | pub(crate) struct StatusMessage { 8 | pub(crate) fork_digest: ForkDigest, 9 | pub(crate) finalized_root: Root, 10 | pub(crate) finalized_epoch: Epoch, 11 | pub(crate) head_root: Root, 12 | pub(crate) head_slot: Slot, 13 | } 14 | 15 | #[derive(Encode, Decode, TreeHash)] 16 | pub(crate) struct BeaconBlocksByRangeRequest { 17 | pub(crate) start_slot: Slot, 18 | pub(crate) count: u64, 19 | pub(crate) step: u64, 20 | } 21 | 22 | #[derive(Encode, Decode, TreeHash)] 23 | pub(crate) struct Metadata { 24 | pub(crate) seq_number: u64, 25 | pub(crate) attnets: BitVector, 26 | pub(crate) syncnets: BitVector, 27 | } 28 | 29 | #[derive(Encode, Decode, TreeHash)] 30 | pub(crate) struct BlobSidecar { 31 | pub(crate) index: BlobIndex, 32 | pub(crate) blob: Blob, 33 | pub(crate) kzg_commitment: KZGCommitment, 34 | pub(crate) kzg_proof: KZGProof, 35 | pub(crate) signed_block_header: SignedBeaconBlockHeader, 36 | pub(crate) kzg_commitment_inclusion_proof: 37 | FixedVector, 38 | } 39 | 40 | #[derive(Encode, Decode, TreeHash)] 41 | pub(crate) struct BlobIdentifier { 42 | pub(crate) block_root: Root, 43 | pub(crate) index: BlobIndex, 44 | } 45 | -------------------------------------------------------------------------------- /native/ssz_nif/src/ssz_types/validator.rs: -------------------------------------------------------------------------------- 1 | use super::{config::Config, *}; 2 | use ssz_derive::{Decode, Encode}; 3 | use ssz_types::BitVector; 4 | use tree_hash_derive::TreeHash; 5 | 6 | #[derive(Encode, Decode, TreeHash)] 7 | pub(crate) struct AggregateAndProof { 8 | pub(crate) aggregator_index: ValidatorIndex, 9 | pub(crate) aggregate: Attestation, 10 | pub(crate) selection_proof: BLSSignature, 11 | } 12 | 13 | #[derive(Encode, Decode, TreeHash)] 14 | pub(crate) struct SignedAggregateAndProof { 15 | pub(crate) message: AggregateAndProof, 16 | pub(crate) signature: BLSSignature, 17 | } 18 | 19 | #[derive(Encode, Decode, TreeHash)] 20 | pub(crate) struct SyncCommitteeMessage { 21 | pub(crate) slot: Slot, 22 | pub(crate) beacon_block_root: Root, 23 | pub(crate) validator_index: ValidatorIndex, 24 | pub(crate) signature: BLSSignature, 25 | } 26 | 27 | #[derive(Encode, Decode, TreeHash)] 28 | pub(crate) struct SyncCommitteeContribution { 29 | pub(crate) slot: Slot, 30 | pub(crate) beacon_block_root: Root, 31 | pub(crate) subcommittee_index: u64, 32 | pub(crate) aggregation_bits: BitVector, 33 | pub(crate) signature: BLSSignature, 34 | } 35 | 36 | #[derive(Encode, Decode, TreeHash)] 37 | pub(crate) struct ContributionAndProof { 38 | pub(crate) aggregator_index: ValidatorIndex, 39 | pub(crate) contribution: SyncCommitteeContribution, 40 | pub(crate) selection_proof: BLSSignature, 41 | } 42 | 43 | #[derive(Encode, Decode, TreeHash)] 44 | pub(crate) struct SignedContributionAndProof { 45 | pub(crate) message: ContributionAndProof, 46 | pub(crate) signature: BLSSignature, 47 | } 48 | -------------------------------------------------------------------------------- /network_params.yaml: -------------------------------------------------------------------------------- 1 | participants: 2 | - el_type: geth 3 | el_image: ethereum/client-go:v1.15.6 4 | cl_type: lighthouse 5 | cl_image: sigp/lighthouse:v7.0.0-beta.5 6 | count: 2 7 | validator_count: 32 8 | - el_type: geth 9 | el_image: ethereum/client-go:v1.15.6 10 | cl_type: lambda 11 | cl_image: lambda_ethereum_consensus:latest 12 | use_separate_vc: false 13 | count: 1 14 | validator_count: 32 15 | cl_max_mem: 4096 16 | keymanager_enabled: true 17 | 18 | network_params: 19 | electra_fork_epoch: 0 20 | # Uncomment the following line to run the the network with the minimal preset (which is various times faster) 21 | # preset: minimal 22 | 23 | additional_services: 24 | - tx_fuzz 25 | - dora 26 | - prometheus_grafana 27 | # Uncomment the following lines to run the the network with the assertoor tests 28 | # - assertoor 29 | 30 | # assertoor_params: 31 | # run_stability_check: false 32 | # run_block_proposal_check: false 33 | # tests: 34 | # - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/main/.github/config/assertoor/cl-stability-check.yml 35 | 36 | # tx_fuzz_params: 37 | # tx_fuzz_extra_args: ["--txcount=3", "--accounts=80"] 38 | -------------------------------------------------------------------------------- /scripts/install_protos.sh: -------------------------------------------------------------------------------- 1 | if [ ! -f ~/.mix/escripts/protoc-gen-elixir ]; then 2 | mix escript.install hex protobuf 3 | fi 4 | 5 | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 6 | 7 | if command -v asdf &> /dev/null; then 8 | asdf reshim 9 | fi 10 | 11 | -------------------------------------------------------------------------------- /test/fixtures/blobs/blob_sidecar.ssz_snappy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/9f252e72c561d105207aa0e86175d1ecf1a2c292/test/fixtures/blobs/blob_sidecar.ssz_snappy -------------------------------------------------------------------------------- /test/fixtures/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule Fixtures.Random do 2 | @moduledoc """ 3 | Module that binarys random binaries. 4 | """ 5 | 6 | @spec binary(integer()) :: binary() 7 | def binary(n) when is_integer(n) and n > 0 do 8 | :rand.bytes(n) 9 | end 10 | 11 | @spec hash32 :: binary 12 | def hash32() do 13 | binary(32) 14 | end 15 | 16 | @spec root :: binary 17 | def root() do 18 | binary(32) 19 | end 20 | 21 | @spec slot() :: Types.slot() 22 | def slot(), do: uint64() 23 | 24 | @spec bls_signature :: binary 25 | def bls_signature() do 26 | binary(96) 27 | end 28 | 29 | @spec sync_committee_bits :: binary 30 | def sync_committee_bits() do 31 | binary(64) 32 | end 33 | 34 | @spec execution_address :: binary 35 | def execution_address() do 36 | binary(20) 37 | end 38 | 39 | @spec uint64 :: pos_integer 40 | def uint64() do 41 | :rand.uniform(2 ** 64 - 1) 42 | end 43 | 44 | @spec uint256 :: pos_integer 45 | def uint256() do 46 | :rand.uniform(2 ** 256 - 1) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/fixtures/validator/proposer/beacon_state.ssz_snappy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/9f252e72c561d105207aa0e86175d1ecf1a2c292/test/fixtures/validator/proposer/beacon_state.ssz_snappy -------------------------------------------------------------------------------- /test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/9f252e72c561d105207aa0e86175d1ecf1a2c292/test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy -------------------------------------------------------------------------------- /test/integration/fork_choice/handlers_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Integration.ForkChoice.HandlersTest do 2 | use ExUnit.Case 3 | 4 | alias LambdaEthereumConsensus.ForkChoice.Handlers 5 | alias LambdaEthereumConsensus.StateTransition.Cache 6 | alias LambdaEthereumConsensus.Store.BlockDb 7 | alias LambdaEthereumConsensus.Store.Blocks 8 | alias LambdaEthereumConsensus.Store.Db 9 | alias LambdaEthereumConsensus.Store.StateDb 10 | alias Types.BlockInfo 11 | 12 | setup_all do 13 | start_supervised!(Db) 14 | start_supervised!(Blocks) 15 | start_supervised!(BlockStates) 16 | Cache.initialize_cache() 17 | :ok 18 | end 19 | 20 | # TODO: refactor to use randomized fixtures 21 | @tag :skip 22 | test "on_block w/data from DB" do 23 | # NOTE: this test requires a DB with a state, and blocks for the state's slot and the next slot. 24 | # WARN: sometimes fails with "OffsetOutOfBounds" errors. Re-run the test in those cases. 25 | {:ok, state} = StateDb.get_latest_state() 26 | 27 | {:ok, %BlockInfo{signed_block: signed_block}} = BlockDb.get_block_info_by_slot(state.slot) 28 | 29 | {:ok, %BlockInfo{signed_block: new_signed_block}} = 30 | BlockDb.get_block_info_by_slot(state.slot + 1) 31 | 32 | assert {:ok, store} = Types.Store.get_forkchoice_store(state, signed_block) 33 | new_store = Handlers.on_tick(store, :os.system_time(:second)) 34 | 35 | assert {:ok, _} = Handlers.on_block(new_store, new_signed_block) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/integration/libp2p_port_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Integration.Libp2pPortTest do 2 | use ExUnit.Case 3 | 4 | alias LambdaEthereumConsensus.Libp2pPort 5 | 6 | @bootnodes Application.compile_env( 7 | :lambda_ethereum_consensus, 8 | :discovery 9 | )[:bootnodes] 10 | 11 | @tag :skip 12 | @tag timeout: :infinity 13 | test "discover peers indefinitely" do 14 | init_args = [ 15 | enable_discovery: true, 16 | discovery_addr: "0.0.0.0:25100", 17 | bootnodes: @bootnodes, 18 | new_peer_handler: self() 19 | ] 20 | 21 | start_link_supervised!({Libp2pPort, init_args}) 22 | 23 | Stream.iterate(0, fn _ -> 24 | receive do 25 | {:new_peer, peer_id} -> peer_id |> Base.encode16() |> IO.puts() 26 | end 27 | end) 28 | |> Stream.run() 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/spec/runner_behaviour.ex: -------------------------------------------------------------------------------- 1 | defmodule TestRunner do 2 | @moduledoc """ 3 | Behaviour for test runners used by the test generator. 4 | """ 5 | 6 | @doc """ 7 | Adds behaviour and default implementation for `skip?/1` (run all tests). 8 | """ 9 | defmacro __using__(_) do 10 | quote([]) do 11 | @behaviour TestRunner 12 | def skip?(_testcase), do: false 13 | defoverridable skip?: 1 14 | 15 | def setup(), do: :ok 16 | defoverridable setup: 0 17 | end 18 | end 19 | 20 | @doc """ 21 | Returns true if the given testcase should be skipped 22 | """ 23 | @callback skip?(testcase :: SpecTestCase.t()) :: boolean 24 | 25 | @doc """ 26 | Runs the given test case. This function should only return 27 | if the test case passes. To ignore test cases use `skip?/1`. 28 | """ 29 | @callback run_test_case(testcase :: SpecTestCase.t()) :: any 30 | 31 | @doc """ 32 | Called in an ExUnit.setup manner, before each test case, to perform setup specific to the runner. 33 | """ 34 | @callback setup() :: :ok 35 | end 36 | -------------------------------------------------------------------------------- /test/spec/runners/epoch_processing.ex: -------------------------------------------------------------------------------- 1 | defmodule EpochProcessingTestRunner do 2 | @moduledoc """ 3 | Runner for Epoch Processing test cases. See: https://github.com/ethereum/consensus-specs/tree/dev/tests/formats/epoch_processing 4 | """ 5 | alias LambdaEthereumConsensus.StateTransition.EpochProcessing 6 | alias LambdaEthereumConsensus.Utils.Diff 7 | alias Types.BeaconState 8 | 9 | use ExUnit.CaseTemplate 10 | use TestRunner 11 | 12 | # TODO: We need to make sure this 2 are still needed to be here 13 | @disabled_handlers [ 14 | "participation_record_updates" 15 | ] 16 | 17 | @deprecated_handlers [ 18 | "historical_roots_update" 19 | ] 20 | 21 | @impl TestRunner 22 | def skip?(%SpecTestCase{fork: "capella", handler: handler}) do 23 | Enum.member?(@disabled_handlers ++ @deprecated_handlers, handler) 24 | end 25 | 26 | def skip?(_), do: false 27 | 28 | @impl TestRunner 29 | def run_test_case(%SpecTestCase{} = testcase) do 30 | case_dir = SpecTestCase.dir(testcase) 31 | 32 | pre = SpecTestUtils.read_ssz_from_file!(case_dir <> "/pre.ssz_snappy", BeaconState) 33 | post = SpecTestUtils.read_ssz_from_optional_file!(case_dir <> "/post.ssz_snappy", BeaconState) 34 | 35 | handle_case(testcase.handler, pre, post) 36 | end 37 | 38 | defp handle_case(name, pre, post) do 39 | fun = "process_#{name}" |> String.to_existing_atom() 40 | result = apply(EpochProcessing, fun, [pre]) 41 | 42 | case post do 43 | nil -> 44 | assert {:error, _error_msg} = result 45 | 46 | post -> 47 | assert {:ok, state} = result 48 | assert Diff.diff(state, post) == :unchanged 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/spec/runners/finality.ex: -------------------------------------------------------------------------------- 1 | defmodule FinalityTestRunner do 2 | @moduledoc """ 3 | Runner for finality test cases. See: https://github.com/ethereum/consensus-specs/tree/dev/tests/formats/finality 4 | """ 5 | 6 | use ExUnit.CaseTemplate 7 | use TestRunner 8 | 9 | @impl TestRunner 10 | def run_test_case(testcase) do 11 | Helpers.ProcessBlocks.process_blocks(testcase) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/spec/runners/helpers/process_blocks.ex: -------------------------------------------------------------------------------- 1 | defmodule Helpers.ProcessBlocks do 2 | @moduledoc """ 3 | Helper module for processing blocks. 4 | """ 5 | 6 | use ExUnit.CaseTemplate 7 | 8 | alias LambdaEthereumConsensus.StateTransition 9 | alias LambdaEthereumConsensus.Utils.Diff 10 | alias Types.BeaconState 11 | alias Types.BlockInfo 12 | alias Types.SignedBeaconBlock 13 | 14 | def process_blocks(%SpecTestCase{} = testcase) do 15 | case_dir = SpecTestCase.dir(testcase) 16 | 17 | pre = SpecTestUtils.read_ssz_from_file!(case_dir <> "/pre.ssz_snappy", BeaconState) 18 | post = SpecTestUtils.read_ssz_from_optional_file!(case_dir <> "/post.ssz_snappy", BeaconState) 19 | 20 | meta = YamlElixir.read_from_file!(case_dir <> "/meta.yaml") |> SpecTestUtils.sanitize_yaml() 21 | 22 | %{blocks_count: blocks_count} = meta 23 | 24 | blocks = 25 | 0..(blocks_count - 1)//1 26 | |> Enum.map(fn index -> 27 | SpecTestUtils.read_ssz_from_file!( 28 | case_dir <> "/blocks_#{index}.ssz_snappy", 29 | SignedBeaconBlock 30 | ) 31 | end) 32 | 33 | result = 34 | blocks 35 | |> Enum.reduce_while({:ok, pre}, fn block, {:ok, state} -> 36 | case StateTransition.verified_transition(state, BlockInfo.from_block(block)) do 37 | {:ok, post_state} -> {:cont, {:ok, post_state.beacon_state}} 38 | {:error, error} -> {:halt, {:error, error}} 39 | end 40 | end) 41 | 42 | case result do 43 | {:ok, state} -> 44 | assert Diff.diff(state, post) == :unchanged 45 | 46 | {:error, error} -> 47 | assert post == nil, "Process block failed, error: #{error}" 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/spec/runners/helpers/ssz_static_containers/bits_struct.ex: -------------------------------------------------------------------------------- 1 | defmodule Helpers.SszStaticContainers.BitsStruct do 2 | @moduledoc """ 3 | Struct definition for `BitsStruct`. 4 | """ 5 | use LambdaEthereumConsensus.Container 6 | 7 | fields = [ 8 | :A, 9 | :B, 10 | :C, 11 | :D, 12 | :E 13 | ] 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | A: Types.bitlist(), 20 | B: Types.bitvector(), 21 | C: Types.bitvector(), 22 | D: Types.bitlist(), 23 | E: Types.bitvector() 24 | } 25 | 26 | @impl LambdaEthereumConsensus.Container 27 | def schema() do 28 | [ 29 | {:A, {:bitlist, 5}}, 30 | {:B, {:bitvector, 2}}, 31 | {:C, {:bitvector, 1}}, 32 | {:D, {:bitlist, 6}}, 33 | {:E, {:bitvector, 8}} 34 | ] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/spec/runners/helpers/ssz_static_containers/complex_test_struct.ex: -------------------------------------------------------------------------------- 1 | defmodule Helpers.SszStaticContainers.ComplexTestStruct do 2 | @moduledoc """ 3 | Struct definition for `ComplexTestStruct`. 4 | """ 5 | alias Helpers.SszStaticContainers.FixedTestStruct 6 | alias Helpers.SszStaticContainers.VarTestStruct 7 | use LambdaEthereumConsensus.Container 8 | 9 | fields = [ 10 | :A, 11 | :B, 12 | :C, 13 | :D, 14 | :E, 15 | :F, 16 | :G 17 | ] 18 | 19 | @enforce_keys fields 20 | defstruct fields 21 | 22 | @type t :: %__MODULE__{ 23 | A: Types.uint16(), 24 | B: list(Types.uint16()), 25 | C: Types.uint8(), 26 | D: list(Types.uint8()), 27 | E: VarTestStruct, 28 | F: list(FixedTestStruct), 29 | G: list(VarTestStruct) 30 | } 31 | 32 | @impl LambdaEthereumConsensus.Container 33 | def schema() do 34 | [ 35 | {:A, {:int, 16}}, 36 | {:B, {:list, {:int, 16}, 128}}, 37 | {:C, {:int, 8}}, 38 | {:D, {:list, {:int, 8}, 256}}, 39 | {:E, VarTestStruct}, 40 | {:F, {:vector, FixedTestStruct, 4}}, 41 | {:G, {:vector, VarTestStruct, 2}} 42 | ] 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/spec/runners/helpers/ssz_static_containers/fixed_test_struct.ex: -------------------------------------------------------------------------------- 1 | defmodule Helpers.SszStaticContainers.FixedTestStruct do 2 | @moduledoc """ 3 | Struct definition for `FixedTestStruct`. 4 | """ 5 | use LambdaEthereumConsensus.Container 6 | 7 | fields = [ 8 | :A, 9 | :B, 10 | :C 11 | ] 12 | 13 | @type uint32 :: 0..unquote(2 ** 32 - 1) 14 | 15 | @enforce_keys fields 16 | defstruct fields 17 | 18 | @type t :: %__MODULE__{ 19 | A: Types.uint8(), 20 | B: Types.uint64(), 21 | C: uint32() 22 | } 23 | 24 | @impl LambdaEthereumConsensus.Container 25 | def schema() do 26 | [ 27 | {:A, {:int, 8}}, 28 | {:B, {:int, 64}}, 29 | {:C, {:int, 32}} 30 | ] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/spec/runners/helpers/ssz_static_containers/single_field_test_struct.ex: -------------------------------------------------------------------------------- 1 | defmodule Helpers.SszStaticContainers.SingleFieldTestStruct do 2 | @moduledoc """ 3 | Struct definition for `SingleFieldTestStruct`. 4 | """ 5 | use LambdaEthereumConsensus.Container 6 | 7 | fields = [ 8 | :A 9 | ] 10 | 11 | @enforce_keys fields 12 | defstruct fields 13 | 14 | @type t :: %__MODULE__{ 15 | A: Types.uint8() 16 | } 17 | 18 | @impl LambdaEthereumConsensus.Container 19 | def schema() do 20 | [ 21 | {:A, {:int, 8}} 22 | ] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/spec/runners/helpers/ssz_static_containers/small_test_struct.ex: -------------------------------------------------------------------------------- 1 | defmodule Helpers.SszStaticContainers.SmallTestStruct do 2 | @moduledoc """ 3 | Struct definition for `SmallTestStruct`. 4 | """ 5 | use LambdaEthereumConsensus.Container 6 | 7 | fields = [ 8 | :A, 9 | :B 10 | ] 11 | 12 | @enforce_keys fields 13 | defstruct fields 14 | 15 | @type t :: %__MODULE__{ 16 | A: Types.uint16(), 17 | B: Types.uint16() 18 | } 19 | 20 | @impl LambdaEthereumConsensus.Container 21 | def schema() do 22 | [ 23 | {:A, {:int, 16}}, 24 | {:B, {:int, 16}} 25 | ] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/spec/runners/helpers/ssz_static_containers/var_test_struct.ex: -------------------------------------------------------------------------------- 1 | defmodule Helpers.SszStaticContainers.VarTestStruct do 2 | @moduledoc """ 3 | Struct definition for `VarTestStruct`. 4 | """ 5 | use LambdaEthereumConsensus.Container 6 | 7 | fields = [ 8 | :A, 9 | :B, 10 | :C 11 | ] 12 | 13 | @enforce_keys fields 14 | defstruct fields 15 | 16 | @type t :: %__MODULE__{ 17 | A: Types.uint16(), 18 | B: list(Types.uint16()), 19 | C: Types.uint8() 20 | } 21 | 22 | @impl LambdaEthereumConsensus.Container 23 | def schema() do 24 | [ 25 | {:A, {:int, 16}}, 26 | {:B, {:list, {:int, 16}, 1024}}, 27 | {:C, {:int, 8}} 28 | ] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/spec/runners/light_client.ex: -------------------------------------------------------------------------------- 1 | defmodule LightClientTestRunner do 2 | alias LambdaEthereumConsensus.StateTransition.Predicates 3 | use ExUnit.CaseTemplate 4 | use TestRunner 5 | 6 | @moduledoc """ 7 | Runner for LightClient test cases. See: https://github.com/ethereum/consensus-specs/tree/dev/tests/formats/light_client 8 | """ 9 | 10 | # Remove handler from here once you implement the corresponding functions 11 | @disabled_handlers [ 12 | # "single_merkle_proof", 13 | "sync", 14 | "update_ranking" 15 | ] 16 | 17 | @impl TestRunner 18 | def skip?(%SpecTestCase{fork: "capella"} = testcase) do 19 | Enum.member?(@disabled_handlers, testcase.handler) 20 | end 21 | 22 | # TODO: We didn't implement lightclient functions yet 23 | def skip?(%SpecTestCase{fork: fork}) when fork in ["deneb", "electra"], do: true 24 | 25 | @impl TestRunner 26 | def run_test_case(%SpecTestCase{} = testcase) do 27 | handle(testcase.handler, testcase) 28 | end 29 | 30 | defp handle("single_merkle_proof", testcase) do 31 | case_dir = SpecTestCase.dir(testcase) 32 | 33 | object_root = 34 | SpecTestUtils.read_ssz_from_file!( 35 | case_dir <> "/object.ssz_snappy", 36 | String.to_existing_atom("Elixir.Types." <> testcase.suite) 37 | ) 38 | |> Ssz.hash_tree_root!() 39 | 40 | %{leaf: leaf, leaf_index: leaf_index, branch: branch} = 41 | YamlElixir.read_from_file!(case_dir <> "/proof.yaml") 42 | |> SpecTestUtils.sanitize_yaml() 43 | 44 | res = 45 | Predicates.valid_merkle_branch?( 46 | leaf, 47 | branch, 48 | Constants.deposit_contract_tree_depth() + 1, 49 | leaf_index, 50 | object_root 51 | ) 52 | 53 | assert true == res 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/spec/runners/random.ex: -------------------------------------------------------------------------------- 1 | defmodule RandomTestRunner do 2 | @moduledoc """ 3 | Runner for random test cases. See: https://github.com/ethereum/consensus-specs/tree/dev/tests/formats/random 4 | """ 5 | 6 | use ExUnit.CaseTemplate 7 | use TestRunner 8 | 9 | @impl TestRunner 10 | def run_test_case(testcase) do 11 | Helpers.ProcessBlocks.process_blocks(testcase) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/spec/runners/sanity.ex: -------------------------------------------------------------------------------- 1 | defmodule SanityTestRunner do 2 | @moduledoc """ 3 | Runner for Sanity test cases. See: https://github.com/ethereum/consensus-specs/tree/dev/tests/formats/sanity 4 | """ 5 | 6 | use ExUnit.CaseTemplate 7 | use TestRunner 8 | 9 | alias LambdaEthereumConsensus.StateTransition 10 | alias LambdaEthereumConsensus.Utils.Diff 11 | alias Types.BeaconState 12 | 13 | # TODO: We need to make sure this is still needed to be here 14 | @disabled_slot_cases [ 15 | "historical_accumulator" 16 | ] 17 | 18 | @impl TestRunner 19 | def skip?(%SpecTestCase{handler: "slots", case: testcase}) do 20 | Enum.member?(@disabled_slot_cases, testcase) 21 | end 22 | 23 | def skip?(_), do: false 24 | 25 | @impl TestRunner 26 | def run_test_case(%SpecTestCase{handler: "slots"} = testcase) do 27 | # TODO process meta.yaml 28 | case_dir = SpecTestCase.dir(testcase) 29 | 30 | pre = SpecTestUtils.read_ssz_from_file!(case_dir <> "/pre.ssz_snappy", BeaconState) 31 | post = SpecTestUtils.read_ssz_from_optional_file!(case_dir <> "/post.ssz_snappy", BeaconState) 32 | 33 | slots_to_process = 34 | YamlElixir.read_from_file!(case_dir <> "/slots.yaml") |> SpecTestUtils.sanitize_yaml() 35 | 36 | assert is_integer(slots_to_process) 37 | 38 | case StateTransition.process_slots(pre, pre.slot + slots_to_process) do 39 | {:ok, state} -> 40 | assert Diff.diff(state, post) == :unchanged 41 | 42 | {:error, error} -> 43 | assert post == nil, "Process slots failed, error: #{error}" 44 | end 45 | end 46 | 47 | @impl TestRunner 48 | def run_test_case(%SpecTestCase{handler: "blocks"} = testcase) do 49 | # TODO process meta.yaml 50 | Helpers.ProcessBlocks.process_blocks(testcase) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/spec/runners/shuffling.ex: -------------------------------------------------------------------------------- 1 | defmodule ShufflingTestRunner do 2 | @moduledoc """ 3 | Runner for Operations test cases. See: https://github.com/ethereum/consensus-specs/tree/dev/tests/formats/shuffling 4 | """ 5 | 6 | use ExUnit.CaseTemplate 7 | use TestRunner 8 | 9 | alias LambdaEthereumConsensus.StateTransition.Misc 10 | alias LambdaEthereumConsensus.StateTransition.Shuffling 11 | 12 | @impl TestRunner 13 | def run_test_case(%SpecTestCase{} = testcase) do 14 | case_dir = SpecTestCase.dir(testcase) 15 | 16 | %{seed: seed, count: index_count, mapping: indices} = 17 | YamlElixir.read_from_file!(case_dir <> "/mapping.yaml") 18 | |> SpecTestUtils.sanitize_yaml() 19 | 20 | handle(testcase.handler, seed, index_count, indices) 21 | end 22 | 23 | defp handle("core", seed, index_count, indices) do 24 | # Testing permute-index by running it for every index in 0..(index_count - 1) and check against expected mapping[i] 25 | for index <- 0..(index_count - 1) do 26 | result = Misc.compute_shuffled_index(index, index_count, seed) 27 | 28 | if index >= index_count or index_count == 0 do 29 | assert result == {:error, "invalid index_count"} 30 | else 31 | assert result == {:ok, Enum.fetch!(indices, index)} 32 | end 33 | end 34 | 35 | shuffled_list = 36 | Shuffling.shuffle_list(0..(index_count - 1)//1 |> Aja.Vector.new(), seed) 37 | |> Aja.Enum.to_list() 38 | 39 | assert shuffled_list == indices 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/spec/testcase.ex: -------------------------------------------------------------------------------- 1 | defmodule SpecTestCase do 2 | @moduledoc """ 3 | Helper methods for deriving test case metadata. 4 | """ 5 | @enforce_keys [:config, :fork, :runner, :handler, :suite, :case] 6 | defstruct [:config, :fork, :runner, :handler, :suite, :case] 7 | 8 | @type t :: %__MODULE__{ 9 | config: binary, 10 | fork: binary, 11 | runner: binary, 12 | handler: binary, 13 | suite: binary, 14 | case: binary 15 | } 16 | 17 | def new([config, fork, runner, handler, suite, cse]) do 18 | %__MODULE__{ 19 | config: config, 20 | fork: fork, 21 | runner: runner, 22 | handler: handler, 23 | suite: suite, 24 | case: cse 25 | } 26 | end 27 | 28 | def name(%__MODULE__{ 29 | config: config, 30 | fork: fork, 31 | runner: runner, 32 | handler: handler, 33 | suite: suite, 34 | case: cse 35 | }) do 36 | "c:#{config} f:#{fork} r:#{runner} h:#{handler} s:#{suite} -> #{cse}" 37 | end 38 | 39 | def dir(%__MODULE__{ 40 | config: config, 41 | fork: fork, 42 | runner: runner, 43 | handler: handler, 44 | suite: suite, 45 | case: cse 46 | }) do 47 | root = SpecTestUtils.vectors_dir() 48 | "#{root}/#{config}/#{fork}/#{runner}/#{handler}/#{suite}/#{cse}" 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | Application.ensure_all_started([:telemetry, :logger]) 3 | # NOTE: logger doesn't fetch configuration from `config/config.exs` in tests 4 | Logger.configure(level: :warning) 5 | LambdaEthereumConsensus.StateTransition.Cache.initialize_cache() 6 | 7 | # Load all modules as ExUnit tests (needed because we use .ex files) 8 | # Copied from https://github.com/elixir-lang/elixir/issues/10983#issuecomment-1133554155 9 | for module <- Application.spec(Mix.Project.config()[:app], :modules) do 10 | ex_unit = Keyword.get(module.module_info(:attributes), :ex_unit_async, []) 11 | 12 | cond do 13 | true in ex_unit -> ExUnit.Server.add_async_module(module) 14 | false in ex_unit -> ExUnit.Server.add_sync_module(module) 15 | true -> :ok 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/unit/bit_field_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.BitFieldTest do 2 | use ExUnit.Case 3 | alias LambdaEthereumConsensus.Utils.BitField 4 | 5 | test "bitwise OR with sizes multiple of 8" do 6 | assert BitField.bitwise_or(<<0b0100_0001::size(8)>>, <<0b0000_0010::size(8)>>) == 7 | <<0b0100_0011::size(8)>> 8 | 9 | assert BitField.bitwise_or(<<0b1010_1001::size(8)>>, <<0b0000_0010::size(8)>>) == 10 | <<0b1010_1011::size(8)>> 11 | 12 | assert BitField.bitwise_or(<<0b0000000000000000::size(16)>>, <<0b1111111111111111::size(16)>>) == 13 | <<0b1111111111111111::size(16)>> 14 | 15 | assert BitField.bitwise_or(<<0b1100110011001100::size(16)>>, <<0b0011001100110011::size(16)>>) == 16 | <<0b1111111111111111::size(16)>> 17 | end 18 | 19 | test "bitwise OR with sizes not multiple of 8" do 20 | assert BitField.bitwise_or(<<0b101::size(3)>>, <<0b010::size(3)>>) == <<0b111::size(3)>> 21 | assert BitField.bitwise_or(<<0b1111::size(7)>>, <<0b0001::size(7)>>) == <<0b1111::size(7)>> 22 | 23 | assert BitField.bitwise_or(<<0b1010101010101::size(13)>>, <<0b0101010101010::size(13)>>) == 24 | <<0b1111111111111::size(13)>> 25 | 26 | assert BitField.bitwise_or(<<0b1111000000001::size(13)>>, <<0b0000111111110::size(13)>>) == 27 | <<0b1111111111111::size(13)>> 28 | end 29 | 30 | test "bitwise OR of all-zero and all-one bitfields" do 31 | assert BitField.bitwise_or(<<0::size(8)>>, <<255::size(8)>>) == <<255::size(8)>> 32 | assert BitField.bitwise_or(<<0::size(4)>>, <<15::size(4)>>) == <<15::size(4)>> 33 | end 34 | 35 | test "bitwise OR of same bitfields" do 36 | assert BitField.bitwise_or(<<0b1010::size(4)>>, <<0b1010::size(4)>>) == <<0b1010::size(4)>> 37 | assert BitField.bitwise_or(<<0b1111::size(4)>>, <<0b1111::size(4)>>) == <<0b1111::size(4)>> 38 | end 39 | 40 | test "bitwise OR with empty bitfields" do 41 | assert BitField.bitwise_or(<<>>, <<>>) == <<>> 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/unit/bit_list_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.BitListTest do 2 | use ExUnit.Case 3 | alias LambdaEthereumConsensus.Utils.BitList 4 | 5 | describe "Sub-byte BitList" do 6 | test "build from binary" do 7 | decoded = BitList.new(<<237, 7>>) 8 | 9 | assert BitList.set?(decoded, 0) == true 10 | assert BitList.set?(decoded, 1) == false 11 | assert BitList.set?(decoded, 4) == false 12 | assert BitList.set?(decoded, 9) == true 13 | 14 | updated_bitlist = 15 | decoded 16 | |> BitList.set(1) 17 | |> BitList.set(4) 18 | |> BitList.clear(0) 19 | |> BitList.clear(9) 20 | 21 | <<254, 5>> = BitList.to_bytes(updated_bitlist) 22 | end 23 | 24 | test "sets a single bit" do 25 | bl = BitList.new(<<0b10100000, 0b1011100, 0b1>>) 26 | 27 | for pos <- 0..15 do 28 | assert bl 29 | |> BitList.set(pos) 30 | |> BitList.set?(pos) 31 | end 32 | end 33 | 34 | test "clears a single bit" do 35 | bl = BitList.new(<<0b10100000, 0b1011100, 0b1>>) 36 | 37 | for pos <- 0..15 do 38 | assert bl 39 | |> BitList.clear(pos) 40 | |> BitList.set?(pos) 41 | |> Kernel.not() 42 | end 43 | end 44 | 45 | test "queries if a bit is set correctly using little-endian bit indexing" do 46 | expected_values = [ 47 | false, 48 | false, 49 | false, 50 | false, 51 | false, 52 | true, 53 | false, 54 | true, 55 | false, 56 | false, 57 | true, 58 | true, 59 | true, 60 | false, 61 | true, 62 | false 63 | ] 64 | 65 | bl = BitList.new(<<0b10100000, 0b1011100, 0b1>>) 66 | assert Enum.map(0..15, &BitList.set?(bl, &1)) == expected_values 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/unit/block_states.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockStatesTest do 2 | use ExUnit.Case 3 | 4 | alias LambdaEthereumConsensus.Store.BlockStates 5 | alias Types.BeaconState 6 | alias Types.StateInfo 7 | 8 | setup_all do 9 | Application.fetch_env!(:lambda_ethereum_consensus, ChainSpec) 10 | |> Keyword.put(:config, MinimalConfig) 11 | |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) 12 | end 13 | 14 | setup %{tmp_dir: tmp_dir} do 15 | Application.fetch_env!(:lambda_ethereum_consensus, ChainSpec) 16 | |> Keyword.put(:config, MinimalConfig) 17 | |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) 18 | 19 | start_link_supervised!({LambdaEthereumConsensus.Store.Db, dir: tmp_dir}) 20 | start_link_supervised!(LambdaEthereumConsensus.Store.BlockStates) 21 | :ok 22 | end 23 | 24 | @tag :tmp_dir 25 | test "Encoded field is calculated if not provided" do 26 | {encoded, decoded} = get_state() 27 | 28 | {:ok, state_info_1} = StateInfo.from_beacon_state(decoded, encoded: encoded) 29 | {:ok, state_info_2} = StateInfo.from_beacon_state(decoded) 30 | 31 | assert state_info_1 == state_info_2 32 | end 33 | 34 | @tag :tmp_dir 35 | test "Save and load state" do 36 | {encoded, decoded} = get_state() 37 | 38 | {:ok, state_info} = StateInfo.from_beacon_state(decoded, encoded: encoded) 39 | 40 | BlockStates.store_state_info(state_info) 41 | loaded_state = BlockStates.get_state_info(state_info.block_root) 42 | 43 | assert state_info == loaded_state 44 | end 45 | 46 | defp get_state() do 47 | {:ok, encoded} = 48 | File.read!("test/fixtures/validator/proposer/beacon_state.ssz_snappy") 49 | |> :snappyer.decompress() 50 | 51 | {:ok, decoded} = SszEx.decode(encoded, BeaconState) 52 | {encoded, decoded} 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/unit/bls_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlsTest do 2 | use ExUnit.Case 3 | 4 | describe "validate_key" do 5 | test "returns true for valid public key" do 6 | valid_public_key = 7 | Base.decode16!( 8 | "8afc8f134790914b4a15d2fa73b07cafd0d30884fd80ca220c8b9503f5f69c33dd27275b129543d2f7f8f635a81867a0", 9 | case: :mixed 10 | ) 11 | 12 | assert Bls.key_validate(valid_public_key) == {:ok, true} 13 | end 14 | 15 | test "returns false for invalid public key" do 16 | invalid_public_key = <<0::384>> 17 | assert Bls.key_validate(invalid_public_key) == {:error, "BlstError(BLST_BAD_ENCODING)"} 18 | end 19 | end 20 | 21 | describe "Private to public key" do 22 | test "return the correct public key for a private key" do 23 | valid_public_key = 24 | Base.decode16!( 25 | "8abb15ca57942b6225af4710bbb74ce8466e99fdc2264d9ffd3b335c7396667e45f537ff1f75ed5afa00585db274f3b6", 26 | case: :mixed 27 | ) 28 | 29 | private_key = 30 | Base.decode16!("18363054f52f3f1fdc9ae50d271de853c582c652ebe8dd0f261da3b00cd98984", 31 | case: :mixed 32 | ) 33 | 34 | assert Bls.derive_pubkey(private_key) == {:ok, valid_public_key} 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/unit/mainnet_config_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.MainnetConfigSmokeTest do 2 | use ExUnit.Case 3 | 4 | doctest HardForkAliasInjection 5 | 6 | setup_all do 7 | Application.fetch_env!(:lambda_ethereum_consensus, ChainSpec) 8 | |> Keyword.put(:config, MainnetConfig) 9 | |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) 10 | end 11 | 12 | test "in mainnet, SLOTS_PER_EPOCH == 32" do 13 | # Chosen because it's unlikely to change 14 | assert ChainSpec.get("SLOTS_PER_EPOCH") == 32 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/unit/minimal_config_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.MinimalConfigSmokeTest do 2 | use ExUnit.Case 3 | 4 | setup_all do 5 | Application.fetch_env!(:lambda_ethereum_consensus, ChainSpec) 6 | |> Keyword.put(:config, MinimalConfig) 7 | |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) 8 | end 9 | 10 | test "in minimal, SLOTS_PER_EPOCH == 8" do 11 | # Chosen because it's unlikely to change 12 | assert ChainSpec.get("SLOTS_PER_EPOCH") == 8 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/unit/p2p_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.P2PTest do 2 | alias LambdaEthereumConsensus.P2P.Utils 3 | use ExUnit.Case 4 | use ExUnitProperties 5 | 6 | property "decode_varint(encode_varint(x)) == {x, \"\"}" do 7 | check all(int <- integer() |> map(&abs/1)) do 8 | encoded = Utils.encode_varint(int) 9 | assert {^int, ""} = Utils.decode_varint(encoded) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/unit/shuffling_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.ShufflingTest do 2 | @moduledoc false 3 | 4 | use ExUnit.Case 5 | 6 | alias LambdaEthereumConsensus.StateTransition.Misc 7 | alias LambdaEthereumConsensus.StateTransition.Shuffling 8 | 9 | doctest LambdaEthereumConsensus.StateTransition.Shuffling 10 | 11 | test "Shuffling a whole list should be equivalent to shuffing each single item" do 12 | seed = 1..32 |> Enum.map(fn _ -> :rand.uniform(256) - 1 end) |> :erlang.iolist_to_binary() 13 | index_count = 100 14 | input = 0..(index_count - 1) 15 | 16 | shuffled = Shuffling.shuffle_list(Aja.Vector.new(input), seed) 17 | 18 | for index <- input do 19 | {:ok, new_index} = Misc.compute_shuffled_index(index, index_count, seed) 20 | assert Aja.Enum.at(shuffled, index) == new_index 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/unit/simple_tree_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.SimpleTreeTest do 2 | use ExUnit.Case 3 | 4 | alias LambdaEthereumConsensus.ForkChoice.Simple.Tree 5 | 6 | test "Create a tree" do 7 | Tree.new("root") 8 | end 9 | 10 | test "Add new blocks to the tree" do 11 | tree = 12 | Tree.new("root") 13 | |> Tree.add_block!("root_child1", "root") 14 | |> Tree.add_block!("root_child2", "root") 15 | |> Tree.add_block!("root_child1_child", "root_child1") 16 | 17 | # We use MapSet to ignore the order of the blocks 18 | expected = MapSet.new(["root_child1", "root_child2"]) 19 | root_children = Tree.get_children!(tree, "root") |> MapSet.new() 20 | 21 | assert MapSet.equal?(root_children, expected) 22 | 23 | assert Tree.get_children!(tree, "root_child1") == ["root_child1_child"] 24 | assert Tree.get_children!(tree, "root_child1_child") == [] 25 | assert Tree.get_children!(tree, "root_child2") == [] 26 | end 27 | 28 | test "Update the tree's root" do 29 | tree = 30 | Tree.new("root") 31 | |> Tree.add_block!("root_child1", "root") 32 | |> Tree.add_block!("root_child2", "root") 33 | |> Tree.add_block!("root_child1_child", "root_child1") 34 | # Update tree's root and prune pre-root blocks 35 | |> Tree.update_root!("root_child1") 36 | 37 | expected_tree = 38 | Tree.new("root_child1") 39 | |> Tree.add_block!("root_child1_child", "root_child1") 40 | 41 | assert tree == expected_tree 42 | 43 | error = {:error, :not_found} 44 | assert Tree.get_children(tree, "root") == error, "root should be pruned" 45 | assert Tree.get_children(tree, "root_child2") == error, "cousins should be pruned" 46 | 47 | assert Tree.get_children!(tree, "root_child1") == ["root_child1_child"] 48 | assert Tree.get_children!(tree, "root_child1_child") == [] 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/unit/state_transition/misc_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.StateTransition.MiscTest do 2 | alias Fixtures.Block 3 | alias LambdaEthereumConsensus.StateTransition.Accessors 4 | alias LambdaEthereumConsensus.StateTransition.Misc 5 | alias LambdaEthereumConsensus.Utils.Diff 6 | 7 | use ExUnit.Case 8 | 9 | setup_all do 10 | Application.fetch_env!(:lambda_ethereum_consensus, ChainSpec) 11 | |> Keyword.put(:config, MinimalConfig) 12 | |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) 13 | end 14 | 15 | @tag :skip 16 | test "Calculating all committees for a single epoch should be the same by any method" do 17 | state = Block.beacon_state_from_file().beacon_state 18 | epoch = Accessors.get_current_epoch(state) 19 | committees = Misc.compute_all_committees(state, epoch) 20 | 21 | assert_all_committees_equal(committees, calculate_all_individually(state, epoch)) 22 | end 23 | 24 | defp calculate_all_individually(state, epoch) do 25 | committee_count_per_slot = Accessors.get_committee_count_per_slot(state, epoch) 26 | slots_per_epoch = ChainSpec.get("SLOTS_PER_EPOCH") 27 | 28 | for slot <- state.slot..(state.slot + slots_per_epoch - 1), 29 | index <- 0..(committee_count_per_slot - 1) do 30 | Accessors.get_beacon_committee(state, slot, index) 31 | end 32 | end 33 | 34 | defp assert_all_committees_equal(all_committees, all_committees_individual) do 35 | adapted_committees = Enum.map(all_committees, &{:ok, &1}) 36 | assert Diff.diff(adapted_committees, all_committees_individual) == :unchanged 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/unit/store/block_by_slot_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.Store.BlockBySlotTest do 2 | alias Fixtures.Random 3 | alias LambdaEthereumConsensus.Store.BlockBySlot 4 | 5 | use ExUnit.Case 6 | 7 | setup %{tmp_dir: tmp_dir} do 8 | start_link_supervised!({LambdaEthereumConsensus.Store.Db, dir: tmp_dir}) 9 | :ok 10 | end 11 | 12 | @tag :tmp_dir 13 | test "Basic saving a block root" do 14 | root = Random.root() 15 | slot = Random.slot() 16 | assert :ok == BlockBySlot.put(slot, root) 17 | assert {:ok, root} == BlockBySlot.get(slot) 18 | end 19 | 20 | @tag :tmp_dir 21 | test "all_present? should return true when checking on a subset or the full set, but false for elements outside" do 22 | Enum.each(2..4, fn slot -> 23 | root = Random.root() 24 | assert :ok == BlockBySlot.put(slot, root) 25 | end) 26 | 27 | assert BlockBySlot.all_present?(2, 4) 28 | assert BlockBySlot.all_present?(3, 3) 29 | refute BlockBySlot.all_present?(1, 4) 30 | refute BlockBySlot.all_present?(2, 5) 31 | refute BlockBySlot.all_present?(1, 1) 32 | end 33 | 34 | @tag :tmp_dir 35 | test "all_present? should return false when elements are missing in between" do 36 | root = Random.root() 37 | BlockBySlot.put(1, root) 38 | BlockBySlot.put(3, root) 39 | 40 | assert BlockBySlot.all_present?(3, 3) 41 | assert BlockBySlot.all_present?(1, 1) 42 | refute BlockBySlot.all_present?(1, 3) 43 | end 44 | 45 | @tag :tmp_dir 46 | test "retrieving an empty slot" do 47 | assert :ok == BlockBySlot.put(1, :empty_slot) 48 | assert {:ok, :empty_slot} == BlockBySlot.get(1) 49 | end 50 | 51 | @tag :tmp_dir 52 | test "Trying to save an atom that's not :empty_slot fails" do 53 | assert_raise(FunctionClauseError, fn -> BlockBySlot.put(1, :some_atom) end) 54 | end 55 | 56 | @tag :tmp_dir 57 | test "Trying to save a non-root binary fails" do 58 | assert_raise(FunctionClauseError, fn -> BlockBySlot.put(1, "Hello") end) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/unit/store/state_info_by_root_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.Store.StateInfoByRoot do 2 | alias Fixtures.Random 3 | alias LambdaEthereumConsensus.Store.StateDb.StateInfoByRoot 4 | alias Types.BeaconState 5 | alias Types.StateInfo 6 | 7 | use ExUnit.Case 8 | 9 | setup_all do 10 | Application.fetch_env!(:lambda_ethereum_consensus, ChainSpec) 11 | |> Keyword.put(:config, MinimalConfig) 12 | |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) 13 | end 14 | 15 | setup %{tmp_dir: tmp_dir} do 16 | start_link_supervised!({LambdaEthereumConsensus.Store.Db, dir: tmp_dir}) 17 | :ok 18 | end 19 | 20 | defp get_state_info() do 21 | {:ok, encoded} = 22 | File.read!("test/fixtures/validator/proposer/beacon_state.ssz_snappy") 23 | |> :snappyer.decompress() 24 | 25 | {:ok, decoded} = SszEx.decode(encoded, BeaconState) 26 | {:ok, state_info} = StateInfo.from_beacon_state(decoded) 27 | state_info 28 | end 29 | 30 | @tag :tmp_dir 31 | test "Get on a non-existent root" do 32 | root = Random.root() 33 | assert :not_found == StateInfoByRoot.get(root) 34 | end 35 | 36 | @tag :tmp_dir 37 | test "Basic saving a state" do 38 | state = get_state_info() 39 | assert :ok == StateInfoByRoot.put(state.root, state) 40 | assert {:ok, state} == StateInfoByRoot.get(state.root) 41 | end 42 | 43 | @tag :tmp_dir 44 | test "Delete one state" do 45 | state = get_state_info() 46 | state_root1 = Random.root() 47 | state_root2 = Random.root() 48 | 49 | assert :ok == StateInfoByRoot.put(state_root1, state) 50 | assert :ok == StateInfoByRoot.put(state_root2, state) 51 | assert :ok == StateInfoByRoot.delete(state_root2) 52 | 53 | assert {:ok, state} == StateInfoByRoot.get(state_root1) 54 | assert :not_found == StateInfoByRoot.get(state_root2) 55 | end 56 | 57 | @tag :tmp_dir 58 | test "Trying to save a different type fails" do 59 | assert_raise(FunctionClauseError, fn -> StateInfoByRoot.put(1, "Hello") end) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/unit/sync_subnet_info.exs: -------------------------------------------------------------------------------- 1 | defmodule Unit.SyncSubnetInfoTest do 2 | alias LambdaEthereumConsensus.Store.Db 3 | alias Types.Checkpoint 4 | alias Types.SyncCommitteeMessage 5 | alias Types.SyncSubnetInfo 6 | 7 | use ExUnit.Case 8 | use Patch 9 | 10 | doctest SyncSubnetInfo 11 | 12 | setup %{tmp_dir: tmp_dir} do 13 | start_link_supervised!({Db, dir: tmp_dir}) 14 | :ok 15 | end 16 | 17 | defp sync_committee_message(validator_index \\ 0) do 18 | %SyncCommitteeMessage{ 19 | slot: 5_057_010_135_270_197_978, 20 | beacon_block_root: 21 | <<31, 38, 101, 174, 248, 168, 116, 226, 15, 39, 218, 148, 42, 8, 80, 80, 241, 149, 162, 22 | 32, 176, 208, 120, 120, 89, 123, 136, 115, 154, 28, 21, 174>>, 23 | validator_index: validator_index, 24 | signature: <<>> 25 | } 26 | end 27 | 28 | @tag :tmp_dir 29 | test "stop collecting with one attestation" do 30 | subnet_id = 1 31 | 32 | expected_message = sync_committee_message() 33 | 34 | SyncSubnetInfo.new_subnet_with_message(subnet_id, sync_committee_message()) 35 | 36 | {:ok, messages} = SyncSubnetInfo.stop_collecting(subnet_id) 37 | 38 | assert [expected_message] == messages 39 | end 40 | 41 | @tag :tmp_dir 42 | test "stop collecting with two attestations" do 43 | subnet_id = 1 44 | 45 | expected_message_1 = sync_committee_message(1) 46 | expected_message_2 = sync_committee_message(2) 47 | 48 | SyncSubnetInfo.new_subnet_with_message(subnet_id, sync_committee_message(1)) 49 | 50 | SyncSubnetInfo.add_message!(subnet_id, sync_committee_message(2)) 51 | 52 | {:ok, messages} = SyncSubnetInfo.stop_collecting(subnet_id) 53 | 54 | assert [expected_message_2, expected_message_1] == messages 55 | end 56 | end 57 | --------------------------------------------------------------------------------