@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,122 @@
|
|
|
1
|
+
import {PublicKey} from "@chainsafe/blst";
|
|
2
|
+
import {
|
|
3
|
+
CachedBeaconStateGloas,
|
|
4
|
+
computeStartSlotAtEpoch,
|
|
5
|
+
createSingleSignatureSetFromComponents,
|
|
6
|
+
getExecutionPayloadEnvelopeSigningRoot,
|
|
7
|
+
} from "@lodestar/state-transition";
|
|
8
|
+
import {gloas} from "@lodestar/types";
|
|
9
|
+
import {toRootHex} from "@lodestar/utils";
|
|
10
|
+
import {ExecutionPayloadEnvelopeError, ExecutionPayloadEnvelopeErrorCode, GossipAction} from "../errors/index.js";
|
|
11
|
+
import {IBeaconChain} from "../index.js";
|
|
12
|
+
|
|
13
|
+
export async function validateApiExecutionPayloadEnvelope(
|
|
14
|
+
chain: IBeaconChain,
|
|
15
|
+
executionPayloadEnvelope: gloas.SignedExecutionPayloadEnvelope
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
return validateExecutionPayloadEnvelope(chain, executionPayloadEnvelope);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function validateGossipExecutionPayloadEnvelope(
|
|
21
|
+
chain: IBeaconChain,
|
|
22
|
+
executionPayloadEnvelope: gloas.SignedExecutionPayloadEnvelope
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
return validateExecutionPayloadEnvelope(chain, executionPayloadEnvelope);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function validateExecutionPayloadEnvelope(
|
|
28
|
+
chain: IBeaconChain,
|
|
29
|
+
executionPayloadEnvelope: gloas.SignedExecutionPayloadEnvelope
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
const envelope = executionPayloadEnvelope.message;
|
|
32
|
+
const {payload} = envelope;
|
|
33
|
+
const blockRootHex = toRootHex(envelope.beaconBlockRoot);
|
|
34
|
+
|
|
35
|
+
// [IGNORE] The envelope's block root `envelope.block_root` has been seen (via
|
|
36
|
+
// gossip or non-gossip sources) (a client MAY queue payload for processing once
|
|
37
|
+
// the block is retrieved).
|
|
38
|
+
// TODO GLOAS: Need to review this
|
|
39
|
+
const block = chain.forkChoice.getBlock(envelope.beaconBlockRoot);
|
|
40
|
+
if (block === null) {
|
|
41
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
|
|
42
|
+
code: ExecutionPayloadEnvelopeErrorCode.BLOCK_ROOT_UNKNOWN,
|
|
43
|
+
blockRoot: blockRootHex,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// [IGNORE] The node has not seen another valid
|
|
48
|
+
// `SignedExecutionPayloadEnvelope` for this block root from this builder.
|
|
49
|
+
if (chain.seenExecutionPayloadEnvelopes.isKnown(blockRootHex)) {
|
|
50
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
|
|
51
|
+
code: ExecutionPayloadEnvelopeErrorCode.ENVELOPE_ALREADY_KNOWN,
|
|
52
|
+
blockRoot: blockRootHex,
|
|
53
|
+
slot: envelope.slot,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// [IGNORE] The envelope is from a slot greater than or equal to the latest finalized slot -- i.e. validate that `envelope.slot >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)`
|
|
58
|
+
const finalizedCheckpoint = chain.forkChoice.getFinalizedCheckpoint();
|
|
59
|
+
const finalizedSlot = computeStartSlotAtEpoch(finalizedCheckpoint.epoch);
|
|
60
|
+
if (envelope.slot < finalizedSlot) {
|
|
61
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
|
|
62
|
+
code: ExecutionPayloadEnvelopeErrorCode.BELONG_TO_FINALIZED_BLOCK,
|
|
63
|
+
envelopeSlot: envelope.slot,
|
|
64
|
+
finalizedSlot,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// [REJECT] `block` passes validation.
|
|
69
|
+
// TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
|
|
70
|
+
// it is possible that the block didn't pass the validation
|
|
71
|
+
|
|
72
|
+
// [REJECT] `block.slot` equals `envelope.slot`.
|
|
73
|
+
if (block.slot !== envelope.slot) {
|
|
74
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
75
|
+
code: ExecutionPayloadEnvelopeErrorCode.SLOT_MISMATCH,
|
|
76
|
+
envelopeSlot: envelope.slot,
|
|
77
|
+
blockSlot: block.slot,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (block.builderIndex === undefined || block.blockHashHex === undefined) {
|
|
82
|
+
// This indicates this block is a pre-gloas block which is wrong
|
|
83
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
|
|
84
|
+
code: ExecutionPayloadEnvelopeErrorCode.CACHE_FAIL,
|
|
85
|
+
blockRoot: blockRootHex,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// [REJECT] `envelope.builder_index == bid.builder_index`
|
|
90
|
+
if (envelope.builderIndex !== block.builderIndex) {
|
|
91
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
92
|
+
code: ExecutionPayloadEnvelopeErrorCode.BUILDER_INDEX_MISMATCH,
|
|
93
|
+
envelopeBuilderIndex: envelope.builderIndex,
|
|
94
|
+
bidBuilderIndex: block.builderIndex,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// [REJECT] `payload.block_hash == bid.block_hash`
|
|
99
|
+
if (toRootHex(payload.blockHash) !== block.blockHashHex) {
|
|
100
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
101
|
+
code: ExecutionPayloadEnvelopeErrorCode.BLOCK_HASH_MISMATCH,
|
|
102
|
+
envelopeBlockHash: toRootHex(payload.blockHash),
|
|
103
|
+
bidBlockHash: block.blockHashHex,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// [REJECT] `signed_execution_payload_envelope.signature` is valid with respect to the builder's public key.
|
|
108
|
+
const state = chain.getHeadState() as CachedBeaconStateGloas;
|
|
109
|
+
const signatureSet = createSingleSignatureSetFromComponents(
|
|
110
|
+
PublicKey.fromBytes(state.builders.getReadonly(envelope.builderIndex).pubkey),
|
|
111
|
+
getExecutionPayloadEnvelopeSigningRoot(chain.config, envelope),
|
|
112
|
+
executionPayloadEnvelope.signature
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
|
|
116
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
117
|
+
code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
chain.seenExecutionPayloadEnvelopes.add(blockRootHex, envelope.slot);
|
|
122
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CachedBeaconStateGloas,
|
|
3
|
+
computeEpochAtSlot,
|
|
4
|
+
createSingleSignatureSetFromComponents,
|
|
5
|
+
getPayloadAttestationDataSigningRoot,
|
|
6
|
+
} from "@lodestar/state-transition";
|
|
7
|
+
import {RootHex, gloas, ssz} from "@lodestar/types";
|
|
8
|
+
import {toRootHex} from "@lodestar/utils";
|
|
9
|
+
import {GossipAction, PayloadAttestationError, PayloadAttestationErrorCode} from "../errors/index.js";
|
|
10
|
+
import {IBeaconChain} from "../index.js";
|
|
11
|
+
|
|
12
|
+
export type PayloadAttestationValidationResult = {
|
|
13
|
+
attDataRootHex: RootHex;
|
|
14
|
+
validatorCommitteeIndex: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function validateApiPayloadAttestationMessage(
|
|
18
|
+
chain: IBeaconChain,
|
|
19
|
+
payloadAttestationMessage: gloas.PayloadAttestationMessage
|
|
20
|
+
): Promise<PayloadAttestationValidationResult> {
|
|
21
|
+
return validatePayloadAttestationMessage(chain, payloadAttestationMessage);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function validateGossipPayloadAttestationMessage(
|
|
25
|
+
chain: IBeaconChain,
|
|
26
|
+
payloadAttestationMessage: gloas.PayloadAttestationMessage
|
|
27
|
+
): Promise<PayloadAttestationValidationResult> {
|
|
28
|
+
return validatePayloadAttestationMessage(chain, payloadAttestationMessage);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function validatePayloadAttestationMessage(
|
|
32
|
+
chain: IBeaconChain,
|
|
33
|
+
payloadAttestationMessage: gloas.PayloadAttestationMessage
|
|
34
|
+
): Promise<PayloadAttestationValidationResult> {
|
|
35
|
+
const {data, validatorIndex} = payloadAttestationMessage;
|
|
36
|
+
const epoch = computeEpochAtSlot(data.slot);
|
|
37
|
+
|
|
38
|
+
// [IGNORE] The message's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `data.slot == current_slot`.
|
|
39
|
+
if (!chain.clock.isCurrentSlotGivenGossipDisparity(data.slot)) {
|
|
40
|
+
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
41
|
+
code: PayloadAttestationErrorCode.NOT_CURRENT_SLOT,
|
|
42
|
+
currentSlot: chain.clock.currentSlot,
|
|
43
|
+
slot: data.slot,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// [IGNORE] The `payload_attestation_message` is the first valid message received
|
|
48
|
+
// from the validator with index `payload_attestation_message.validator_index`.
|
|
49
|
+
// A single validator can participate PTC at most once per epoch
|
|
50
|
+
if (chain.seenPayloadAttesters.isKnown(epoch, validatorIndex)) {
|
|
51
|
+
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
52
|
+
code: PayloadAttestationErrorCode.PAYLOAD_ATTESTATION_ALREADY_KNOWN,
|
|
53
|
+
validatorIndex,
|
|
54
|
+
slot: data.slot,
|
|
55
|
+
blockRoot: toRootHex(data.beaconBlockRoot),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// [IGNORE] The message's block `data.beacon_block_root` has been seen (via
|
|
60
|
+
// gossip or non-gossip sources) (a client MAY queue attestation for processing
|
|
61
|
+
// once the block is retrieved. Note a client might want to request payload after).
|
|
62
|
+
const block = chain.forkChoice.getBlock(data.beaconBlockRoot);
|
|
63
|
+
if (block === null) {
|
|
64
|
+
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
65
|
+
code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
66
|
+
blockRoot: toRootHex(data.beaconBlockRoot),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const state = chain.getHeadState() as CachedBeaconStateGloas;
|
|
71
|
+
|
|
72
|
+
// [REJECT] The message's block `data.beacon_block_root` passes validation.
|
|
73
|
+
// TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
|
|
74
|
+
// it is possible that the block didn't pass the validation
|
|
75
|
+
|
|
76
|
+
// [REJECT] The message's validator index is within the payload committee in
|
|
77
|
+
// `get_ptc(state, data.slot)`. The `state` is the head state corresponding to
|
|
78
|
+
// processing the block up to the current slot as determined by the fork choice.
|
|
79
|
+
const ptc = state.epochCtx.getPayloadTimelinessCommittee(data.slot);
|
|
80
|
+
const validatorCommitteeIndex = ptc.indexOf(validatorIndex);
|
|
81
|
+
|
|
82
|
+
if (validatorCommitteeIndex === -1) {
|
|
83
|
+
throw new PayloadAttestationError(GossipAction.REJECT, {
|
|
84
|
+
code: PayloadAttestationErrorCode.INVALID_ATTESTER,
|
|
85
|
+
attesterIndex: validatorIndex,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// [REJECT] `payload_attestation_message.signature` is valid with respect to the validator's public key.
|
|
90
|
+
const signatureSet = createSingleSignatureSetFromComponents(
|
|
91
|
+
chain.index2pubkey[validatorIndex],
|
|
92
|
+
getPayloadAttestationDataSigningRoot(state, data),
|
|
93
|
+
payloadAttestationMessage.signature
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
|
|
97
|
+
throw new PayloadAttestationError(GossipAction.REJECT, {
|
|
98
|
+
code: PayloadAttestationErrorCode.INVALID_SIGNATURE,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Valid
|
|
103
|
+
chain.seenPayloadAttesters.add(epoch, validatorIndex);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
attDataRootHex: toRootHex(ssz.gloas.PayloadAttestationData.hashTreeRoot(data)),
|
|
107
|
+
validatorCommitteeIndex,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -9,7 +9,7 @@ import {Bucket, getBucketNameByValue} from "../buckets.js";
|
|
|
9
9
|
export class CheckpointStateRepository extends BinaryRepository<Uint8Array> {
|
|
10
10
|
constructor(config: ChainForkConfig, db: Db) {
|
|
11
11
|
const bucket = Bucket.allForks_checkpointState;
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
super(config, db, bucket, getBucketNameByValue(bucket));
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -1141,6 +1141,46 @@ export function createLodestarMetrics(
|
|
|
1141
1141
|
help: "Total number of empty returns in SyncContributionAndProofPool.getAggregate(slot, root)",
|
|
1142
1142
|
}),
|
|
1143
1143
|
},
|
|
1144
|
+
payloadAttestationPool: {
|
|
1145
|
+
size: register.gauge({
|
|
1146
|
+
name: "lodestar_oppool_payload_attestation_pool_size",
|
|
1147
|
+
help: "Current size of the PayloadAttestationPool = total payload attestations unique by data and slot",
|
|
1148
|
+
}),
|
|
1149
|
+
payloadAttDataPerSlot: register.gauge({
|
|
1150
|
+
name: "lodestar_oppool_payload_attestation_pool_payload_attestation_data_per_slot_total",
|
|
1151
|
+
help: "Total number of payload attestation data per slot in PayloadAttestationPool",
|
|
1152
|
+
}),
|
|
1153
|
+
gossipInsertOutcome: register.counter<{insertOutcome: InsertOutcome}>({
|
|
1154
|
+
name: "lodestar_oppool_payload_attestation_pool_gossip_insert_outcome_total",
|
|
1155
|
+
help: "Total number of InsertOutcome as a result of adding a payload attestation message from gossip to the pool",
|
|
1156
|
+
labelNames: ["insertOutcome"],
|
|
1157
|
+
}),
|
|
1158
|
+
apiInsertOutcome: register.counter<{insertOutcome: InsertOutcome}>({
|
|
1159
|
+
name: "lodestar_oppool_payload_attestation_pool_api_insert_outcome_total",
|
|
1160
|
+
help: "Total number of InsertOutcome as a result of adding a payload attestation message from api to the pool",
|
|
1161
|
+
labelNames: ["insertOutcome"],
|
|
1162
|
+
}),
|
|
1163
|
+
getPayloadAttestationsCacheMisses: register.counter({
|
|
1164
|
+
name: "lodestar_oppool_payload_attestation_pool_get_payload_attestations_cache_misses_total",
|
|
1165
|
+
help: "Total number of getPayloadAttestationsForBlock calls with no aggregate for slot and payload attestation data root",
|
|
1166
|
+
}),
|
|
1167
|
+
},
|
|
1168
|
+
executionPayloadBidPool: {
|
|
1169
|
+
size: register.gauge({
|
|
1170
|
+
name: "lodestar_oppool_execution_payload_bid_pool_size",
|
|
1171
|
+
help: "Current size of the ExecutionPayloadBidPool = total number of bids",
|
|
1172
|
+
}),
|
|
1173
|
+
gossipInsertOutcome: register.counter<{insertOutcome: InsertOutcome}>({
|
|
1174
|
+
name: "lodestar_oppool_execution_payload_bid_pool_gossip_insert_outcome_total",
|
|
1175
|
+
help: "Total number of InsertOutcome as a result of adding an execution payload bid from gossip to the pool",
|
|
1176
|
+
labelNames: ["insertOutcome"],
|
|
1177
|
+
}),
|
|
1178
|
+
apiInsertOutcome: register.counter<{insertOutcome: InsertOutcome}>({
|
|
1179
|
+
name: "lodestar_oppool_execution_payload_bid_pool_api_insert_outcome_total",
|
|
1180
|
+
help: "Total number of InsertOutcome as a result of adding an execution payload bid from api to the pool",
|
|
1181
|
+
labelNames: ["insertOutcome"],
|
|
1182
|
+
}),
|
|
1183
|
+
},
|
|
1144
1184
|
},
|
|
1145
1185
|
|
|
1146
1186
|
chain: {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
capella,
|
|
16
16
|
deneb,
|
|
17
17
|
fulu,
|
|
18
|
+
gloas,
|
|
18
19
|
phase0,
|
|
19
20
|
} from "@lodestar/types";
|
|
20
21
|
import {Logger} from "@lodestar/utils";
|
|
@@ -37,6 +38,9 @@ export enum GossipType {
|
|
|
37
38
|
light_client_finality_update = "light_client_finality_update",
|
|
38
39
|
light_client_optimistic_update = "light_client_optimistic_update",
|
|
39
40
|
bls_to_execution_change = "bls_to_execution_change",
|
|
41
|
+
execution_payload = "execution_payload",
|
|
42
|
+
payload_attestation_message = "payload_attestation_message",
|
|
43
|
+
execution_payload_bid = "execution_payload_bid",
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
export type SequentialGossipType = Exclude<GossipType, GossipType.beacon_attestation>;
|
|
@@ -71,6 +75,9 @@ export type GossipTopicTypeMap = {
|
|
|
71
75
|
[GossipType.light_client_finality_update]: {type: GossipType.light_client_finality_update};
|
|
72
76
|
[GossipType.light_client_optimistic_update]: {type: GossipType.light_client_optimistic_update};
|
|
73
77
|
[GossipType.bls_to_execution_change]: {type: GossipType.bls_to_execution_change};
|
|
78
|
+
[GossipType.execution_payload]: {type: GossipType.execution_payload};
|
|
79
|
+
[GossipType.payload_attestation_message]: {type: GossipType.payload_attestation_message};
|
|
80
|
+
[GossipType.execution_payload_bid]: {type: GossipType.execution_payload_bid};
|
|
74
81
|
};
|
|
75
82
|
|
|
76
83
|
export type GossipTopicMap = {
|
|
@@ -100,6 +107,9 @@ export type GossipTypeMap = {
|
|
|
100
107
|
[GossipType.light_client_finality_update]: LightClientFinalityUpdate;
|
|
101
108
|
[GossipType.light_client_optimistic_update]: LightClientOptimisticUpdate;
|
|
102
109
|
[GossipType.bls_to_execution_change]: capella.SignedBLSToExecutionChange;
|
|
110
|
+
[GossipType.execution_payload]: gloas.SignedExecutionPayloadEnvelope;
|
|
111
|
+
[GossipType.payload_attestation_message]: gloas.PayloadAttestationMessage;
|
|
112
|
+
[GossipType.execution_payload_bid]: gloas.SignedExecutionPayloadBid;
|
|
103
113
|
};
|
|
104
114
|
|
|
105
115
|
export type GossipFnByType = {
|
|
@@ -124,6 +134,13 @@ export type GossipFnByType = {
|
|
|
124
134
|
[GossipType.bls_to_execution_change]: (
|
|
125
135
|
blsToExecutionChange: capella.SignedBLSToExecutionChange
|
|
126
136
|
) => Promise<void> | void;
|
|
137
|
+
[GossipType.execution_payload]: (
|
|
138
|
+
executionPayloadEnvelope: gloas.SignedExecutionPayloadEnvelope
|
|
139
|
+
) => Promise<void> | void;
|
|
140
|
+
[GossipType.payload_attestation_message]: (
|
|
141
|
+
payloadAttestationMessage: gloas.PayloadAttestationMessage
|
|
142
|
+
) => Promise<void> | void;
|
|
143
|
+
[GossipType.execution_payload_bid]: (executionPayloadBid: gloas.SignedExecutionPayloadBid) => Promise<void> | void;
|
|
127
144
|
};
|
|
128
145
|
|
|
129
146
|
export type GossipFn = GossipFnByType[keyof GossipFnByType];
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
defaultTopicScoreParams,
|
|
6
6
|
} from "@chainsafe/libp2p-gossipsub/score";
|
|
7
7
|
import {BeaconConfig} from "@lodestar/config";
|
|
8
|
-
import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, TARGET_AGGREGATORS_PER_COMMITTEE} from "@lodestar/params";
|
|
8
|
+
import {ATTESTATION_SUBNET_COUNT, PTC_SIZE, SLOTS_PER_EPOCH, TARGET_AGGREGATORS_PER_COMMITTEE} from "@lodestar/params";
|
|
9
9
|
import {computeCommitteeCount} from "@lodestar/state-transition";
|
|
10
10
|
import {getActiveForkBoundaries} from "../forks.js";
|
|
11
11
|
import {Eth2Context} from "./gossipsub.js";
|
|
@@ -24,6 +24,9 @@ const VOLUNTARY_EXIT_WEIGHT = 0.05;
|
|
|
24
24
|
const PROPOSER_SLASHING_WEIGHT = 0.05;
|
|
25
25
|
const ATTESTER_SLASHING_WEIGHT = 0.05;
|
|
26
26
|
const BLS_TO_EXECUTION_CHANGE_WEIGHT = 0.05;
|
|
27
|
+
const EXECUTION_PAYLOAD_WEIGHT = 0.5;
|
|
28
|
+
const PAYLOAD_ATTESTATION_WEIGHT = 0.05;
|
|
29
|
+
const EXECUTION_PAYLOAD_BID_WEIGHT = 0.05;
|
|
27
30
|
|
|
28
31
|
const beaconAttestationSubnetWeight = 1 / ATTESTATION_SUBNET_COUNT;
|
|
29
32
|
const maxPositiveScore =
|
|
@@ -34,7 +37,10 @@ const maxPositiveScore =
|
|
|
34
37
|
VOLUNTARY_EXIT_WEIGHT +
|
|
35
38
|
PROPOSER_SLASHING_WEIGHT +
|
|
36
39
|
ATTESTER_SLASHING_WEIGHT +
|
|
37
|
-
BLS_TO_EXECUTION_CHANGE_WEIGHT
|
|
40
|
+
BLS_TO_EXECUTION_CHANGE_WEIGHT +
|
|
41
|
+
EXECUTION_PAYLOAD_WEIGHT +
|
|
42
|
+
PAYLOAD_ATTESTATION_WEIGHT +
|
|
43
|
+
EXECUTION_PAYLOAD_BID_WEIGHT);
|
|
38
44
|
|
|
39
45
|
/**
|
|
40
46
|
* The following params is implemented by Lighthouse at
|
|
@@ -172,6 +178,26 @@ function getAllTopicsScoreParams(
|
|
|
172
178
|
expectedMessageRate: 1 / 5 / SLOTS_PER_EPOCH,
|
|
173
179
|
firstMessageDecayTime: epochDurationMs * 100,
|
|
174
180
|
});
|
|
181
|
+
topicsParams[
|
|
182
|
+
stringifyGossipTopic(config, {
|
|
183
|
+
type: GossipType.payload_attestation_message,
|
|
184
|
+
boundary,
|
|
185
|
+
})
|
|
186
|
+
] = getTopicScoreParams(config, precomputedParams, {
|
|
187
|
+
topicWeight: PAYLOAD_ATTESTATION_WEIGHT,
|
|
188
|
+
expectedMessageRate: PTC_SIZE,
|
|
189
|
+
firstMessageDecayTime: epochDurationMs * 100,
|
|
190
|
+
});
|
|
191
|
+
topicsParams[
|
|
192
|
+
stringifyGossipTopic(config, {
|
|
193
|
+
type: GossipType.execution_payload_bid,
|
|
194
|
+
boundary,
|
|
195
|
+
})
|
|
196
|
+
] = getTopicScoreParams(config, precomputedParams, {
|
|
197
|
+
topicWeight: EXECUTION_PAYLOAD_BID_WEIGHT,
|
|
198
|
+
expectedMessageRate: 1024, // TODO GLOAS: Need an estimate for this
|
|
199
|
+
firstMessageDecayTime: epochDurationMs * 100,
|
|
200
|
+
});
|
|
175
201
|
|
|
176
202
|
// other topics
|
|
177
203
|
topicsParams[
|
|
@@ -190,6 +216,22 @@ function getAllTopicsScoreParams(
|
|
|
190
216
|
currentSlot: eth2Context.currentSlot,
|
|
191
217
|
},
|
|
192
218
|
});
|
|
219
|
+
topicsParams[
|
|
220
|
+
stringifyGossipTopic(config, {
|
|
221
|
+
type: GossipType.execution_payload,
|
|
222
|
+
boundary,
|
|
223
|
+
})
|
|
224
|
+
] = getTopicScoreParams(config, precomputedParams, {
|
|
225
|
+
topicWeight: EXECUTION_PAYLOAD_WEIGHT,
|
|
226
|
+
expectedMessageRate: 1,
|
|
227
|
+
firstMessageDecayTime: epochDurationMs * 20,
|
|
228
|
+
meshMessageInfo: {
|
|
229
|
+
decaySlots: SLOTS_PER_EPOCH * 5,
|
|
230
|
+
capFactor: 3,
|
|
231
|
+
activationWindow: epochDurationMs,
|
|
232
|
+
currentSlot: eth2Context.currentSlot,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
193
235
|
|
|
194
236
|
const activeValidatorCount = eth2Context.activeValidatorCount;
|
|
195
237
|
const {aggregatorsPerslot, committeesPerSlot} = expectedAggregatorCountPerSlot(activeValidatorCount);
|
|
@@ -69,6 +69,9 @@ function stringifyGossipTopicType(topic: GossipTopic): string {
|
|
|
69
69
|
case GossipType.light_client_finality_update:
|
|
70
70
|
case GossipType.light_client_optimistic_update:
|
|
71
71
|
case GossipType.bls_to_execution_change:
|
|
72
|
+
case GossipType.execution_payload:
|
|
73
|
+
case GossipType.payload_attestation_message:
|
|
74
|
+
case GossipType.execution_payload_bid:
|
|
72
75
|
return topic.type;
|
|
73
76
|
case GossipType.beacon_attestation:
|
|
74
77
|
case GossipType.sync_committee:
|
|
@@ -114,6 +117,12 @@ export function getGossipSSZType(topic: GossipTopic) {
|
|
|
114
117
|
: ssz.altair.LightClientFinalityUpdate;
|
|
115
118
|
case GossipType.bls_to_execution_change:
|
|
116
119
|
return ssz.capella.SignedBLSToExecutionChange;
|
|
120
|
+
case GossipType.execution_payload:
|
|
121
|
+
return ssz.gloas.SignedExecutionPayloadEnvelope;
|
|
122
|
+
case GossipType.payload_attestation_message:
|
|
123
|
+
return ssz.gloas.PayloadAttestationMessage;
|
|
124
|
+
case GossipType.execution_payload_bid:
|
|
125
|
+
return ssz.gloas.SignedExecutionPayloadBid;
|
|
117
126
|
}
|
|
118
127
|
}
|
|
119
128
|
|
|
@@ -190,6 +199,9 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr:
|
|
|
190
199
|
case GossipType.light_client_finality_update:
|
|
191
200
|
case GossipType.light_client_optimistic_update:
|
|
192
201
|
case GossipType.bls_to_execution_change:
|
|
202
|
+
case GossipType.execution_payload:
|
|
203
|
+
case GossipType.payload_attestation_message:
|
|
204
|
+
case GossipType.execution_payload_bid:
|
|
193
205
|
return {type: gossipTypeStr, boundary, encoding};
|
|
194
206
|
}
|
|
195
207
|
|
|
@@ -240,6 +252,12 @@ export function getCoreTopicsAtFork(
|
|
|
240
252
|
{type: GossipType.attester_slashing},
|
|
241
253
|
];
|
|
242
254
|
|
|
255
|
+
if (ForkSeq[fork] >= ForkSeq.gloas) {
|
|
256
|
+
topics.push({type: GossipType.execution_payload});
|
|
257
|
+
topics.push({type: GossipType.payload_attestation_message});
|
|
258
|
+
topics.push({type: GossipType.execution_payload_bid});
|
|
259
|
+
}
|
|
260
|
+
|
|
243
261
|
// After fulu also track data_column_sidecar_{index}
|
|
244
262
|
if (ForkSeq[fork] >= ForkSeq.fulu) {
|
|
245
263
|
topics.push(...getDataColumnSidecarTopics(networkConfig));
|
|
@@ -329,4 +347,7 @@ export const gossipTopicIgnoreDuplicatePublishError: Record<GossipType, boolean>
|
|
|
329
347
|
[GossipType.light_client_finality_update]: false,
|
|
330
348
|
[GossipType.light_client_optimistic_update]: false,
|
|
331
349
|
[GossipType.bls_to_execution_change]: true,
|
|
350
|
+
[GossipType.execution_payload]: true,
|
|
351
|
+
[GossipType.payload_attestation_message]: true,
|
|
352
|
+
[GossipType.execution_payload_bid]: true,
|
|
332
353
|
};
|
|
@@ -48,6 +48,8 @@ import {
|
|
|
48
48
|
import {IBeaconChain} from "../../chain/interface.js";
|
|
49
49
|
import {validateGossipBlobSidecar} from "../../chain/validation/blobSidecar.js";
|
|
50
50
|
import {validateGossipDataColumnSidecar} from "../../chain/validation/dataColumnSidecar.js";
|
|
51
|
+
import {validateGossipExecutionPayloadBid} from "../../chain/validation/executionPayloadBid.js";
|
|
52
|
+
import {validateGossipExecutionPayloadEnvelope} from "../../chain/validation/executionPayloadEnvelope.js";
|
|
51
53
|
import {
|
|
52
54
|
AggregateAndProofValidationResult,
|
|
53
55
|
GossipAttestation,
|
|
@@ -64,6 +66,7 @@ import {
|
|
|
64
66
|
} from "../../chain/validation/index.js";
|
|
65
67
|
import {validateLightClientFinalityUpdate} from "../../chain/validation/lightClientFinalityUpdate.js";
|
|
66
68
|
import {validateLightClientOptimisticUpdate} from "../../chain/validation/lightClientOptimisticUpdate.js";
|
|
69
|
+
import {validateGossipPayloadAttestationMessage} from "../../chain/validation/payloadAttestationMessage.js";
|
|
67
70
|
import {OpSource} from "../../chain/validatorMonitor.js";
|
|
68
71
|
import {Metrics} from "../../metrics/index.js";
|
|
69
72
|
import {kzgCommitmentToVersionedHash} from "../../util/blobs.js";
|
|
@@ -815,6 +818,51 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
815
818
|
|
|
816
819
|
chain.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange);
|
|
817
820
|
},
|
|
821
|
+
[GossipType.execution_payload]: async ({
|
|
822
|
+
gossipData,
|
|
823
|
+
topic,
|
|
824
|
+
}: GossipHandlerParamGeneric<GossipType.execution_payload>) => {
|
|
825
|
+
const {serializedData} = gossipData;
|
|
826
|
+
const executionPayloadEnvelope = sszDeserialize(topic, serializedData);
|
|
827
|
+
await validateGossipExecutionPayloadEnvelope(chain, executionPayloadEnvelope);
|
|
828
|
+
|
|
829
|
+
// TODO GLOAS: Handle valid envelope. Need an import flow that calls `processExecutionPayloadEnvelope` and fork choice
|
|
830
|
+
},
|
|
831
|
+
[GossipType.payload_attestation_message]: async ({
|
|
832
|
+
gossipData,
|
|
833
|
+
topic,
|
|
834
|
+
}: GossipHandlerParamGeneric<GossipType.payload_attestation_message>) => {
|
|
835
|
+
const {serializedData} = gossipData;
|
|
836
|
+
const payloadAttestationMessage = sszDeserialize(topic, serializedData);
|
|
837
|
+
const validationResult = await validateGossipPayloadAttestationMessage(chain, payloadAttestationMessage);
|
|
838
|
+
|
|
839
|
+
try {
|
|
840
|
+
const insertOutcome = chain.payloadAttestationPool.add(
|
|
841
|
+
payloadAttestationMessage,
|
|
842
|
+
validationResult.attDataRootHex,
|
|
843
|
+
validationResult.validatorCommitteeIndex
|
|
844
|
+
);
|
|
845
|
+
metrics?.opPool.payloadAttestationPool.gossipInsertOutcome.inc({insertOutcome});
|
|
846
|
+
} catch (e) {
|
|
847
|
+
logger.error("Error adding to payloadAttestation pool", {}, e as Error);
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
[GossipType.execution_payload_bid]: async ({
|
|
851
|
+
gossipData,
|
|
852
|
+
topic,
|
|
853
|
+
}: GossipHandlerParamGeneric<GossipType.execution_payload_bid>) => {
|
|
854
|
+
const {serializedData} = gossipData;
|
|
855
|
+
const executionPayloadBid = sszDeserialize(topic, serializedData);
|
|
856
|
+
await validateGossipExecutionPayloadBid(chain, executionPayloadBid);
|
|
857
|
+
|
|
858
|
+
// Handle valid payload bid by storing in a bid pool
|
|
859
|
+
try {
|
|
860
|
+
const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid.message);
|
|
861
|
+
metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
|
|
862
|
+
} catch (e) {
|
|
863
|
+
logger.error("Error adding to executionPayloadBid pool", {}, e as Error);
|
|
864
|
+
}
|
|
865
|
+
},
|
|
818
866
|
};
|
|
819
867
|
}
|
|
820
868
|
|
|
@@ -67,6 +67,22 @@ const linearGossipQueueOpts: {
|
|
|
67
67
|
type: QueueType.FIFO,
|
|
68
68
|
dropOpts: {type: DropType.count, count: 1},
|
|
69
69
|
},
|
|
70
|
+
[GossipType.execution_payload]: {
|
|
71
|
+
maxLength: 1024,
|
|
72
|
+
type: QueueType.FIFO,
|
|
73
|
+
dropOpts: {type: DropType.count, count: 1},
|
|
74
|
+
},
|
|
75
|
+
[GossipType.payload_attestation_message]: {
|
|
76
|
+
maxLength: 1024,
|
|
77
|
+
type: QueueType.FIFO,
|
|
78
|
+
dropOpts: {type: DropType.count, count: 1},
|
|
79
|
+
},
|
|
80
|
+
// TODO GLOAS: It is hard to predict how many bids are there. Put 1024 for max length for now
|
|
81
|
+
[GossipType.execution_payload_bid]: {
|
|
82
|
+
maxLength: 1024,
|
|
83
|
+
type: QueueType.FIFO,
|
|
84
|
+
dropOpts: {type: DropType.count, count: 1},
|
|
85
|
+
},
|
|
70
86
|
};
|
|
71
87
|
|
|
72
88
|
const indexedGossipQueueOpts: {
|
|
@@ -78,6 +78,9 @@ const executeGossipWorkOrderObj: Record<GossipType, WorkOpts> = {
|
|
|
78
78
|
[GossipType.sync_committee]: {},
|
|
79
79
|
[GossipType.light_client_finality_update]: {},
|
|
80
80
|
[GossipType.light_client_optimistic_update]: {},
|
|
81
|
+
[GossipType.execution_payload]: {bypassQueue: true},
|
|
82
|
+
[GossipType.payload_attestation_message]: {},
|
|
83
|
+
[GossipType.execution_payload_bid]: {},
|
|
81
84
|
};
|
|
82
85
|
const executeGossipWorkOrder = Object.keys(executeGossipWorkOrderObj) as (keyof typeof executeGossipWorkOrderObj)[];
|
|
83
86
|
|