Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
146 commits
Select commit Hold shift + click to select a range
a01aa5d
exporter: drop dead vendor sszgen directive (file is hand-maintained)
iurii-ssv Jun 23, 2026
5b7e5c0
gloas: add node-side ePBS wire types and Gloas fork placeholder
iurii-ssv Jun 23, 2026
c8a7fb6
beacon: wire GLOAS_FORK_EPOCH from BN spec and add (*Beacon).IsGloas
iurii-ssv Jun 23, 2026
806c798
gloas: add PTC payload-attestation types and goclient endpoints
iurii-ssv Jun 23, 2026
e5f2061
gloas: align ePBS role names with SIP #94 and map beacon->runner roles
iurii-ssv Jun 23, 2026
b7b6236
beacon: expose PTCCalls on the BeaconNode interface
iurii-ssv Jun 23, 2026
29d31ee
gloas, goclient: dedupe PTC JSON hex-decoding and client-fallback loops
iurii-ssv Jun 23, 2026
52484bf
ekm: support Gloas (ePBS) PTC payload-attestation signing
iurii-ssv Jun 24, 2026
523151c
gloas: add PTC attester runner on ssv-spec ePBS constants
iurii-ssv Jun 24, 2026
7ab07f3
gloas: register PTCAttesterRunner and add runner unit tests
iurii-ssv Jun 24, 2026
ebf9429
gloas: add PTC attestation scheduler handler
iurii-ssv Jun 24, 2026
2a82615
message/validation: accept Gloas (ePBS) PTC attestation messages
iurii-ssv Jun 24, 2026
39d1af4
gloas: move DataVersionGloas to networkconfig and tidy fork helpers
iurii-ssv Jun 24, 2026
d614d75
networkconfig: add GlamsterdamDevnet (ePBS devnet) stub
iurii-ssv Jun 24, 2026
03a60de
docker: exclude tla/ from the build context
iurii-ssv Jun 24, 2026
df1b0d6
ptc: address code-review feedback
iurii-ssv Jun 24, 2026
c1f7f5c
ptc: reconcile runner with boole-fork's renamed duty-completion API
iurii-ssv Jun 24, 2026
746259e
gloas: add ProposerPreferences wire types, endpoint abstraction, signing
iurii-ssv Jun 24, 2026
296a098
gloas: add ProposerPreferences runner (SIP #94 §5)
iurii-ssv Jun 24, 2026
844605c
gloas: emit & route ProposerPreferences duties (SIP #94 §5)
iurii-ssv Jun 24, 2026
9365623
gloas: ProposerPreferences message validation (SIP #94 §5)
iurii-ssv Jun 24, 2026
4f09b43
gloas: deprecate ValidatorRegistration at the fork (SIP #94 §5)
iurii-ssv Jun 24, 2026
cc843ab
gloas: pre-fork ProposerPreferences emission window (SIP #94 §5)
iurii-ssv Jun 24, 2026
3659ace
gloas: ProposerPreferences spam hardening (SIP #94 §5)
iurii-ssv Jun 24, 2026
06bb3c8
gloas: ProposerPreferences runner unit tests (SIP #94 §5)
iurii-ssv Jun 24, 2026
fdad680
gloas: ProposerPreferences reorg re-emission (SIP #94 §5)
iurii-ssv Jun 25, 2026
71b82b7
gloas: ProposerPreferences multi-slot runner + review fixes (SIP #94 §5)
iurii-ssv Jun 25, 2026
7880f12
gloas: dedup ProposerPreferences dependent_root fetches (SIP #94 §5)
iurii-ssv Jun 25, 2026
3e56803
gloas: add NewGloasVoteChecker for §2 attestations (SIP #94 §2)
iurii-ssv Jun 25, 2026
ec85f13
gloas: wire GloasBeaconVote into the committee runner (SIP #94 §2)
iurii-ssv Jun 25, 2026
d58fd4b
gloas: recognize §2 GloasBeaconVote in observer + duty tracer (SIP #9…
iurii-ssv Jun 25, 2026
4a8261a
gloas: test §2 fork-aware decode in observer + duty tracer (SIP #94 §2)
iurii-ssv Jun 25, 2026
974c6c8
gloas: retime duty deadlines to quarters at the fork (SIP #94 §1)
iurii-ssv Jun 25, 2026
b0584fe
gloas: add node-side Gloas block + bid SSZ types (SIP #94 §4)
iurii-ssv Jun 25, 2026
4a8c106
gloas: add goclient produce/publish for Gloas blocks (SIP #94 §4)
iurii-ssv Jun 25, 2026
a538cdf
gloas: run the proposer duty on node-side Gloas blocks (SIP #94 §4)
iurii-ssv Jun 25, 2026
2c99bc4
gloas: tidy stale comments and a parse-error message from review
iurii-ssv Jun 25, 2026
ec7d776
runner: guard GetStateRoot against a nil State
iurii-ssv Jun 25, 2026
951104f
goclient: drop stale 2/3-of-slot wording from the aggregate/contribut…
iurii-ssv Jun 25, 2026
9ad18d7
gloas: content-match before decoding on the proposer submit path
iurii-ssv Jun 25, 2026
6bbc8e4
networkconfig: add IsGloasAtSlot and use it across slot-keyed callers
iurii-ssv Jun 25, 2026
06969e3
gloas: type ExecutionPayloadBid.PrevRandao as Hash32
iurii-ssv Jun 25, 2026
70378e5
gloas: add §6 envelope QBFT types (SIP #94 §6)
iurii-ssv Jun 26, 2026
8dfc7f9
gloas: add §6 envelope value-check + decided-block-root store (SIP #9…
iurii-ssv Jun 26, 2026
616bb01
gloas: wire the proposer side of §6 — record the §4 root + trigger th…
iurii-ssv Jun 26, 2026
b65a40b
gloas: address §6 envelope/proposer review findings
iurii-ssv Jun 26, 2026
c9cd100
gloas: add the §6 EnvelopeBuilder runner and register it
iurii-ssv Jun 26, 2026
84ae18e
runner: dedup the {BaseRunner, ValCheck} JSON (de)serialization
iurii-ssv Jun 26, 2026
3ae0488
message/validation: accept the §6 envelope-builder role (role 9)
iurii-ssv Jun 26, 2026
f0d0c65
gloas: implement the §6 heavy payload — types, goclient, runner, trigger
iurii-ssv Jun 26, 2026
a77d70d
gloas: drop the unused full/signed envelope Encode/Decode wrappers
iurii-ssv Jun 26, 2026
9c7fbde
gloas: test the §6 envelope runner's post-consensus publish path
iurii-ssv Jun 26, 2026
e6d6a34
gloas: verify the §6 ExecutionPayload layout against the canonical spec
iurii-ssv Jun 26, 2026
cce4765
gloas: every operator submits the §4 block (restore liveness redundancy)
iurii-ssv Jun 27, 2026
9759a06
gloas: fix two stale §6 doc comments
iurii-ssv Jun 27, 2026
5a5368d
ekm: add the §6 DomainBeaconBuilder signing arm
iurii-ssv Jun 27, 2026
6c660a3
gloas: keep the §2 aggregation index on Gloas slots
iurii-ssv Jun 27, 2026
0b1887f
gloas: refresh PTC duties on reorg / indices change
iurii-ssv Jun 27, 2026
d5f68a3
gloas: fix two stale §5/§6 doc comments
iurii-ssv Jun 27, 2026
d4218be
gloas: harden PTC message validation (lateness + duty-count cap)
iurii-ssv Jun 27, 2026
19bb6ef
gloas: add proposal build-source telemetry + envelope log slot
iurii-ssv Jun 27, 2026
eb5edd6
gloas: gate PTC messages on a real duty assignment
iurii-ssv Jun 27, 2026
95aaead
gloas: retime QBFT round timer to IntervalDuration; add ProposerDelay…
iurii-ssv Jun 27, 2026
29b013e
gloas: scale attestation-data fetch budgets to the Gloas slot window
iurii-ssv Jun 28, 2026
1d8aeb2
gloas: fix stale ePBS submit comments, add submit-error test, tidy EKM
iurii-ssv Jun 28, 2026
817ce5e
gloas: commit ePBS implementation plan as temporary in-branch scratch
iurii-ssv Jun 28, 2026
e51da27
gloas: tighten ePBS code comments across the PR
iurii-ssv Jun 28, 2026
6052da5
gloas: plan §8 fork-transition monitoring + logs-first observability
iurii-ssv Jun 28, 2026
238e620
gloas: add greppable logs for fork activation, build-source, duty out…
iurii-ssv Jun 28, 2026
5b4a7e3
gloas: mark §8 log-coverage audit G1-G4 done (logs shipped)
iurii-ssv Jun 28, 2026
20d9adf
gloas: log §2 vote index + §5 proposer-preferences inputs (greppable)
iurii-ssv Jun 28, 2026
9533fc7
gloas: fix golangci-lint findings on the ePBS PR
iurii-ssv Jun 28, 2026
c607ab2
gloas: address ePBS PR review — fan-out publish, envelope duty check,…
iurii-ssv Jun 29, 2026
ba4266e
gloas: review cleanups — detach attestation singleflight ctx
iurii-ssv Jun 29, 2026
5c95232
gloas: address ePBS PR review — doc-comment clarifications + plan notes
iurii-ssv Jun 30, 2026
a85a916
gloas: retarget ePBS devnet plan to glamsterdam-devnet-6; document en…
iurii-ssv Jun 30, 2026
5730625
gloas: wire §4/§5/§6 to the merged beacon-APIs (#580)
iurii-ssv Jun 30, 2026
e6ad547
gloas: normalize hand-rolled beacon-node address (fixes scheme-less c…
iurii-ssv Jun 30, 2026
8e0ce20
gloas: plan — local_testnet ePBS testing merged to aetheria main (§7 …
iurii-ssv Jun 30, 2026
20894f8
gloas: retry duty fetch when no validators are eligible yet
iurii-ssv Jul 1, 2026
f958c2d
fix linter
iurii-ssv Jul 1, 2026
b8f7b16
gloas: bring ePBS submit paths onto the mature runners' idiom
iurii-ssv Jul 1, 2026
b99cc56
gloas: tighten ePBS doc-comments
iurii-ssv Jul 1, 2026
70862f1
gloas: §5 publish-finality KNOWN ISSUE + consolidated plan TODO index
iurii-ssv Jul 1, 2026
65cc483
gloas: add proposer slot-dispatch + late-fetch diagnostics (ssvlabs/s…
iurii-ssv Jul 1, 2026
9d417b9
gloas: plan — track remote-signing (Web3Signer) breakage RS-1/2/3 acc…
iurii-ssv Jul 1, 2026
c15d848
gloas: fix remote-signer fork_info on Gloas slots (RS-1)
iurii-ssv Jul 1, 2026
6d4a891
gloas: plan — RS-1 done; correct RS-2 (module boundary, not "add a ca…
iurii-ssv Jul 1, 2026
3689e94
fix(gloas): pad graffiti to 32 bytes in the §4 block request
iurii-ssv Jul 1, 2026
edf4999
fix(gloas): sign the §4 Gloas block on the local key manager
iurii-ssv Jul 1, 2026
0a6e4af
gloas: plan — devnet run 2026-07-01 (§4 produces on devnet-5); correc…
iurii-ssv Jul 1, 2026
b27d1bc
gloas: add block slashing protection to the local §4 Gloas block signer
iurii-ssv Jul 1, 2026
a51a485
gloas: fetch §2 attestation data via a hand-rolled GET on Gloas slots
iurii-ssv Jul 1, 2026
426dd0d
fix(gloas): type §4/§6 execution requests as the EIP-8282 five-list v…
iurii-ssv Jul 1, 2026
6888226
add tla/ to gitignore
iurii-ssv Jul 2, 2026
1b24268
gloas: remove the ePBS implementation plan (fully migrated)
iurii-ssv Jul 2, 2026
f0e1967
fix(gloas): key §4 block slashing protection to the signed block's slot
iurii-ssv Jul 2, 2026
de9f5bc
gloas: export GloasDataVersion and guard its mirror of DataVersionGloas
iurii-ssv Jul 2, 2026
c2a5fd5
gloas: convert proposer-duty TEMP diagnostics to permanent Debug logs
iurii-ssv Jul 2, 2026
1a8809a
gloas: decode proposer dependent_root directly into phase0.Root
iurii-ssv Jul 2, 2026
507fde5
gloas: extract requestProposerDutiesDependentRoot + cover it with tests
iurii-ssv Jul 2, 2026
57b9a37
gloas: treat "already known" §4 block submit as success (#2922)
iurii-ssv Jul 2, 2026
1efe42c
gloas: publish the full §6 envelope (unblinded) to match Lodestar (#2…
iurii-ssv Jul 2, 2026
2f10a8d
gloas: fix slot-fraction wording left stale by the interval retiming
iurii-ssv Jul 3, 2026
40d2be6
gloas: fix "signalling" misspellings flagged by the misspell linter
iurii-ssv Jul 3, 2026
188c634
gloas: drop embedded-field selector in gloasBlockStub (staticcheck QF…
iurii-ssv Jul 3, 2026
fb1cbd9
gloas: treat "already known" §6 envelope publish as success (#2923)
iurii-ssv Jul 3, 2026
b42af09
gloas: enable §5 proposer-preferences re-emission on dependent_root c…
iurii-ssv Jul 8, 2026
c56c013
gloas: §5 proposer-preferences dedup — IGNORE distinct root past cap
iurii-ssv Jul 8, 2026
3ea0973
gloas: align PTC duty-limit and proposer value-check with the SIP
iurii-ssv Jul 8, 2026
40d1283
gloas: flag §5 submit 404 as a missing beacon-API route
iurii-ssv Jul 15, 2026
3cc4171
gloas: fix §5 pre-consensus starvation across skewed emission ticks
iurii-ssv Jul 15, 2026
8b67488
gloas: tolerate §5/§6 duty-view staleness across validator-set changes
iurii-ssv Jul 15, 2026
0ec079d
gloas: mark proposer duty views stale on indices-change receipt, not …
iurii-ssv Jul 16, 2026
47afe38
gloas: §5 skip re-broadcast of an unchanged in-flight preference on r…
iurii-ssv Jul 16, 2026
a60cf5d
gloas: log own-validator message drops at the router fall-through
iurii-ssv Jul 17, 2026
f90b520
gloas: settle the V5/V9/V11 node-side verification items
iurii-ssv Jul 26, 2026
e94ae64
ibft/storage: resolve local-directory ssv-spec replaces in GetSpecDir
iurii-ssv Jul 28, 2026
378dd59
gloas: re-pin ssv-spec for the request-auth constants; absorb the Env…
iurii-ssv Jul 28, 2026
51ffe9c
gloas: request-auth wire types and the BuilderEntry config vocabulary…
iurii-ssv Jul 28, 2026
e1ef01b
gloas: DomainRequestAuth signing arms (#2962)
iurii-ssv Jul 28, 2026
6419fee
gloas: #2962 phase 1 — threshold request-auth rounds on the §5 dispat…
iurii-ssv Jul 28, 2026
724eafc
gloas: #2962 review follow-ups — clock-anchored auth cache, shared-to…
iurii-ssv Jul 29, 2026
2699659
gloas: #2962 follow-ups — hard-fail auth partials without local build…
iurii-ssv Jul 29, 2026
f43435f
gloas: #2962 review round 3 — replay stash into concluded re-emission…
iurii-ssv Jul 29, 2026
e2d9098
gloas: retire the GlamsterdamDevnet networkconfig stub
iurii-ssv Jul 29, 2026
d007683
gloas: group the Gloas beacon-node interfaces with the other *Calls b…
iurii-ssv Aug 5, 2026
c343942
gloas: reconcile the request-auth overlay to the merged Gloas builder…
iurii-ssv Aug 25, 2026
a660a04
gloas: adopt the renamed spectypes.DomainBuilderRequestAuth
iurii-ssv Aug 25, 2026
9af2a1a
gloas: adopt the produceBlockV4 POST for the direct-builder overlay (…
iurii-ssv Aug 25, 2026
31e00e0
gloas: phase-2 self-review — dedupe the produce SSZ decode; comment p…
iurii-ssv Aug 25, 2026
4073af8
gloas: submit ahead-of-time builder preferences (phase 3)
iurii-ssv Aug 25, 2026
99824ae
gloas: phase-3 self-review — simplify the proposer-pubkey conversion
iurii-ssv Aug 25, 2026
53fb2fa
gloas: deferred goclient refinements (produce version guard, HTTP-hel…
iurii-ssv Aug 26, 2026
94ca5e1
gloas: code-review follow-ups — POST on a knobs-only config; comment/…
iurii-ssv Aug 26, 2026
9279bbd
gloas: carry builder_boost_factor on the produce GET fallback
iurii-ssv Aug 26, 2026
ea641ee
message/validation: admit the Gloas runner roles in validRoleUnion (#…
iurii-ssv Aug 26, 2026
b878ae2
message: add the Gloas runner roles to the exporter role-string mappers
iurii-ssv Aug 26, 2026
ae64199
exporter/dutytracer: explicitly skip the Gloas duty types
iurii-ssv Aug 26, 2026
4323db0
gloas: point the remote-signer TODO(gloas) markers at the tracking issue
iurii-ssv Aug 26, 2026
cf4861f
gloas: address direct-builder review follow-ups
iurii-ssv Aug 26, 2026
28dcc52
gloas: resolve the builder config once, not per read (parse-once)
iurii-ssv Aug 26, 2026
db5c360
gloas: polish the parse-once follow-ups (docs, receiver, lint)
iurii-ssv Aug 26, 2026
6dbe17c
gloas: fix PTC payload_attestation_data URL and 204 abstain handling
iurii-ssv Aug 27, 2026
8f1e4e0
gloas: extract the Eth-Consensus-Version header name into a constant
iurii-ssv Aug 27, 2026
f070738
gloas: POST-first produceBlockV4 with a neutral local-build config
iurii-ssv Aug 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ bin
data
docs
e2e
tla
.cache
.github
coverage.out
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ bin/
/scripts/spec_align_report/ssv-spec
.vscode/
patches/
tla/

go.work
go.work.sum
Expand Down
154 changes: 109 additions & 45 deletions beacon/goclient/aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"github.com/attestantio/go-eth2-client/spec/electra"
"github.com/attestantio/go-eth2-client/spec/phase0"
ssz "github.com/ferranbt/fastssz"
"go.uber.org/zap"

"github.com/ssvlabs/ssv/networkconfig"
"github.com/ssvlabs/ssv/observability/log/fields"
)

// IsAggregator returns true if the validator is selected as an aggregator for the given
Expand Down Expand Up @@ -49,11 +51,11 @@ func (gc *GoClient) SubmitAggregateSelectionProof(
index phase0.ValidatorIndex,
slotSig []byte,
) (ssz.Marshaler, spec.DataVersion, error) {
// As specified in spec, an aggregator should wait until two thirds of the way through slot
// to broadcast the best aggregate to the global aggregate channel.
// As specified in spec, an aggregator waits until the aggregation deadline (see
// waitIntoSlot) to broadcast the best aggregate to the global aggregate channel.
// https://github.com/ethereum/consensus-specs/blob/v0.9.3/specs/validator/0_beacon-chain-validator.md#broadcast-aggregate
if err := gc.waitTwoThirdsIntoSlot(ctx, slot); err != nil {
return nil, 0, fmt.Errorf("wait for 2/3 of slot: %w", err)
if err := gc.waitIntoSlot(ctx, slot, 2); err != nil {
return nil, 0, fmt.Errorf("wait for aggregation deadline: %w", err)
}

va, _, err := gc.fetchVersionedAggregate(ctx, slot, committeeIndex)
Expand Down Expand Up @@ -82,32 +84,83 @@ func (gc *GoClient) SubmitSignedAggregateSelectionProof(
return nil
}

// computeAttestationDataRoot re-derives the attestation data root for the given slot/committee
// from this node's own view, used as a fallback when the cluster-attested root is unknown.
// waitIntoSlot waits until the given number of intervals into the slot has transpired
// (intervals * IntervalDuration after the start of the slot): intervals=1 is the attestation and
// sync-message deadline, intervals=2 the aggregation and contribution deadline. IntervalDuration
// is 1/3 of the slot before Gloas, 1/4 from Gloas on (SIP #94 §1).
func (gc *GoClient) waitIntoSlot(ctx context.Context, slot phase0.Slot, intervals int) error {
config := gc.getBeaconConfig()
finalTime := config.SlotStartTime(slot).Add(time.Duration(intervals) * config.IntervalDuration(slot))
wait := time.Until(finalTime)
if wait <= 0 {
return nil
}

select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(wait):
return nil
}
}

// computeAttestationDataRoot re-derives the attestation-data root for (slot, committeeIndex) from this
// node's own view. On Gloas it also returns the root under the opposite payload-status index — the one
// field of the re-derived data that can disagree with what the cluster decided, which the caller retries
// under (see fetchVersionedAggregate). Both roots come from the same fetch, so they are guaranteed to
// differ in nothing else; a nil altRoot means the slot is pre-Gloas and no such alternative exists.
func (gc *GoClient) computeAttestationDataRoot(
ctx context.Context,
slot phase0.Slot,
committeeIndex phase0.CommitteeIndex,
) (root [32]byte, err error) {
attData, _, err := gc.GetAttestationData(ctx, slot)
) (root [32]byte, altRoot *[32]byte, err error) {
cached, _, err := gc.GetAttestationData(ctx, slot)
if err != nil {
return root, fmt.Errorf("fetch attestation data: %w", err)
return root, nil, fmt.Errorf("fetch attestation data: %w", err)
}

// GetAttestationData hands back the pointer it caches for the slot, shared with every other caller
// (notably the committee runner). Work on a copy so the Index rewrites below can't write through.
attData := *cached

// Explicitly set Index field as beacon nodes may return inconsistent values.
// EIP-7549: For Electra and later, index must always be 0, pre-Electra uses committee index.
config := gc.getBeaconConfig()
dataVersion, _ := config.ForkAtEpoch(config.EstimatedEpochAtSlot(slot))
attData.Index = 0
if dataVersion < spec.DataVersionElectra {
attData.Index = committeeIndex
// EIP-7549: Electra+ uses Index=0; pre-Electra uses committee index. Gloas (EIP-7732) instead keeps
// the BN-supplied payload-status index (0=EMPTY/1=FULL) — it is part of the signed AttestationData
// (SIP #94 §2 aggregation path), so the aggregate must be fetched under it.
// Decide the fork from the requested duty slot, not attData.Slot — the latter is what the
// beacon node returned (the same source the comment above warns "may return inconsistent values"),
// whereas the aggregate is for our duty's slot, which is authoritative.
cfg := gc.getBeaconConfig()
isGloas := cfg.IsGloasAtSlot(slot)
// On Gloas the BN-supplied Index is the payload-status value and is kept exactly as returned.
if !isGloas {
version, _ := cfg.ForkAtEpoch(cfg.EstimatedEpochAtSlot(slot))
attData.Index = 0
if version < spec.DataVersionElectra {
attData.Index = committeeIndex
}
}

root, err = attData.HashTreeRoot()
if err != nil {
return root, fmt.Errorf("fetch attestation data root: %w", err)
return root, nil, fmt.Errorf("fetch attestation data root: %w", err)
}
if !isGloas {
return root, nil, nil
}

// The §2 payload-status index is a single bit (0=EMPTY / 1=FULL), so flipping it enumerates the
// only value the cluster could have decided other than the one our own beacon node reported.
if attData.Index == 0 {
attData.Index = 1
} else {
attData.Index = 0
}
return root, nil
flipped, err := attData.HashTreeRoot()
if err != nil {
return root, nil, fmt.Errorf("hash flipped attestation data root: %w", err)
}
return root, &flipped, nil
}

// fetchVersionedAggregate fetches the aggregate attestation for the given slot/committee,
Expand All @@ -118,37 +171,66 @@ func (gc *GoClient) computeAttestationDataRoot(
// cluster-decided value): the beacon node then holds at least our own attestation matching it.
// Re-deriving the data locally can yield a root nobody attested with, which the node answers
// with a 404 (no matching aggregate).
//
// A cache hit only guarantees that *some* beacon node accepted the attestation. The
// AggregateAttestation request below is routed to the single highest-scored client and does
// not fail over on 4xx, so it can still 404 if that node hasn't ingested the attestation yet
// (gossip backfill before the aggregation deadline makes this rare).
func (gc *GoClient) fetchVersionedAggregate(
ctx context.Context,
slot phase0.Slot,
committeeIndex phase0.CommitteeIndex,
) (*spec.VersionedAttestation, spec.DataVersion, error) {
root, found := gc.attestedDataRoot(slot, committeeIndex)
var altRoot *[32]byte
if !found {
// No record of our own attestation (it failed or hasn't landed yet) — fall back
// to re-deriving the root from this node's view of the slot.
var err error
root, err = gc.computeAttestationDataRoot(ctx, slot, committeeIndex)
root, altRoot, err = gc.computeAttestationDataRoot(ctx, slot, committeeIndex)
if err != nil {
return nil, DataVersionNil, err
}
}

aggDataReqStart := time.Now()
aggDataResp, err := gc.multiClient.AggregateAttestation(ctx, &api.AggregateAttestationOpts{
Slot: slot,
AttestationDataRoot: root,
CommitteeIndex: committeeIndex,
})
recordRequest(ctx, gc.log, "AggregateAttestation", gc.multiClient, http.MethodGet, true, time.Since(aggDataReqStart), err)
resp, err := gc.fetchAggregate(ctx, slot, committeeIndex, root)
if err != nil && altRoot != nil && isNotFound(err) {
// Only the re-derived root can disagree with the cluster: it carries *our* beacon node's
// Gloas payload-status index (SIP #94 §2), while the committee signed the QBFT-decided one.
// A 404 means no aggregate exists under our index, so try the only other value the bit can
// hold rather than silently missing the aggregate. Cheap: one extra GET on a path that has
// already failed, and unreachable from the common cache-hit path, whose root is by
// construction the decided one.
gc.log.Debug("retrying gloas aggregate fetch under the opposite payload-status index",
fields.Slot(slot),
zap.Uint64("committee_index", uint64(committeeIndex)))
resp, err = gc.fetchAggregate(ctx, slot, committeeIndex, *altRoot)
}
if err != nil {
return nil, DataVersionNil, errMultiClient(fmt.Errorf("fetch aggregate attestation: %w", err), "AggregateAttestation")
}
if err := checkPtrResponse(aggDataResp, "aggregate attestation"); err != nil {
if err := checkPtrResponse(resp, "aggregate attestation"); err != nil {
return nil, DataVersionNil, errMultiClient(err, "AggregateAttestation")
}

return aggDataResp.Data, aggDataResp.Data.Version, nil
return resp.Data, resp.Data.Version, nil
}

// fetchAggregate performs the aggregate GET for one candidate attestation-data root.
func (gc *GoClient) fetchAggregate(
ctx context.Context,
slot phase0.Slot,
committeeIndex phase0.CommitteeIndex,
root phase0.Root,
) (*api.Response[*spec.VersionedAttestation], error) {
start := time.Now()
resp, err := gc.multiClient.AggregateAttestation(ctx, &api.AggregateAttestationOpts{
Slot: slot,
AttestationDataRoot: root,
CommitteeIndex: committeeIndex,
})
recordRequest(ctx, gc.log, "AggregateAttestation", gc.multiClient, http.MethodGet, true, time.Since(start), err)
return resp, err
}

func versionedAggregateToSSZ(va *spec.VersionedAttestation) (ssz.Marshaler, spec.DataVersion, error) {
Expand Down Expand Up @@ -266,21 +348,3 @@ func versionedToAggregateAndProof(
return nil, DataVersionNil, fmt.Errorf("unknown data version: %d", va.Version)
}
}

// waitTwoThirdsIntoSlot waits until two-third of the slot has transpired (SECONDS_PER_SLOT * 2 / 3 seconds after the start of slot)
func (gc *GoClient) waitTwoThirdsIntoSlot(ctx context.Context, slot phase0.Slot) error {
config := gc.getBeaconConfig()
oneInterval := config.IntervalDuration()
finalTime := config.SlotStartTime(slot).Add(2 * oneInterval)
wait := time.Until(finalTime)
if wait <= 0 {
return nil
}

select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(wait):
return nil
}
}
Loading
Loading