From b73bcae552ed4c379b2995e42aa31ac303beeda7 Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Tue, 27 Aug 2024 18:51:53 +0100 Subject: [PATCH 1/9] attestation pool support and tests --- AllTests-mainnet.md | 1 + .../attestation_pool.nim | 37 +++++++- tests/test_attestation_pool.nim | 84 +++++++++++++++++-- 3 files changed, 112 insertions(+), 10 deletions(-) diff --git a/AllTests-mainnet.md b/AllTests-mainnet.md index 917ac17c2e..3dac2a3d31 100644 --- a/AllTests-mainnet.md +++ b/AllTests-mainnet.md @@ -10,6 +10,7 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 + Aggregated attestations with disjoint comittee bits into a single on-chain aggregate [Pres OK + Attestations with disjoint comittee bits and equal data into single on-chain aggregate [Pr OK + Can add and retrieve simple electra attestations [Preset: mainnet] OK ++ Working with electra aggregates [Preset: mainnet] OK ``` OK: 3/3 Fail: 0/3 Skip: 0/3 ## Attestation pool processing [Preset: mainnet] diff --git a/beacon_chain/consensus_object_pools/attestation_pool.nim b/beacon_chain/consensus_object_pools/attestation_pool.nim index 5723b0e395..1d32b54933 100644 --- a/beacon_chain/consensus_object_pools/attestation_pool.nim +++ b/beacon_chain/consensus_object_pools/attestation_pool.nim @@ -199,11 +199,16 @@ proc addForkChoiceVotes( # hopefully the fork choice will heal itself over time. error "Couldn't add attestation to fork choice, bug?", err = v.error() -func candidateIdx(pool: AttestationPool, slot: Slot): Opt[int] = +func candidateIdx(pool: AttestationPool, slot: Slot, + isElectra: bool = false): Opt[int] = static: doAssert pool.phase0Candidates.len == pool.electraCandidates.len + + let poolLength = if isElectra: + pool.electraCandidates.lenu64 else: pool.phase0Candidates.lenu64 + if slot >= pool.startingSlot and - slot < (pool.startingSlot + pool.phase0Candidates.lenu64): - Opt.some(int(slot mod pool.phase0Candidates.lenu64)) + slot < (pool.startingSlot + poolLength): + Opt.some(int(slot mod poolLength)) else: Opt.none(int) @@ -978,7 +983,8 @@ proc getElectraAttestationsForBlock*( else: default(seq[electra.Attestation]) -func bestValidation(aggregates: openArray[Phase0Validation]): (int, int) = +func bestValidation( + aggregates: openArray[Phase0Validation | ElectraValidation]): (int, int) = # Look for best validation based on number of votes in the aggregate doAssert aggregates.len() > 0, "updateAggregates should have created at least one aggregate" @@ -993,6 +999,29 @@ func bestValidation(aggregates: openArray[Phase0Validation]): (int, int) = bestIndex = i (bestIndex, best) +func getElectraAggregatedAttestation*( + pool: var AttestationPool, slot: Slot, + attestationDataRoot: Eth2Digest, committeeIndex: CommitteeIndex): + Opt[electra.Attestation] = + + let candidateIdx = pool.candidateIdx(slot) + if candidateIdx.isNone: + return Opt.none(electra.Attestation) + + var res: Opt[electra.Attestation] + for _, entry in pool.electraCandidates[candidateIdx.get].mpairs(): + if entry.data.index != committeeIndex.uint64: + continue + + entry.updateAggregates() + + let (bestIndex, best) = bestValidation(entry.aggregates) + + if res.isNone() or best > res.get().aggregation_bits.countOnes(): + res = Opt.some(entry.toElectraAttestation(entry.aggregates[bestIndex])) + + res + func getAggregatedAttestation*( pool: var AttestationPool, slot: Slot, attestation_data_root: Eth2Digest): Opt[phase0.Attestation] = diff --git a/tests/test_attestation_pool.nim b/tests/test_attestation_pool.nim index 510e667b75..cc9e379932 100644 --- a/tests/test_attestation_pool.nim +++ b/tests/test_attestation_pool.nim @@ -28,7 +28,8 @@ import from std/sequtils import toSeq from ./testbcutil import addHeadBlock -func combine(tgt: var phase0.Attestation, src: phase0.Attestation) = +func combine(tgt: var (phase0.Attestation | electra.Attestation), + src: phase0.Attestation | electra.Attestation) = ## Combine the signature and participation bitfield, with the assumption that ## the same data is being signed - if the signatures overlap, they are not ## combined. @@ -849,10 +850,13 @@ suite "Attestation pool electra processing" & preset(): # We should now get both attestations for the block, but the aggregate # should be the one with the most votes pool[].getElectraAttestationsForBlock(state[], cache).len() == 2 - # pool[].getAggregatedAttestation(2.Slot, 0.CommitteeIndex). - # get().aggregation_bits.countOnes() == 2 - # pool[].getAggregatedAttestation(2.Slot, hash_tree_root(att2.data)). - # get().aggregation_bits.countOnes() == 2 + pool[].getElectraAggregatedAttestation(2.Slot, combined[0].data.beacon_block_root, + 0.CommitteeIndex).get().aggregation_bits.countOnes() == 2 + pool[].getElectraAggregatedAttestation(2.Slot, hash_tree_root(att2.data), 0.CommitteeIndex). + get().aggregation_bits.countOnes() == 2 + # requests to get and aggregate from different committees should be empty + pool[].getElectraAggregatedAttestation( + 2.Slot, combined[0].data.beacon_block_root, 1.CommitteeIndex).isNone() let # Someone votes for a different root @@ -949,4 +953,72 @@ suite "Attestation pool electra processing" & preset(): # with same data, 2 committee bits and 3 aggregation bits attestations.len == 1 attestations[0].aggregation_bits.countOnes() == 3 - attestations[0].committee_bits.countOnes() == 2 \ No newline at end of file + attestations[0].committee_bits.countOnes() == 2 + + + test "Working with electra aggregates" & preset(): + let + # Create an attestation for slot 1! + bc0 = get_beacon_committee( + state[], getStateField(state[], slot), 0.CommitteeIndex, cache) + + var + att0 = makeElectraAttestation( + state[], state[].latest_block_root, bc0[0], cache) + att0x = att0 + att1 = makeElectraAttestation( + state[], state[].latest_block_root, bc0[1], cache) + att2 = makeElectraAttestation( + state[], state[].latest_block_root, bc0[2], cache) + att3 = makeElectraAttestation( + state[], state[].latest_block_root, bc0[3], cache) + + # Both attestations include member 2 but neither is a subset of the other + att0.combine(att2) + att1.combine(att2) + + check: + not pool[].covers(att0.data, att0.aggregation_bits) + + pool[].addAttestation( + att0, @[bc0[0], bc0[2]], att0.loadSig, att0.data.slot.start_beacon_time) + pool[].addAttestation( + att1, @[bc0[1], bc0[2]], att1.loadSig, att1.data.slot.start_beacon_time) + + check: + process_slots( + defaultRuntimeConfig, state[], + getStateField(state[], slot) + MIN_ATTESTATION_INCLUSION_DELAY, cache, + info, {}).isOk() + + check: + pool[].getElectraAttestationsForBlock(state[], cache).len() == 1 + # Can get either aggregate here, random! + pool[].getElectraAggregatedAttestation( + 1.Slot, att0.data.beacon_block_root, 0.CommitteeIndex).isSome() + + # Add in attestation 3 - both aggregates should now have it added + pool[].addAttestation( + att3, @[bc0[3]], att3.loadSig, att3.data.slot.start_beacon_time) + + block: + let attestations = pool[].getElectraAttestationsForBlock(state[], cache) + check: + attestations.len() == 1 + attestations[0].aggregation_bits.countOnes() == 6 + # Can get either aggregate here, random! + pool[].getElectraAggregatedAttestation( + 1.Slot, attestations[0].data.beacon_block_root, 0.CommitteeIndex).isSome() + + # Add in attestation 0 as single - attestation 1 is now a superset of the + # aggregates in the pool, so everything else should be removed + pool[].addAttestation( + att0x, @[bc0[0]], att0x.loadSig, att0x.data.slot.start_beacon_time) + + block: + let attestations = pool[].getElectraAttestationsForBlock(state[], cache) + check: + attestations.len() == 1 + attestations[0].aggregation_bits.countOnes() == 4 + pool[].getElectraAggregatedAttestation( + 1.Slot, attestations[0].data.beacon_block_root, 0.CommitteeIndex).isSome() \ No newline at end of file From 75aa093de136d82cd8ba349dea9b208264956feb Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Tue, 27 Aug 2024 18:55:51 +0100 Subject: [PATCH 2/9] REST endpoints changes --- beacon_chain/rpc/rest_constants.nim | 3 ++ beacon_chain/rpc/rest_validator_api.nim | 57 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/beacon_chain/rpc/rest_constants.nim b/beacon_chain/rpc/rest_constants.nim index 2a462b4b41..e9186401f5 100644 --- a/beacon_chain/rpc/rest_constants.nim +++ b/beacon_chain/rpc/rest_constants.nim @@ -87,6 +87,9 @@ const "Invalid attestation data root value" UnableToGetAggregatedAttestationError* = "Unable to retrieve an aggregated attestation" + DeprecatedGetAggregatedAttestation*: string = + "Deprecated endpoint /eth/v1/validator/aggregate_attestation. Replaced with" & + "https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestationV2" MissingRandaoRevealValue* = "Missing `randao_reveal` value" InvalidRandaoRevealValue* = diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index 6e477f2383..ba29a265c1 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -775,6 +775,12 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = router.api2(MethodGet, "/eth/v1/validator/aggregate_attestation") do ( attestation_data_root: Option[Eth2Digest], slot: Option[Slot]) -> RestApiResponse: + + let contextFork = node.dag.cfg.consensusForkAtEpoch(node.currentSlot.epoch) + if contextFork >= ConsensusFork.Electra: + return RestApiResponse.jsonError(Http410, + DeprecatedGetAggregatedAttestation) + let attestation = block: let qslot = @@ -804,6 +810,57 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = res.get() RestApiResponse.jsonResponse(attestation) + # https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestationV2 + router.api2(MethodGet, "/eth/v2/validator/aggregate_attestation") do ( + attestation_data_root: Option[Eth2Digest], + committee_index: Option[CommitteeIndex], + slot: Option[Slot]) -> RestApiResponse: + + let contextFork = node.dag.cfg.consensusForkAtEpoch(node.currentSlot.epoch) + if contextFork < ConsensusFork.Electra: + return RestApiResponse.jsonError(Http400, + UnableToGetAggregatedAttestationError) + + let attestation = + block: + let slot = + block: + if slot.isNone(): + return RestApiResponse.jsonError(Http400, MissingSlotValueError) + let res = slot.get() + if res.isErr(): + return RestApiResponse.jsonError(Http400, InvalidSlotValueError, + $res.error()) + res.get() + let committee_index = + block: + if committee_index.isNone(): + return RestApiResponse.jsonError(Http400, + MissingCommitteeIndexValueError) + let res = committee_index.get() + if res.isErr(): + return RestApiResponse.jsonError(Http400, + InvalidCommitteeIndexValueError, + $res.error()) + res.get() + let root = + block: + if attestation_data_root.isNone(): + return RestApiResponse.jsonError(Http400, + MissingAttestationDataRootValueError) + let res = attestation_data_root.get() + if res.isErr(): + return RestApiResponse.jsonError(Http400, + InvalidAttestationDataRootValueError, $res.error()) + res.get() + let res = + node.attestationPool[].getElectraAggregatedAttestation(slot, root, comittee_index) + if res.isNone(): + return RestApiResponse.jsonError(Http400, + UnableToGetAggregatedAttestationError) + res.get() + RestApiResponse.jsonResponse(attestation) + # https://ethereum.github.io/beacon-APIs/#/Validator/publishAggregateAndProofs router.api2(MethodPost, "/eth/v1/validator/aggregate_and_proofs") do ( contentBody: Option[ContentBody]) -> RestApiResponse: From ee6e83c9077ebd4d08ca38e0a62e1cbe662f8650 Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Tue, 27 Aug 2024 18:59:05 +0100 Subject: [PATCH 3/9] initial ncli and validator client support --- beacon_chain/spec/eth2_apis/rest_types.nim | 1 + .../spec/eth2_apis/rest_validator_calls.nim | 9 +++ ncli/resttest-rules.json | 68 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index 0c2ff4381d..0509cff006 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -530,6 +530,7 @@ type # Types based on the OAPI yaml file - used in responses to requests GetBeaconHeadResponse* = DataEnclosedObject[Slot] GetAggregatedAttestationResponse* = DataEnclosedObject[phase0.Attestation] + GetElectraAggregatedAttestationResponse* = DataEnclosedObject[electra.Attestation] GetAttesterDutiesResponse* = DataRootEnclosedObject[seq[RestAttesterDuty]] GetBlockAttestationsResponse* = DataEnclosedObject[seq[phase0.Attestation]] GetBlockHeaderResponse* = DataOptimisticAndFinalizedObject[RestBlockHeaderInfo] diff --git a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim index d12b221523..0672615d21 100644 --- a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim @@ -71,6 +71,15 @@ proc getAggregatedAttestationPlain*( meth: MethodGet.} ## https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation +proc getAggregatedAttestationPlainV2*( + attestation_data_root: Eth2Digest, + slot: Slot, + committee_index: CommitteeIndex + ): RestPlainResponse {. + rest, endpoint: "/eth/v2/validator/aggregate_attestation" + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation + proc publishAggregateAndProofs*( body: seq[phase0.SignedAggregateAndProof] ): RestPlainResponse {. diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index 98cd240601..071a8c38bf 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -4523,6 +4523,74 @@ "status": {"operator": "oneof", "value": ["400", "200"]} } }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v2/validator/aggregate_attestation", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] + } + }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v2/validator/aggregate_attestation?slot=0", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] + } + }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v2/validator/aggregate_attestation?slot=&attestation_data_root=", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] + } + }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v2/validator/aggregate_attestation?slot=&attestation_data_root=&committee_index=", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] + } + }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v2/validator/aggregate_attestation?slot=0&attestation_data_root=0x0000000000000000000000000000000000000000000000000000000000000000", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "oneof", "value": ["400", "200"]} + } + }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v2/validator/aggregate_attestation?slot=0&attestation_data_root=0x0000000000000000000000000000000000000000000000000000000000000000&committee_index=0", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "oneof", "value": ["400", "200"]} + } + }, { "topics": ["validator", "attester_duties"], "request": { From 416fd468c6b90aaa8a2fcdfb6379052c1f5c0b4f Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Tue, 27 Aug 2024 22:03:06 +0100 Subject: [PATCH 4/9] updated tests file --- AllTests-mainnet.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AllTests-mainnet.md b/AllTests-mainnet.md index 3dac2a3d31..d8c78443c7 100644 --- a/AllTests-mainnet.md +++ b/AllTests-mainnet.md @@ -12,7 +12,7 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 + Can add and retrieve simple electra attestations [Preset: mainnet] OK + Working with electra aggregates [Preset: mainnet] OK ``` -OK: 3/3 Fail: 0/3 Skip: 0/3 +OK: 4/4 Fail: 0/4 Skip: 0/4 ## Attestation pool processing [Preset: mainnet] ```diff + Attestation from different branch [Preset: mainnet] OK @@ -1041,4 +1041,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2 OK: 9/9 Fail: 0/9 Skip: 0/9 ---TOTAL--- -OK: 693/698 Fail: 0/698 Skip: 5/698 +OK: 694/699 Fail: 0/699 Skip: 5/699 From e8b3b556308e86c75739af3a8eadfc0419f82ac9 Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Wed, 28 Aug 2024 00:26:56 +0100 Subject: [PATCH 5/9] fixed typos --- beacon_chain/rpc/rest_validator_api.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index ba29a265c1..5c3aafe24a 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -854,7 +854,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = InvalidAttestationDataRootValueError, $res.error()) res.get() let res = - node.attestationPool[].getElectraAggregatedAttestation(slot, root, comittee_index) + node.attestationPool[].getElectraAggregatedAttestation(slot, root, committee_index) if res.isNone(): return RestApiResponse.jsonError(Http400, UnableToGetAggregatedAttestationError) From e104f34e2c59138f0abb380b608c8c9a7d5a80ef Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Wed, 28 Aug 2024 20:00:07 +0100 Subject: [PATCH 6/9] review improvements --- .../attestation_pool.nim | 4 +-- beacon_chain/rpc/rest_validator_api.nim | 28 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/beacon_chain/consensus_object_pools/attestation_pool.nim b/beacon_chain/consensus_object_pools/attestation_pool.nim index 1d32b54933..18d648249a 100644 --- a/beacon_chain/consensus_object_pools/attestation_pool.nim +++ b/beacon_chain/consensus_object_pools/attestation_pool.nim @@ -984,7 +984,7 @@ proc getElectraAttestationsForBlock*( default(seq[electra.Attestation]) func bestValidation( - aggregates: openArray[Phase0Validation | ElectraValidation]): (int, int) = + aggregates: openArray[Phase0Validation | ElectraValidation]): (int, int) = # Look for best validation based on number of votes in the aggregate doAssert aggregates.len() > 0, "updateAggregates should have created at least one aggregate" @@ -1010,7 +1010,7 @@ func getElectraAggregatedAttestation*( var res: Opt[electra.Attestation] for _, entry in pool.electraCandidates[candidateIdx.get].mpairs(): - if entry.data.index != committeeIndex.uint64: + if entry.data.index != committeeIndex.distinctBase: continue entry.updateAggregates() diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index 5c3aafe24a..f0d8414bd5 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -776,11 +776,6 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = attestation_data_root: Option[Eth2Digest], slot: Option[Slot]) -> RestApiResponse: - let contextFork = node.dag.cfg.consensusForkAtEpoch(node.currentSlot.epoch) - if contextFork >= ConsensusFork.Electra: - return RestApiResponse.jsonError(Http410, - DeprecatedGetAggregatedAttestation) - let attestation = block: let qslot = @@ -803,7 +798,13 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = InvalidAttestationDataRootValueError, $res.error()) res.get() let res = - node.attestationPool[].getAggregatedAttestation(qslot, qroot) + block: + let contextFork = node.dag.cfg.consensusForkAtEpoch(epoch(qslot)) + if contextFork >= ConsensusFork.Electra: + return RestApiResponse.jsonError(Http410, + DeprecatedGetAggregatedAttestation) + + node.attestationPool[].getAggregatedAttestation(qslot, qroot) if res.isNone(): return RestApiResponse.jsonError(Http400, UnableToGetAggregatedAttestationError) @@ -816,14 +817,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = committee_index: Option[CommitteeIndex], slot: Option[Slot]) -> RestApiResponse: - let contextFork = node.dag.cfg.consensusForkAtEpoch(node.currentSlot.epoch) - if contextFork < ConsensusFork.Electra: - return RestApiResponse.jsonError(Http400, - UnableToGetAggregatedAttestationError) - let attestation = block: - let slot = + let qslot = block: if slot.isNone(): return RestApiResponse.jsonError(Http400, MissingSlotValueError) @@ -854,7 +850,13 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = InvalidAttestationDataRootValueError, $res.error()) res.get() let res = - node.attestationPool[].getElectraAggregatedAttestation(slot, root, committee_index) + block: + let contextFork = node.dag.cfg.consensusForkAtEpoch(epoch(qslot)) + if contextFork < ConsensusFork.Electra: + return RestApiResponse.jsonError(Http400, + UnableToGetAggregatedAttestationError) + + node.attestationPool[].getElectraAggregatedAttestation(qslot, root, committee_index) if res.isNone(): return RestApiResponse.jsonError(Http400, UnableToGetAggregatedAttestationError) From 364b3bc4b824faa890f779189c770f2d8b30b1c5 Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Mon, 2 Sep 2024 13:22:08 +0100 Subject: [PATCH 7/9] remove V1 endpoint --- beacon_chain/rpc/rest_constants.nim | 3 -- beacon_chain/rpc/rest_validator_api.nim | 49 ++----------------- .../spec/eth2_apis/rest_validator_calls.nim | 10 +--- beacon_chain/validator_client/api.nim | 7 +-- .../validator_client/attestation_service.nim | 1 + ncli/resttest-rules.json | 46 ----------------- 6 files changed, 10 insertions(+), 106 deletions(-) diff --git a/beacon_chain/rpc/rest_constants.nim b/beacon_chain/rpc/rest_constants.nim index e9186401f5..2a462b4b41 100644 --- a/beacon_chain/rpc/rest_constants.nim +++ b/beacon_chain/rpc/rest_constants.nim @@ -87,9 +87,6 @@ const "Invalid attestation data root value" UnableToGetAggregatedAttestationError* = "Unable to retrieve an aggregated attestation" - DeprecatedGetAggregatedAttestation*: string = - "Deprecated endpoint /eth/v1/validator/aggregate_attestation. Replaced with" & - "https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestationV2" MissingRandaoRevealValue* = "Missing `randao_reveal` value" InvalidRandaoRevealValue* = diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index f0d8414bd5..087db88536 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -771,46 +771,6 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = makeAttestationData(epochRef, qhead.atSlot(qslot), qindex) RestApiResponse.jsonResponse(adata) - # https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation - router.api2(MethodGet, "/eth/v1/validator/aggregate_attestation") do ( - attestation_data_root: Option[Eth2Digest], - slot: Option[Slot]) -> RestApiResponse: - - let attestation = - block: - let qslot = - block: - if slot.isNone(): - return RestApiResponse.jsonError(Http400, MissingSlotValueError) - let res = slot.get() - if res.isErr(): - return RestApiResponse.jsonError(Http400, InvalidSlotValueError, - $res.error()) - res.get() - let qroot = - block: - if attestation_data_root.isNone(): - return RestApiResponse.jsonError(Http400, - MissingAttestationDataRootValueError) - let res = attestation_data_root.get() - if res.isErr(): - return RestApiResponse.jsonError(Http400, - InvalidAttestationDataRootValueError, $res.error()) - res.get() - let res = - block: - let contextFork = node.dag.cfg.consensusForkAtEpoch(epoch(qslot)) - if contextFork >= ConsensusFork.Electra: - return RestApiResponse.jsonError(Http410, - DeprecatedGetAggregatedAttestation) - - node.attestationPool[].getAggregatedAttestation(qslot, qroot) - if res.isNone(): - return RestApiResponse.jsonError(Http400, - UnableToGetAggregatedAttestationError) - res.get() - RestApiResponse.jsonResponse(attestation) - # https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestationV2 router.api2(MethodGet, "/eth/v2/validator/aggregate_attestation") do ( attestation_data_root: Option[Eth2Digest], @@ -852,11 +812,10 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = let res = block: let contextFork = node.dag.cfg.consensusForkAtEpoch(epoch(qslot)) - if contextFork < ConsensusFork.Electra: - return RestApiResponse.jsonError(Http400, - UnableToGetAggregatedAttestationError) - - node.attestationPool[].getElectraAggregatedAttestation(qslot, root, committee_index) + if contextFork >= ConsensusFork.Electra: + node.attestationPool[].getElectraAggregatedAttestation(qslot, root, committee_index) + else: + node.attestationPool[].getAggregatedAttestation(qslot, root) if res.isNone(): return RestApiResponse.jsonError(Http400, UnableToGetAggregatedAttestationError) diff --git a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim index 0672615d21..9704f42656 100644 --- a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim @@ -63,14 +63,6 @@ proc produceAttestationDataPlain*( meth: MethodGet.} ## https://ethereum.github.io/beacon-APIs/#/Validator/produceAttestationData -proc getAggregatedAttestationPlain*( - attestation_data_root: Eth2Digest, - slot: Slot - ): RestPlainResponse {. - rest, endpoint: "/eth/v1/validator/aggregate_attestation" - meth: MethodGet.} - ## https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation - proc getAggregatedAttestationPlainV2*( attestation_data_root: Eth2Digest, slot: Slot, @@ -78,7 +70,7 @@ proc getAggregatedAttestationPlainV2*( ): RestPlainResponse {. rest, endpoint: "/eth/v2/validator/aggregate_attestation" meth: MethodGet.} - ## https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation + ## https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getPoolAttestationsV2 proc publishAggregateAndProofs*( body: seq[phase0.SignedAggregateAndProof] diff --git a/beacon_chain/validator_client/api.nim b/beacon_chain/validator_client/api.nim index d02254aae2..4f2c589394 100644 --- a/beacon_chain/validator_client/api.nim +++ b/beacon_chain/validator_client/api.nim @@ -1662,6 +1662,7 @@ proc submitPoolSyncCommitteeSignature*( proc getAggregatedAttestation*( vc: ValidatorClientRef, slot: Slot, + committee_index: CommitteeIndex, root: Eth2Digest, strategy: ApiStrategyKind ): Future[phase0.Attestation] {.async.} = @@ -1678,7 +1679,7 @@ proc getAggregatedAttestation*( OneThirdDuration, ViableNodeStatus, {BeaconNodeRole.AggregatedData}, - getAggregatedAttestationPlain(it, root, slot)): + getAggregatedAttestationPlainV2(it, root, slot, committee_index)): if apiResponse.isErr(): handleCommunicationError() ApiResponse[GetAggregatedAttestationResponse].err(apiResponse.error) @@ -1718,7 +1719,7 @@ proc getAggregatedAttestation*( OneThirdDuration, ViableNodeStatus, {BeaconNodeRole.AggregatedData}, - getAggregatedAttestationPlain(it, root, slot), + getAggregatedAttestationPlainV2(it, root, slot, committee_index), getAggregatedAttestationDataScore(itresponse)): if apiResponse.isErr(): handleCommunicationError() @@ -1764,7 +1765,7 @@ proc getAggregatedAttestation*( OneThirdDuration, ViableNodeStatus, {BeaconNodeRole.AggregatedData}, - getAggregatedAttestationPlain(it, root, slot)): + getAggregatedAttestationPlainV2(it, root, slot, committee_index)): if apiResponse.isErr(): handleCommunicationError() false diff --git a/beacon_chain/validator_client/attestation_service.nim b/beacon_chain/validator_client/attestation_service.nim index 33feb85dd4..fd3be30d59 100644 --- a/beacon_chain/validator_client/attestation_service.nim +++ b/beacon_chain/validator_client/attestation_service.nim @@ -281,6 +281,7 @@ proc produceAndPublishAggregates(service: AttestationServiceRef, let aggAttestation = try: await vc.getAggregatedAttestation(slot, attestationRoot, + committeeIndex, ApiStrategyKind.Best) except ValidatorApiError as exc: warn "Unable to get aggregated attestation data", slot = slot, diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index 071a8c38bf..a8560c342d 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -4477,52 +4477,6 @@ "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] } }, - { - "topics": ["validator", "aggregate_attestation"], - "request": { - "url": "/eth/v1/validator/aggregate_attestation", - "headers": {"Accept": "application/json"} - }, - "response": { - "status": {"operator": "equals", "value": "400"}, - "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], - "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] - } - }, - { - "topics": ["validator", "aggregate_attestation"], - "request": { - "url": "/eth/v1/validator/aggregate_attestation?slot=0", - "headers": {"Accept": "application/json"} - }, - "response": { - "status": {"operator": "equals", "value": "400"}, - "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], - "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] - } - }, - { - "topics": ["validator", "aggregate_attestation"], - "request": { - "url": "/eth/v1/validator/aggregate_attestation?slot=&attestation_data_root=", - "headers": {"Accept": "application/json"} - }, - "response": { - "status": {"operator": "equals", "value": "400"}, - "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], - "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] - } - }, - { - "topics": ["validator", "aggregate_attestation"], - "request": { - "url": "/eth/v1/validator/aggregate_attestation?slot=0&attestation_data_root=0x0000000000000000000000000000000000000000000000000000000000000000", - "headers": {"Accept": "application/json"} - }, - "response": { - "status": {"operator": "oneof", "value": ["400", "200"]} - } - }, { "topics": ["validator", "aggregate_attestation"], "request": { From dbe6b0215c2c21b17205478a680fd58d2166bfde Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Tue, 3 Sep 2024 17:35:58 +0100 Subject: [PATCH 8/9] revert v1 removal --- beacon_chain/rpc/rest_validator_api.nim | 41 ++++++++++++++--- .../spec/eth2_apis/rest_validator_calls.nim | 8 ++++ beacon_chain/validator_client/api.nim | 7 ++- .../validator_client/attestation_service.nim | 1 - ncli/resttest-rules.json | 46 +++++++++++++++++++ 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index 087db88536..cd773cc1ed 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -771,7 +771,40 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = makeAttestationData(epochRef, qhead.atSlot(qslot), qindex) RestApiResponse.jsonResponse(adata) - # https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestationV2 + # https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation + router.api2(MethodGet, "/eth/v1/validator/aggregate_attestation") do ( + attestation_data_root: Option[Eth2Digest], + slot: Option[Slot]) -> RestApiResponse: + let attestation = + block: + let qslot = + block: + if slot.isNone(): + return RestApiResponse.jsonError(Http400, MissingSlotValueError) + let res = slot.get() + if res.isErr(): + return RestApiResponse.jsonError(Http400, InvalidSlotValueError, + $res.error()) + res.get() + let qroot = + block: + if attestation_data_root.isNone(): + return RestApiResponse.jsonError(Http400, + MissingAttestationDataRootValueError) + let res = attestation_data_root.get() + if res.isErr(): + return RestApiResponse.jsonError(Http400, + InvalidAttestationDataRootValueError, $res.error()) + res.get() + let res = + node.attestationPool[].getAggregatedAttestation(qslot, qroot) + if res.isNone(): + return RestApiResponse.jsonError(Http400, + UnableToGetAggregatedAttestationError) + res.get() + RestApiResponse.jsonResponse(attestation) + + # https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getPoolAttestationsV2 router.api2(MethodGet, "/eth/v2/validator/aggregate_attestation") do ( attestation_data_root: Option[Eth2Digest], committee_index: Option[CommitteeIndex], @@ -811,11 +844,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = res.get() let res = block: - let contextFork = node.dag.cfg.consensusForkAtEpoch(epoch(qslot)) - if contextFork >= ConsensusFork.Electra: - node.attestationPool[].getElectraAggregatedAttestation(qslot, root, committee_index) - else: - node.attestationPool[].getAggregatedAttestation(qslot, root) + node.attestationPool[].getElectraAggregatedAttestation(qslot, root, committee_index) if res.isNone(): return RestApiResponse.jsonError(Http400, UnableToGetAggregatedAttestationError) diff --git a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim index 9704f42656..f46d136b24 100644 --- a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim @@ -63,6 +63,14 @@ proc produceAttestationDataPlain*( meth: MethodGet.} ## https://ethereum.github.io/beacon-APIs/#/Validator/produceAttestationData +proc getAggregatedAttestationPlain*( + attestation_data_root: Eth2Digest, + slot: Slot + ): RestPlainResponse {. + rest, endpoint: "/eth/v1/validator/aggregate_attestation" + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation + proc getAggregatedAttestationPlainV2*( attestation_data_root: Eth2Digest, slot: Slot, diff --git a/beacon_chain/validator_client/api.nim b/beacon_chain/validator_client/api.nim index 4f2c589394..d02254aae2 100644 --- a/beacon_chain/validator_client/api.nim +++ b/beacon_chain/validator_client/api.nim @@ -1662,7 +1662,6 @@ proc submitPoolSyncCommitteeSignature*( proc getAggregatedAttestation*( vc: ValidatorClientRef, slot: Slot, - committee_index: CommitteeIndex, root: Eth2Digest, strategy: ApiStrategyKind ): Future[phase0.Attestation] {.async.} = @@ -1679,7 +1678,7 @@ proc getAggregatedAttestation*( OneThirdDuration, ViableNodeStatus, {BeaconNodeRole.AggregatedData}, - getAggregatedAttestationPlainV2(it, root, slot, committee_index)): + getAggregatedAttestationPlain(it, root, slot)): if apiResponse.isErr(): handleCommunicationError() ApiResponse[GetAggregatedAttestationResponse].err(apiResponse.error) @@ -1719,7 +1718,7 @@ proc getAggregatedAttestation*( OneThirdDuration, ViableNodeStatus, {BeaconNodeRole.AggregatedData}, - getAggregatedAttestationPlainV2(it, root, slot, committee_index), + getAggregatedAttestationPlain(it, root, slot), getAggregatedAttestationDataScore(itresponse)): if apiResponse.isErr(): handleCommunicationError() @@ -1765,7 +1764,7 @@ proc getAggregatedAttestation*( OneThirdDuration, ViableNodeStatus, {BeaconNodeRole.AggregatedData}, - getAggregatedAttestationPlainV2(it, root, slot, committee_index)): + getAggregatedAttestationPlain(it, root, slot)): if apiResponse.isErr(): handleCommunicationError() false diff --git a/beacon_chain/validator_client/attestation_service.nim b/beacon_chain/validator_client/attestation_service.nim index fd3be30d59..33feb85dd4 100644 --- a/beacon_chain/validator_client/attestation_service.nim +++ b/beacon_chain/validator_client/attestation_service.nim @@ -281,7 +281,6 @@ proc produceAndPublishAggregates(service: AttestationServiceRef, let aggAttestation = try: await vc.getAggregatedAttestation(slot, attestationRoot, - committeeIndex, ApiStrategyKind.Best) except ValidatorApiError as exc: warn "Unable to get aggregated attestation data", slot = slot, diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index a8560c342d..071a8c38bf 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -4477,6 +4477,52 @@ "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] } }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v1/validator/aggregate_attestation", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] + } + }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v1/validator/aggregate_attestation?slot=0", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] + } + }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v1/validator/aggregate_attestation?slot=&attestation_data_root=", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], + "body": [{"operator": "jstructcmpns", "value": {"code": 400, "message": ""}}] + } + }, + { + "topics": ["validator", "aggregate_attestation"], + "request": { + "url": "/eth/v1/validator/aggregate_attestation?slot=0&attestation_data_root=0x0000000000000000000000000000000000000000000000000000000000000000", + "headers": {"Accept": "application/json"} + }, + "response": { + "status": {"operator": "oneof", "value": ["400", "200"]} + } + }, { "topics": ["validator", "aggregate_attestation"], "request": { From fa770eb811188dce63bbc35d89acea9f2a3a6189 Mon Sep 17 00:00:00 2001 From: Pedro Miranda Date: Thu, 5 Sep 2024 17:24:52 +0100 Subject: [PATCH 9/9] V2 endpoint version available to pre electra --- beacon_chain/rpc/rest_validator_api.nim | 77 +++++++++++++------------ 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index cd773cc1ed..c7e6966f0c 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -810,46 +810,51 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = committee_index: Option[CommitteeIndex], slot: Option[Slot]) -> RestApiResponse: - let attestation = + let qslot = block: - let qslot = - block: - if slot.isNone(): - return RestApiResponse.jsonError(Http400, MissingSlotValueError) - let res = slot.get() - if res.isErr(): - return RestApiResponse.jsonError(Http400, InvalidSlotValueError, - $res.error()) - res.get() - let committee_index = - block: - if committee_index.isNone(): - return RestApiResponse.jsonError(Http400, - MissingCommitteeIndexValueError) - let res = committee_index.get() - if res.isErr(): - return RestApiResponse.jsonError(Http400, - InvalidCommitteeIndexValueError, - $res.error()) - res.get() - let root = - block: - if attestation_data_root.isNone(): - return RestApiResponse.jsonError(Http400, + if slot.isNone(): + return RestApiResponse.jsonError(Http400, MissingSlotValueError) + let res = slot.get() + if res.isErr(): + return RestApiResponse.jsonError(Http400, InvalidSlotValueError, + $res.error()) + res.get() + let committee_index = + block: + if committee_index.isNone(): + return RestApiResponse.jsonError(Http400, + MissingCommitteeIndexValueError) + let res = committee_index.get() + if res.isErr(): + return RestApiResponse.jsonError(Http400, + InvalidCommitteeIndexValueError, + $res.error()) + res.get() + let root = + block: + if attestation_data_root.isNone(): + return RestApiResponse.jsonError(Http400, MissingAttestationDataRootValueError) - let res = attestation_data_root.get() - if res.isErr(): - return RestApiResponse.jsonError(Http400, - InvalidAttestationDataRootValueError, $res.error()) - res.get() - let res = - block: - node.attestationPool[].getElectraAggregatedAttestation(qslot, root, committee_index) - if res.isNone(): + let res = attestation_data_root.get() + if res.isErr(): return RestApiResponse.jsonError(Http400, - UnableToGetAggregatedAttestationError) + InvalidAttestationDataRootValueError, $res.error()) res.get() - RestApiResponse.jsonResponse(attestation) + let phase0_attestations = + node.attestationPool[].getAggregatedAttestation(qslot, root) + + if phase0_attestations.isSome(): + return RestApiResponse.jsonResponse(phase0_attestations.get()) + + let electra_attestations = + node.attestationPool[].getElectraAggregatedAttestation(qslot, + root, + committee_index) + + if electra_attestations.isSome(): + return RestApiResponse.jsonResponse(electra_attestations.get()) + + RestApiResponse.jsonError(Http400, UnableToGetAggregatedAttestationError) # https://ethereum.github.io/beacon-APIs/#/Validator/publishAggregateAndProofs router.api2(MethodPost, "/eth/v1/validator/aggregate_and_proofs") do (