@lodestar/beacon-node 1.40.0-dev.63c5c3e7f7 → 1.40.0-dev.9defa5c09b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chain/chain.d.ts +7 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +20 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/attestationError.d.ts +14 -1
- package/lib/chain/errors/attestationError.d.ts.map +1 -1
- package/lib/chain/errors/attestationError.js +8 -0
- package/lib/chain/errors/attestationError.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +48 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -0
- package/lib/chain/errors/executionPayloadBid.js +15 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +48 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -0
- package/lib/chain/errors/executionPayloadEnvelope.js +16 -0
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -0
- package/lib/chain/errors/index.d.ts +3 -0
- package/lib/chain/errors/index.d.ts.map +1 -1
- package/lib/chain/errors/index.js +3 -0
- package/lib/chain/errors/index.js.map +1 -1
- package/lib/chain/errors/payloadAttestation.d.ts +34 -0
- package/lib/chain/errors/payloadAttestation.d.ts.map +1 -0
- package/lib/chain/errors/payloadAttestation.js +13 -0
- package/lib/chain/errors/payloadAttestation.js.map +1 -0
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +18 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/interface.d.ts +7 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/executionPayloadBidPool.d.ts +21 -0
- package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -0
- package/lib/chain/opPools/executionPayloadBidPool.js +57 -0
- package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -0
- package/lib/chain/opPools/index.d.ts +2 -0
- package/lib/chain/opPools/index.d.ts.map +1 -1
- package/lib/chain/opPools/index.js +2 -0
- package/lib/chain/opPools/index.js.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.d.ts +24 -0
- package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -0
- package/lib/chain/opPools/payloadAttestationPool.js +109 -0
- package/lib/chain/opPools/payloadAttestationPool.js.map +1 -0
- package/lib/chain/regen/interface.d.ts +1 -0
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +1 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/seenCache/index.d.ts +3 -1
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +3 -1
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/seenCache/seenAttesters.d.ts +5 -0
- package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
- package/lib/chain/seenCache/seenAttesters.js +5 -0
- package/lib/chain/seenCache/seenAttesters.js.map +1 -1
- package/lib/chain/seenCache/seenExecutionPayloadBids.d.ts +12 -0
- package/lib/chain/seenCache/seenExecutionPayloadBids.d.ts.map +1 -0
- package/lib/chain/seenCache/seenExecutionPayloadBids.js +30 -0
- package/lib/chain/seenCache/seenExecutionPayloadBids.js.map +1 -0
- package/lib/chain/seenCache/seenExecutionPayloadEnvelope.d.ts +15 -0
- package/lib/chain/seenCache/seenExecutionPayloadEnvelope.d.ts.map +1 -0
- package/lib/chain/seenCache/seenExecutionPayloadEnvelope.js +28 -0
- package/lib/chain/seenCache/seenExecutionPayloadEnvelope.js.map +1 -0
- package/lib/chain/validation/aggregateAndProof.js +32 -10
- package/lib/chain/validation/aggregateAndProof.js.map +1 -1
- package/lib/chain/validation/attestation.d.ts.map +1 -1
- package/lib/chain/validation/attestation.js +23 -4
- package/lib/chain/validation/attestation.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +4 -3
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts +5 -0
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -0
- package/lib/chain/validation/executionPayloadBid.js +103 -0
- package/lib/chain/validation/executionPayloadBid.js.map +1 -0
- package/lib/chain/validation/executionPayloadEnvelope.d.ts +5 -0
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -0
- package/lib/chain/validation/executionPayloadEnvelope.js +89 -0
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -0
- package/lib/chain/validation/payloadAttestationMessage.d.ts +9 -0
- package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -0
- package/lib/chain/validation/payloadAttestationMessage.js +72 -0
- package/lib/chain/validation/payloadAttestationMessage.js.map +1 -0
- package/lib/db/repositories/checkpointState.js +0 -1
- package/lib/db/repositories/checkpointState.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +20 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +40 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/interface.d.ts +20 -2
- package/lib/network/gossip/interface.d.ts.map +1 -1
- package/lib/network/gossip/interface.js +3 -0
- package/lib/network/gossip/interface.js.map +1 -1
- package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
- package/lib/network/gossip/scoringParameters.js +38 -2
- package/lib/network/gossip/scoringParameters.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +77 -1
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +20 -0
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +34 -0
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
- package/lib/network/processor/gossipQueues/index.js +16 -0
- package/lib/network/processor/gossipQueues/index.js.map +1 -1
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +3 -0
- package/lib/network/processor/index.js.map +1 -1
- package/package.json +15 -15
- package/src/chain/chain.ts +23 -1
- package/src/chain/errors/attestationError.ts +11 -1
- package/src/chain/errors/executionPayloadBid.ts +35 -0
- package/src/chain/errors/executionPayloadEnvelope.ts +34 -0
- package/src/chain/errors/index.ts +3 -0
- package/src/chain/errors/payloadAttestation.ts +25 -0
- package/src/chain/forkChoice/index.ts +19 -0
- package/src/chain/interface.ts +16 -1
- package/src/chain/opPools/executionPayloadBidPool.ts +77 -0
- package/src/chain/opPools/index.ts +2 -0
- package/src/chain/opPools/payloadAttestationPool.ts +157 -0
- package/src/chain/regen/interface.ts +1 -0
- package/src/chain/seenCache/index.ts +3 -1
- package/src/chain/seenCache/seenAttesters.ts +5 -0
- package/src/chain/seenCache/seenExecutionPayloadBids.ts +35 -0
- package/src/chain/seenCache/seenExecutionPayloadEnvelope.ts +34 -0
- package/src/chain/validation/aggregateAndProof.ts +33 -10
- package/src/chain/validation/attestation.ts +24 -3
- package/src/chain/validation/block.ts +4 -3
- package/src/chain/validation/executionPayloadBid.ts +140 -0
- package/src/chain/validation/executionPayloadEnvelope.ts +122 -0
- package/src/chain/validation/payloadAttestationMessage.ts +109 -0
- package/src/db/repositories/checkpointState.ts +1 -1
- package/src/metrics/metrics/lodestar.ts +40 -0
- package/src/network/gossip/interface.ts +17 -0
- package/src/network/gossip/scoringParameters.ts +44 -2
- package/src/network/gossip/topic.ts +21 -0
- package/src/network/processor/gossipHandlers.ts +48 -0
- package/src/network/processor/gossipQueues/index.ts +16 -0
- package/src/network/processor/index.ts +3 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {Signature, aggregateSignatures} from "@chainsafe/blst";
|
|
2
|
+
import {BitArray} from "@chainsafe/ssz";
|
|
3
|
+
import {ChainForkConfig} from "@lodestar/config";
|
|
4
|
+
import {MAX_COMMITTEES_PER_SLOT, PTC_SIZE} from "@lodestar/params";
|
|
5
|
+
import {RootHex, Slot, gloas} from "@lodestar/types";
|
|
6
|
+
import {MapDef, toRootHex} from "@lodestar/utils";
|
|
7
|
+
import {Metrics} from "../../metrics/metrics.js";
|
|
8
|
+
import {IClock} from "../../util/clock.js";
|
|
9
|
+
import {InsertOutcome, OpPoolError, OpPoolErrorCode} from "./types.js";
|
|
10
|
+
import {pruneBySlot, signatureFromBytesNoCheck} from "./utils.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* TODO GLOAS: Revisit this value and add rational for choosing it
|
|
14
|
+
*/
|
|
15
|
+
const SLOTS_RETAINED = 2;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The maximum number of distinct `PayloadAttestationData` that will be stored in each slot.
|
|
19
|
+
*
|
|
20
|
+
* This is a DoS protection measure.
|
|
21
|
+
*/
|
|
22
|
+
// TODO GLOAS: Revisit this value. Educated guess would be MAX_ATTESTATIONS_PER_SLOT in AttestationPool divided by MAX_COMMITTEES_PER_SLOT
|
|
23
|
+
const MAX_PAYLOAD_ATTESTATIONS_PER_SLOT = 16_384 / MAX_COMMITTEES_PER_SLOT;
|
|
24
|
+
|
|
25
|
+
type DataRootHex = string;
|
|
26
|
+
type BlockRootHex = string;
|
|
27
|
+
|
|
28
|
+
type AggregateFast = {
|
|
29
|
+
aggregationBits: BitArray;
|
|
30
|
+
data: gloas.PayloadAttestationData;
|
|
31
|
+
signature: Signature;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class PayloadAttestationPool {
|
|
35
|
+
private readonly aggregateByDataRootByBlockRootBySlot = new MapDef<
|
|
36
|
+
Slot,
|
|
37
|
+
Map<BlockRootHex, Map<DataRootHex, AggregateFast>>
|
|
38
|
+
>(() => new Map<BlockRootHex, Map<DataRootHex, AggregateFast>>());
|
|
39
|
+
private lowestPermissibleSlot = 0;
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
private readonly config: ChainForkConfig,
|
|
43
|
+
private readonly clock: IClock,
|
|
44
|
+
private readonly metrics: Metrics | null = null
|
|
45
|
+
) {}
|
|
46
|
+
|
|
47
|
+
get size(): number {
|
|
48
|
+
let count = 0;
|
|
49
|
+
for (const aggregateByDataRootByBlockRoot of this.aggregateByDataRootByBlockRootBySlot.values()) {
|
|
50
|
+
for (const aggregateByDataRoot of aggregateByDataRootByBlockRoot.values()) {
|
|
51
|
+
count += aggregateByDataRoot.size;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return count;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
add(
|
|
58
|
+
message: gloas.PayloadAttestationMessage,
|
|
59
|
+
payloadAttDataRootHex: RootHex,
|
|
60
|
+
validatorCommitteeIndex: number
|
|
61
|
+
): InsertOutcome {
|
|
62
|
+
const slot = message.data.slot;
|
|
63
|
+
const lowestPermissibleSlot = this.lowestPermissibleSlot;
|
|
64
|
+
|
|
65
|
+
if (slot < lowestPermissibleSlot) {
|
|
66
|
+
return InsertOutcome.Old;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (slot < this.clock.slotWithPastTolerance(this.config.MAXIMUM_GOSSIP_CLOCK_DISPARITY / 1000)) {
|
|
70
|
+
return InsertOutcome.Late;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.getOrDefault(slot);
|
|
74
|
+
let aggregateByDataRoot = aggregateByDataRootByBlockRoot.get(toRootHex(message.data.beaconBlockRoot));
|
|
75
|
+
|
|
76
|
+
if (!aggregateByDataRoot) {
|
|
77
|
+
aggregateByDataRoot = new Map<DataRootHex, AggregateFast>();
|
|
78
|
+
aggregateByDataRootByBlockRoot.set(toRootHex(message.data.beaconBlockRoot), aggregateByDataRoot);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (aggregateByDataRoot.size >= MAX_PAYLOAD_ATTESTATIONS_PER_SLOT) {
|
|
82
|
+
throw new OpPoolError({code: OpPoolErrorCode.REACHED_MAX_PER_SLOT});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const aggregate = aggregateByDataRoot.get(payloadAttDataRootHex);
|
|
86
|
+
if (aggregate) {
|
|
87
|
+
// Aggregate msg into aggregate
|
|
88
|
+
return aggregateMessageInto(message, validatorCommitteeIndex, aggregate);
|
|
89
|
+
}
|
|
90
|
+
// Create a new aggregate with data
|
|
91
|
+
aggregateByDataRoot.set(payloadAttDataRootHex, messageToAggregate(message, validatorCommitteeIndex));
|
|
92
|
+
|
|
93
|
+
return InsertOutcome.NewData;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get payload attestations to be included in a block.
|
|
98
|
+
* Pick the top `maxAttestation` number of attestations with the most votes
|
|
99
|
+
*/
|
|
100
|
+
getPayloadAttestationsForBlock(
|
|
101
|
+
beaconBlockRoot: BlockRootHex,
|
|
102
|
+
slot: Slot,
|
|
103
|
+
maxAttestation: number
|
|
104
|
+
): gloas.PayloadAttestation[] {
|
|
105
|
+
const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.get(slot);
|
|
106
|
+
|
|
107
|
+
if (!aggregateByDataRootByBlockRoot) {
|
|
108
|
+
this.metrics?.opPool.payloadAttestationPool.getPayloadAttestationsCacheMisses.inc();
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const aggregateByDataRoot = aggregateByDataRootByBlockRoot.get(beaconBlockRoot);
|
|
113
|
+
|
|
114
|
+
if (!aggregateByDataRoot) {
|
|
115
|
+
this.metrics?.opPool.payloadAttestationPool.getPayloadAttestationsCacheMisses.inc();
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return Array.from(aggregateByDataRoot.values())
|
|
120
|
+
.slice()
|
|
121
|
+
.sort((a, b) => b.aggregationBits.getTrueBitIndexes().length - a.aggregationBits.getTrueBitIndexes().length)
|
|
122
|
+
.slice(0, maxAttestation)
|
|
123
|
+
.map(fastToPayloadAttestation);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
prune(clockSlot: Slot): void {
|
|
127
|
+
pruneBySlot(this.aggregateByDataRootByBlockRootBySlot, clockSlot, SLOTS_RETAINED);
|
|
128
|
+
this.lowestPermissibleSlot = clockSlot;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function messageToAggregate(message: gloas.PayloadAttestationMessage, validatorCommitteeIndex: number): AggregateFast {
|
|
133
|
+
return {
|
|
134
|
+
aggregationBits: BitArray.fromSingleBit(PTC_SIZE, validatorCommitteeIndex),
|
|
135
|
+
data: message.data,
|
|
136
|
+
signature: signatureFromBytesNoCheck(message.signature),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function aggregateMessageInto(
|
|
141
|
+
message: gloas.PayloadAttestationMessage,
|
|
142
|
+
validatorCommitteeIndex: number,
|
|
143
|
+
aggregate: AggregateFast
|
|
144
|
+
): InsertOutcome {
|
|
145
|
+
if (aggregate.aggregationBits.get(validatorCommitteeIndex) === true) {
|
|
146
|
+
return InsertOutcome.AlreadyKnown;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
aggregate.aggregationBits.set(validatorCommitteeIndex, true);
|
|
150
|
+
aggregate.signature = aggregateSignatures([aggregate.signature, signatureFromBytesNoCheck(message.signature)]);
|
|
151
|
+
|
|
152
|
+
return InsertOutcome.Aggregated;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function fastToPayloadAttestation(aggFast: AggregateFast): gloas.PayloadAttestation {
|
|
156
|
+
return {...aggFast, signature: aggFast.signature.toBytes()};
|
|
157
|
+
}
|
|
@@ -18,6 +18,7 @@ export enum RegenCaller {
|
|
|
18
18
|
validateGossipAggregateAndProof = "validateGossipAggregateAndProof",
|
|
19
19
|
validateGossipAttestation = "validateGossipAttestation",
|
|
20
20
|
validateGossipVoluntaryExit = "validateGossipVoluntaryExit",
|
|
21
|
+
validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid",
|
|
21
22
|
onForkChoiceFinalized = "onForkChoiceFinalized",
|
|
22
23
|
restApi = "restApi",
|
|
23
24
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export {SeenAggregators, SeenAttesters} from "./seenAttesters.js";
|
|
1
|
+
export {SeenAggregators, SeenAttesters, SeenPayloadAttesters} from "./seenAttesters.js";
|
|
2
2
|
export {SeenBlockProposers} from "./seenBlockProposers.js";
|
|
3
3
|
export {SeenSyncCommitteeMessages} from "./seenCommittee.js";
|
|
4
4
|
export {SeenContributionAndProof} from "./seenCommitteeContribution.js";
|
|
5
|
+
export {SeenExecutionPayloadBids} from "./seenExecutionPayloadBids.js";
|
|
6
|
+
export {SeenExecutionPayloadEnvelopes} from "./seenExecutionPayloadEnvelope.js";
|
|
5
7
|
export {SeenBlockInput} from "./seenGossipBlockInput.js";
|
|
@@ -56,3 +56,8 @@ export class SeenAttesters {
|
|
|
56
56
|
* Keeps a cache to filter aggregated attestations from the same aggregators in the same epoch
|
|
57
57
|
*/
|
|
58
58
|
export class SeenAggregators extends SeenAttesters {}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Keeps a cache to filter payload attestations from the same attesters in the same epoch
|
|
62
|
+
*/
|
|
63
|
+
export class SeenPayloadAttesters extends SeenAttesters {}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {BuilderIndex, Slot} from "@lodestar/types";
|
|
2
|
+
import {MapDef} from "@lodestar/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TODO GLOAS: Revisit this value and add rational for choosing it
|
|
6
|
+
*/
|
|
7
|
+
const SLOTS_RETAINED = 2;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tracks execution payload bids we've already seen per (slot, builder).
|
|
11
|
+
*/
|
|
12
|
+
export class SeenExecutionPayloadBids {
|
|
13
|
+
private readonly builderIndexesBySlot = new MapDef<Slot, Set<BuilderIndex>>(() => new Set<BuilderIndex>());
|
|
14
|
+
private lowestPermissibleSlot: Slot = 0;
|
|
15
|
+
|
|
16
|
+
isKnown(slot: Slot, builderIndex: BuilderIndex): boolean {
|
|
17
|
+
return this.builderIndexesBySlot.get(slot)?.has(builderIndex) === true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
add(slot: Slot, builderIndex: BuilderIndex): void {
|
|
21
|
+
if (slot < this.lowestPermissibleSlot) {
|
|
22
|
+
throw Error(`slot ${slot} < lowestPermissibleSlot ${this.lowestPermissibleSlot}`);
|
|
23
|
+
}
|
|
24
|
+
this.builderIndexesBySlot.getOrDefault(slot).add(builderIndex);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
prune(currentSlot: Slot): void {
|
|
28
|
+
this.lowestPermissibleSlot = Math.max(currentSlot - SLOTS_RETAINED, 0);
|
|
29
|
+
for (const slot of this.builderIndexesBySlot.keys()) {
|
|
30
|
+
if (slot < this.lowestPermissibleSlot) {
|
|
31
|
+
this.builderIndexesBySlot.delete(slot);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {RootHex, Slot} from "@lodestar/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cache to prevent processing multiple execution payload envelopes for the same block root.
|
|
5
|
+
* Only one builder qualifies to submit an execution payload for a given slot.
|
|
6
|
+
* We only keep track of envelopes of unfinalized slots.
|
|
7
|
+
* [IGNORE] The node has not seen another valid `SignedExecutionPayloadEnvelope` for this block root.
|
|
8
|
+
*/
|
|
9
|
+
export class SeenExecutionPayloadEnvelopes {
|
|
10
|
+
private readonly slotByBlockRoot = new Map<RootHex, Slot>();
|
|
11
|
+
private finalizedSlot: Slot = 0;
|
|
12
|
+
|
|
13
|
+
isKnown(blockRoot: RootHex): boolean {
|
|
14
|
+
return this.slotByBlockRoot.has(blockRoot);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
add(blockRoot: RootHex, slot: Slot): void {
|
|
18
|
+
if (slot < this.finalizedSlot) {
|
|
19
|
+
throw Error(`slot ${slot} < finalizedSlot ${this.finalizedSlot}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.slotByBlockRoot.set(blockRoot, slot);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
prune(finalizedSlot: Slot): void {
|
|
26
|
+
this.finalizedSlot = finalizedSlot;
|
|
27
|
+
|
|
28
|
+
for (const [blockRoot, slot] of this.slotByBlockRoot.entries()) {
|
|
29
|
+
if (slot < finalizedSlot) {
|
|
30
|
+
this.slotByBlockRoot.delete(blockRoot);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -71,11 +71,34 @@ async function validateAggregateAndProof(
|
|
|
71
71
|
const attData = aggregate.data;
|
|
72
72
|
const attSlot = attData.slot;
|
|
73
73
|
|
|
74
|
-
let
|
|
75
|
-
if (ForkSeq[fork] >= ForkSeq.
|
|
76
|
-
|
|
74
|
+
let committeeIndex: number | null;
|
|
75
|
+
if (ForkSeq[fork] >= ForkSeq.gloas) {
|
|
76
|
+
// [REJECT] `aggregate.data.index < 2`.
|
|
77
|
+
if (attData.index >= 2) {
|
|
78
|
+
throw new AttestationError(GossipAction.REJECT, {
|
|
79
|
+
code: AttestationErrorCode.INVALID_PAYLOAD_STATUS_VALUE,
|
|
80
|
+
attDataIndex: attData.index,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// [REJECT] `aggregate.data.index == 0` if `block.slot == aggregate.data.slot`.
|
|
84
|
+
const block = chain.forkChoice.getBlock(attData.beaconBlockRoot);
|
|
85
|
+
|
|
86
|
+
// If block is unknown, we don't handle it here. It will throw error later on at `verifyHeadBlockAndTargetRoot()`
|
|
87
|
+
if (block !== null && block.slot === attData.slot && attData.index !== 0) {
|
|
88
|
+
throw new AttestationError(GossipAction.REJECT, {
|
|
89
|
+
code: AttestationErrorCode.PREMATURELY_INDICATED_PAYLOAD_PRESENT,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// [REJECT] len(committee_indices) == 1, where committee_indices = get_committee_indices(aggregate)
|
|
94
|
+
committeeIndex = (aggregate as electra.Attestation).committeeBits.getSingleTrueBit();
|
|
95
|
+
if (committeeIndex === null) {
|
|
96
|
+
throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.NOT_EXACTLY_ONE_COMMITTEE_BIT_SET});
|
|
97
|
+
}
|
|
98
|
+
} else if (ForkSeq[fork] >= ForkSeq.electra) {
|
|
99
|
+
committeeIndex = (aggregate as electra.Attestation).committeeBits.getSingleTrueBit();
|
|
77
100
|
// [REJECT] len(committee_indices) == 1, where committee_indices = get_committee_indices(aggregate)
|
|
78
|
-
if (
|
|
101
|
+
if (committeeIndex === null) {
|
|
79
102
|
throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.NOT_EXACTLY_ONE_COMMITTEE_BIT_SET});
|
|
80
103
|
}
|
|
81
104
|
// [REJECT] aggregate.data.index == 0
|
|
@@ -83,11 +106,11 @@ async function validateAggregateAndProof(
|
|
|
83
106
|
throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX});
|
|
84
107
|
}
|
|
85
108
|
} else {
|
|
86
|
-
|
|
109
|
+
committeeIndex = attData.index;
|
|
87
110
|
}
|
|
88
111
|
|
|
89
112
|
const seenAttDataKey = serializedData ? getSeenAttDataKeyFromSignedAggregateAndProof(fork, serializedData) : null;
|
|
90
|
-
const cachedAttData = seenAttDataKey ? chain.seenAttestationDatas.get(attSlot,
|
|
113
|
+
const cachedAttData = seenAttDataKey ? chain.seenAttestationDatas.get(attSlot, committeeIndex, seenAttDataKey) : null;
|
|
91
114
|
|
|
92
115
|
const attEpoch = computeEpochAtSlot(attSlot);
|
|
93
116
|
const attTarget = attData.target;
|
|
@@ -136,7 +159,7 @@ async function validateAggregateAndProof(
|
|
|
136
159
|
: toRootHex(ssz.phase0.AttestationData.hashTreeRoot(attData));
|
|
137
160
|
if (
|
|
138
161
|
!skipValidationKnownAttesters &&
|
|
139
|
-
chain.seenAggregatedAttestations.isKnown(targetEpoch,
|
|
162
|
+
chain.seenAggregatedAttestations.isKnown(targetEpoch, committeeIndex, attDataRootHex, aggregationBits)
|
|
140
163
|
) {
|
|
141
164
|
throw new AttestationError(GossipAction.IGNORE, {
|
|
142
165
|
code: AttestationErrorCode.ATTESTERS_ALREADY_KNOWN,
|
|
@@ -177,7 +200,7 @@ async function validateAggregateAndProof(
|
|
|
177
200
|
// -- i.e. data.index < get_committee_count_per_slot(state, data.target.epoch)
|
|
178
201
|
const committeeValidatorIndices = cachedAttData
|
|
179
202
|
? cachedAttData.committeeValidatorIndices
|
|
180
|
-
: getCommitteeValidatorIndices(shuffling, attSlot,
|
|
203
|
+
: getCommitteeValidatorIndices(shuffling, attSlot, committeeIndex);
|
|
181
204
|
|
|
182
205
|
// [REJECT] The number of aggregation bits matches the committee size
|
|
183
206
|
// -- i.e. `len(aggregation_bits) == len(get_beacon_committee(state, aggregate.data.slot, index))`.
|
|
@@ -248,7 +271,7 @@ async function validateAggregateAndProof(
|
|
|
248
271
|
// Same race-condition check as above for seen aggregators
|
|
249
272
|
if (
|
|
250
273
|
!skipValidationKnownAttesters &&
|
|
251
|
-
chain.seenAggregatedAttestations.isKnown(targetEpoch,
|
|
274
|
+
chain.seenAggregatedAttestations.isKnown(targetEpoch, committeeIndex, attDataRootHex, aggregationBits)
|
|
252
275
|
) {
|
|
253
276
|
throw new AttestationError(GossipAction.IGNORE, {
|
|
254
277
|
code: AttestationErrorCode.ATTESTERS_ALREADY_KNOWN,
|
|
@@ -260,7 +283,7 @@ async function validateAggregateAndProof(
|
|
|
260
283
|
chain.seenAggregators.add(targetEpoch, aggregatorIndex);
|
|
261
284
|
chain.seenAggregatedAttestations.add(
|
|
262
285
|
targetEpoch,
|
|
263
|
-
|
|
286
|
+
committeeIndex,
|
|
264
287
|
attDataRootHex,
|
|
265
288
|
{aggregationBits, trueBitCount: attestingIndices.length},
|
|
266
289
|
false
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ForkSeq,
|
|
11
11
|
SLOTS_PER_EPOCH,
|
|
12
12
|
isForkPostElectra,
|
|
13
|
+
isForkPostGloas,
|
|
13
14
|
} from "@lodestar/params";
|
|
14
15
|
import {
|
|
15
16
|
EpochShuffling,
|
|
@@ -293,9 +294,29 @@ async function validateAttestationNoSignatureCheck(
|
|
|
293
294
|
// api or first time validation of a gossip attestation
|
|
294
295
|
committeeIndex = attestationOrCache.attestation.committeeIndex;
|
|
295
296
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
if (isForkPostGloas(fork)) {
|
|
298
|
+
// [REJECT] `attestation.data.index < 2`.
|
|
299
|
+
if (attData.index >= 2) {
|
|
300
|
+
throw new AttestationError(GossipAction.REJECT, {
|
|
301
|
+
code: AttestationErrorCode.INVALID_PAYLOAD_STATUS_VALUE,
|
|
302
|
+
attDataIndex: attData.index,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// [REJECT] `attestation.data.index == 0` if `block.slot == attestation.data.slot`.
|
|
307
|
+
const block = chain.forkChoice.getBlock(attData.beaconBlockRoot);
|
|
308
|
+
|
|
309
|
+
// block being null will be handled by `verifyHeadBlockAndTargetRoot`
|
|
310
|
+
if (block !== null && block.slot === attSlot && attData.index !== 0) {
|
|
311
|
+
throw new AttestationError(GossipAction.REJECT, {
|
|
312
|
+
code: AttestationErrorCode.PREMATURELY_INDICATED_PAYLOAD_PRESENT,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
// [REJECT] attestation.data.index == 0
|
|
317
|
+
if (attData.index !== 0) {
|
|
318
|
+
throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX});
|
|
319
|
+
}
|
|
299
320
|
}
|
|
300
321
|
} else {
|
|
301
322
|
// phase0 attestation
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {ForkName, isForkPostDeneb} from "@lodestar/params";
|
|
2
|
+
import {ForkName, isForkPostBellatrix, isForkPostDeneb, isForkPostGloas} from "@lodestar/params";
|
|
3
3
|
import {
|
|
4
4
|
computeEpochAtSlot,
|
|
5
5
|
computeStartSlotAtEpoch,
|
|
@@ -111,7 +111,7 @@ export async function validateGossipBlock(
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
|
|
114
|
-
if (isForkPostDeneb(fork)) {
|
|
114
|
+
if (isForkPostDeneb(fork) && !isForkPostGloas(fork)) {
|
|
115
115
|
const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
|
|
116
116
|
const maxBlobsPerBlock = config.getMaxBlobsPerBlock(computeEpochAtSlot(blockSlot));
|
|
117
117
|
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
@@ -128,6 +128,7 @@ export async function validateGossipBlock(
|
|
|
128
128
|
// this is something we should change this in the future to make the code airtight to the spec.
|
|
129
129
|
// [IGNORE] The block's parent (defined by block.parent_root) has been seen (via both gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is retrieved).
|
|
130
130
|
// [REJECT] The block's parent (defined by block.parent_root) passes validation.
|
|
131
|
+
// TODO GLOAS: post-gloas, we check the validity of bid's parent payload, not the entire beacon block
|
|
131
132
|
const blockState = await chain.regen
|
|
132
133
|
.getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
|
|
133
134
|
.catch(() => {
|
|
@@ -140,7 +141,7 @@ export async function validateGossipBlock(
|
|
|
140
141
|
// Extra conditions for merge fork blocks
|
|
141
142
|
// [REJECT] The block's execution payload timestamp is correct with respect to the slot
|
|
142
143
|
// -- i.e. execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot).
|
|
143
|
-
if (fork
|
|
144
|
+
if (isForkPostBellatrix(fork) && !isForkPostGloas(fork)) {
|
|
144
145
|
if (!isExecutionBlockBodyType(block.body)) throw Error("Not merge block type");
|
|
145
146
|
const executionPayload = block.body.executionPayload;
|
|
146
147
|
if (isExecutionStateType(blockState) && isExecutionEnabled(blockState, block)) {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {PublicKey} from "@chainsafe/blst";
|
|
2
|
+
import {
|
|
3
|
+
CachedBeaconStateGloas,
|
|
4
|
+
canBuilderCoverBid,
|
|
5
|
+
createSingleSignatureSetFromComponents,
|
|
6
|
+
getExecutionPayloadBidSigningRoot,
|
|
7
|
+
isActiveBuilder,
|
|
8
|
+
} from "@lodestar/state-transition";
|
|
9
|
+
import {gloas} from "@lodestar/types";
|
|
10
|
+
import {toRootHex} from "@lodestar/utils";
|
|
11
|
+
import {ExecutionPayloadBidError, ExecutionPayloadBidErrorCode, GossipAction} from "../errors/index.js";
|
|
12
|
+
import {IBeaconChain} from "../index.js";
|
|
13
|
+
import {RegenCaller} from "../regen/index.js";
|
|
14
|
+
|
|
15
|
+
export async function validateApiExecutionPayloadBid(
|
|
16
|
+
chain: IBeaconChain,
|
|
17
|
+
signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
return validateExecutionPayloadBid(chain, signedExecutionPayloadBid);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function validateGossipExecutionPayloadBid(
|
|
23
|
+
chain: IBeaconChain,
|
|
24
|
+
signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
return validateExecutionPayloadBid(chain, signedExecutionPayloadBid);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function validateExecutionPayloadBid(
|
|
30
|
+
chain: IBeaconChain,
|
|
31
|
+
signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
const bid = signedExecutionPayloadBid.message;
|
|
34
|
+
const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
|
|
35
|
+
const parentBlockHashHex = toRootHex(bid.parentBlockHash);
|
|
36
|
+
const state = (await chain.getHeadStateAtCurrentEpoch(
|
|
37
|
+
RegenCaller.validateGossipExecutionPayloadBid
|
|
38
|
+
)) as CachedBeaconStateGloas;
|
|
39
|
+
|
|
40
|
+
// [IGNORE] `bid.slot` is the current slot or the next slot.
|
|
41
|
+
const currentSlot = chain.clock.currentSlot;
|
|
42
|
+
if (bid.slot !== currentSlot && bid.slot !== currentSlot + 1) {
|
|
43
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
44
|
+
code: ExecutionPayloadBidErrorCode.INVALID_SLOT,
|
|
45
|
+
builderIndex: bid.builderIndex,
|
|
46
|
+
slot: bid.slot,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// [IGNORE] the `SignedProposerPreferences` where `preferences.proposal_slot`
|
|
51
|
+
// is equal to `bid.slot` has been seen.
|
|
52
|
+
// TODO GLOAS: Implement this along with proposer preference
|
|
53
|
+
|
|
54
|
+
// [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
|
|
55
|
+
// `is_active_builder(state, bid.builder_index)` returns `True`.
|
|
56
|
+
if (!isActiveBuilder(state, bid.builderIndex)) {
|
|
57
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
58
|
+
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
59
|
+
builderIndex: bid.builderIndex,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// [REJECT] `bid.execution_payment` is zero.
|
|
64
|
+
if (bid.executionPayment !== 0) {
|
|
65
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
66
|
+
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
67
|
+
builderIndex: bid.builderIndex,
|
|
68
|
+
executionPayment: bid.executionPayment,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// [REJECT] `bid.fee_recipient` matches the `fee_recipient` from the proposer's
|
|
73
|
+
// `SignedProposerPreferences` associated with `bid.slot`.
|
|
74
|
+
// [REJECT] `bid.gas_limit` matches the `gas_limit` from the proposer's
|
|
75
|
+
// `SignedProposerPreferences` associated with `bid.slot`.
|
|
76
|
+
// TODO GLOAS: Implement this along with proposer preference
|
|
77
|
+
|
|
78
|
+
// [IGNORE] this is the first signed bid seen with a valid signature from the given builder for this slot.
|
|
79
|
+
if (chain.seenExecutionPayloadBids.isKnown(bid.slot, bid.builderIndex)) {
|
|
80
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
81
|
+
code: ExecutionPayloadBidErrorCode.BID_ALREADY_KNOWN,
|
|
82
|
+
builderIndex: bid.builderIndex,
|
|
83
|
+
slot: bid.slot,
|
|
84
|
+
parentBlockRoot: parentBlockRootHex,
|
|
85
|
+
parentBlockHash: parentBlockHashHex,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// [IGNORE] this bid is the highest value bid seen for the corresponding slot
|
|
90
|
+
// and the given parent block hash.
|
|
91
|
+
const bestBid = chain.executionPayloadBidPool.getBestBid(parentBlockRootHex, parentBlockHashHex, bid.slot);
|
|
92
|
+
if (bestBid !== null && bestBid.value >= bid.value) {
|
|
93
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
94
|
+
code: ExecutionPayloadBidErrorCode.BID_TOO_LOW,
|
|
95
|
+
bidValue: bid.value,
|
|
96
|
+
currentHighestBid: bestBid.value,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// [IGNORE] `bid.value` is less or equal than the builder's excess balance --
|
|
100
|
+
// i.e. `can_builder_cover_bid(state, builder_index, amount)` returns `True`.
|
|
101
|
+
if (!canBuilderCoverBid(state, bid.builderIndex, bid.value)) {
|
|
102
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
103
|
+
code: ExecutionPayloadBidErrorCode.BID_TOO_HIGH,
|
|
104
|
+
bidValue: bid.value,
|
|
105
|
+
builderBalance: state.builders.getReadonly(bid.builderIndex).balance,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// [IGNORE] `bid.parent_block_hash` is the block hash of a known execution
|
|
110
|
+
// payload in fork choice.
|
|
111
|
+
// TODO GLOAS: implement this
|
|
112
|
+
|
|
113
|
+
// [IGNORE] `bid.parent_block_root` is the hash tree root of a known beacon
|
|
114
|
+
// block in fork choice.
|
|
115
|
+
const block = chain.forkChoice.getBlock(bid.parentBlockRoot);
|
|
116
|
+
if (block === null) {
|
|
117
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
118
|
+
code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
119
|
+
parentBlockRoot: parentBlockRootHex,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
|
|
124
|
+
const signatureSet = createSingleSignatureSetFromComponents(
|
|
125
|
+
PublicKey.fromBytes(state.builders.getReadonly(bid.builderIndex).pubkey),
|
|
126
|
+
getExecutionPayloadBidSigningRoot(chain.config, state as CachedBeaconStateGloas, bid),
|
|
127
|
+
signedExecutionPayloadBid.signature
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
|
|
131
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
132
|
+
code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE,
|
|
133
|
+
builderIndex: bid.builderIndex,
|
|
134
|
+
slot: bid.slot,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Valid
|
|
139
|
+
chain.seenExecutionPayloadBids.add(bid.slot, bid.builderIndex);
|
|
140
|
+
}
|