Add GetAggregateAttestation V2 endpoint version (#6511)
* attestation pool support and tests * REST endpoints changes * initial ncli and validator client support * updated tests file * fixed typos * review improvements * remove V1 endpoint * revert v1 removal * V2 endpoint version available to pre electra --------- Co-authored-by: Pedro Miranda <pedro.miranda@nimbus.team>
This commit is contained in:
parent
f9a4add803
commit
1ac9b851b9
|
@ -10,8 +10,9 @@ 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
|
||||
OK: 4/4 Fail: 0/4 Skip: 0/4
|
||||
## Attestation pool processing [Preset: mainnet]
|
||||
```diff
|
||||
+ Attestation from different branch [Preset: mainnet] OK
|
||||
|
@ -1113,4 +1114,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
|
|||
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||
|
||||
---TOTAL---
|
||||
OK: 758/763 Fail: 0/763 Skip: 5/763
|
||||
OK: 759/764 Fail: 0/764 Skip: 5/764
|
||||
|
|
|
@ -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.distinctBase:
|
||||
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] =
|
||||
|
|
|
@ -804,6 +804,58 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
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],
|
||||
slot: Option[Slot]) -> RestApiResponse:
|
||||
|
||||
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,
|
||||
MissingAttestationDataRootValueError)
|
||||
let res = attestation_data_root.get()
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
InvalidAttestationDataRootValueError, $res.error())
|
||||
res.get()
|
||||
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 (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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/?urls.primaryName=dev#/Beacon/getPoolAttestationsV2
|
||||
|
||||
proc publishAggregateAndProofs*(
|
||||
body: seq[phase0.SignedAggregateAndProof]
|
||||
): RestPlainResponse {.
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
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()
|
Loading…
Reference in New Issue