-
-
Notifications
You must be signed in to change notification settings - Fork 323
/
Copy pathprocessAttestationPhase0.ts
157 lines (138 loc) Β· 6.43 KB
/
processAttestationPhase0.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import {ForkSeq, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
import {Attestation, Slot, electra, phase0, ssz} from "@lodestar/types";
import {toRootHex} from "@lodestar/utils";
import {assert} from "@lodestar/utils";
import {CachedBeaconStateAllForks, CachedBeaconStatePhase0} from "../types.js";
import {computeEpochAtSlot} from "../util/index.js";
import {isValidIndexedAttestation} from "./index.js";
/**
* Process an Attestation operation. Validates an attestation and appends it to state.currentEpochAttestations or
* state.previousEpochAttestations to be processed in bulk at the epoch transition.
*
* PERF: Work depends on number of Attestation per block. On mainnet the average is 89.7 / block, with 87.8 participant
* true bits on average. See `packages/state-transition/test/perf/analyzeBlocks.ts`
*/
export function processAttestationPhase0(
state: CachedBeaconStatePhase0,
attestation: phase0.Attestation,
verifySignature = true
): void {
const {epochCtx} = state;
const slot = state.slot;
const data = attestation.data;
validateAttestation(ForkSeq.phase0, state, attestation);
const pendingAttestation = ssz.phase0.PendingAttestation.toViewDU({
data: data,
aggregationBits: attestation.aggregationBits,
inclusionDelay: slot - data.slot,
proposerIndex: epochCtx.getBeaconProposer(slot),
});
if (data.target.epoch === epochCtx.epoch) {
if (!ssz.phase0.Checkpoint.equals(data.source, state.currentJustifiedCheckpoint)) {
throw new Error(
`Attestation source does not equal current justified checkpoint: source=${checkpointToStr(
data.source
)} currentJustifiedCheckpoint=${checkpointToStr(state.currentJustifiedCheckpoint)}`
);
}
state.currentEpochAttestations.push(pendingAttestation);
} else {
if (!ssz.phase0.Checkpoint.equals(data.source, state.previousJustifiedCheckpoint)) {
throw new Error(
`Attestation source does not equal previous justified checkpoint: source=${checkpointToStr(
data.source
)} previousJustifiedCheckpoint=${checkpointToStr(state.previousJustifiedCheckpoint)}`
);
}
state.previousEpochAttestations.push(pendingAttestation);
}
if (!isValidIndexedAttestation(state, epochCtx.getIndexedAttestation(ForkSeq.phase0, attestation), verifySignature)) {
throw new Error("Attestation is not valid");
}
}
export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllForks, attestation: Attestation): void {
const {epochCtx} = state;
const slot = state.slot;
const data = attestation.data;
const computedEpoch = computeEpochAtSlot(data.slot);
const committeeCount = epochCtx.getCommitteeCountPerSlot(computedEpoch);
if (!(data.target.epoch === epochCtx.previousShuffling.epoch || data.target.epoch === epochCtx.epoch)) {
throw new Error(
"Attestation target epoch not in previous or current epoch: " +
`targetEpoch=${data.target.epoch} currentEpoch=${epochCtx.epoch}`
);
}
if (!(data.target.epoch === computedEpoch)) {
throw new Error(
"Attestation target epoch does not match epoch computed from slot: " +
`targetEpoch=${data.target.epoch} computedEpoch=${computedEpoch}`
);
}
// post deneb, the attestations are valid till end of next epoch
if (!(data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= slot && isTimelyTarget(fork, slot - data.slot))) {
throw new Error(
"Attestation slot not within inclusion window: " +
`slot=${data.slot} window=${data.slot + MIN_ATTESTATION_INCLUSION_DELAY}..${data.slot + SLOTS_PER_EPOCH}`
);
}
if (fork >= ForkSeq.electra) {
assert.equal(data.index, 0, `AttestationData.index must be zero: index=${data.index}`);
const attestationElectra = attestation as electra.Attestation;
const committeeIndices = attestationElectra.committeeBits.getTrueBitIndexes();
if (committeeIndices.length === 0) {
throw Error("Attestation should have at least one committee bit set");
}
const lastCommitteeIndex = committeeIndices[committeeIndices.length - 1];
if (lastCommitteeIndex >= committeeCount) {
throw new Error(
`Attestation committee index exceeds committee count: lastCommitteeIndex=${lastCommitteeIndex} numCommittees=${committeeCount}`
);
}
const validatorsByCommittee = epochCtx.getBeaconCommittees(data.slot, committeeIndices);
const aggregationBitsArray = attestationElectra.aggregationBits.toBoolArray();
// Total number of attestation participants of every committee specified
let committeeOffset = 0;
for (const committeeValidators of validatorsByCommittee) {
const committeeAggregationBits = aggregationBitsArray.slice(
committeeOffset,
committeeOffset + committeeValidators.length
);
// Assert aggregation bits in this committee have at least one true bit
if (committeeAggregationBits.every((bit) => !bit)) {
throw new Error("Every committee in aggregation bits must have at least one attester");
}
committeeOffset += committeeValidators.length;
}
// Bitfield length matches total number of participants
assert.equal(
attestationElectra.aggregationBits.bitLen,
committeeOffset,
`Attestation aggregation bits length does not match total number of committee participants aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${committeeOffset}`
);
} else {
if (!(data.index < committeeCount)) {
throw new Error(
"Attestation committee index not within current committee count: " +
`committeeIndex=${data.index} committeeCount=${committeeCount}`
);
}
const committee = epochCtx.getBeaconCommittee(data.slot, data.index);
if (attestation.aggregationBits.bitLen !== committee.length) {
throw new Error(
"Attestation aggregation bits length does not match committee length: " +
`aggregationBitsLength=${attestation.aggregationBits.bitLen} committeeLength=${committee.length}`
);
}
}
}
// Modified https://github.com/ethereum/consensus-specs/pull/3360
export function isTimelyTarget(fork: ForkSeq, inclusionDistance: Slot): boolean {
// post deneb attestation is valid till end of next epoch for target
if (fork >= ForkSeq.deneb) {
return true;
}
return inclusionDistance <= SLOTS_PER_EPOCH;
}
export function checkpointToStr(checkpoint: phase0.Checkpoint): string {
return `${toRootHex(checkpoint.root)}:${checkpoint.epoch}`;
}