@lodestar/beacon-node 1.43.0-dev.38479366cc → 1.43.0-dev.3bcc6d0ad5
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/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +13 -3
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +45 -2
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +66 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +10 -21
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +5 -3
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +23 -10
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +7 -5
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +1 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +5 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts +2 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +26 -7
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
- package/lib/chain/chain.d.ts +2 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +5 -1
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +0 -11
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +0 -4
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/index.d.ts +1 -0
- package/lib/chain/errors/index.d.ts.map +1 -1
- package/lib/chain/errors/index.js +1 -0
- package/lib/chain/errors/index.js.map +1 -1
- package/lib/chain/errors/proposerPreferences.d.ts +33 -0
- package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
- package/lib/chain/errors/proposerPreferences.js +13 -0
- package/lib/chain/errors/proposerPreferences.js.map +1 -0
- package/lib/chain/interface.d.ts +2 -1
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
- package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.js +26 -4
- package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +15 -17
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +11 -3
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +33 -22
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- 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 +1 -0
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +1 -0
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +8 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +20 -4
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +15 -0
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
- package/lib/chain/seenCache/seenProposerPreferences.js +25 -0
- package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
- package/lib/chain/validation/proposerPreferences.d.ts +8 -0
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
- package/lib/chain/validation/proposerPreferences.js +69 -0
- package/lib/chain/validation/proposerPreferences.js.map +1 -0
- package/lib/network/gossip/interface.d.ts +7 -1
- package/lib/network/gossip/interface.d.ts.map +1 -1
- package/lib/network/gossip/interface.js +1 -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 +12 -1
- package/lib/network/gossip/scoringParameters.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +8 -0
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +6 -0
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/interface.d.ts +1 -0
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +1 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +5 -0
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +22 -15
- 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 +5 -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 +6 -5
- package/lib/network/processor/index.js.map +1 -1
- package/lib/sync/range/batch.d.ts +11 -0
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +29 -4
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +23 -5
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +0 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +0 -47
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +21 -15
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRoot.js +10 -0
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +16 -3
- package/src/api/impl/beacon/pool/index.ts +83 -1
- package/src/api/impl/validator/index.ts +80 -0
- package/src/chain/blocks/importBlock.ts +9 -36
- package/src/chain/blocks/importExecutionPayload.ts +31 -10
- package/src/chain/blocks/index.ts +14 -6
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +6 -1
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -0
- package/src/chain/blocks/verifyBlock.ts +39 -9
- package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
- package/src/chain/chain.ts +5 -0
- package/src/chain/emitter.ts +0 -11
- package/src/chain/errors/index.ts +1 -0
- package/src/chain/errors/proposerPreferences.ts +39 -0
- package/src/chain/interface.ts +2 -0
- package/src/chain/opPools/payloadAttestationPool.ts +29 -8
- package/src/chain/prepareNextSlot.ts +20 -28
- package/src/chain/produceBlock/produceBlockBody.ts +43 -27
- package/src/chain/regen/interface.ts +1 -0
- package/src/chain/seenCache/index.ts +1 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +25 -5
- package/src/chain/seenCache/seenProposerPreferences.ts +29 -0
- package/src/chain/validation/proposerPreferences.ts +91 -0
- package/src/network/gossip/interface.ts +6 -0
- package/src/network/gossip/scoringParameters.ts +14 -1
- package/src/network/gossip/topic.ts +6 -0
- package/src/network/interface.ts +1 -0
- package/src/network/network.ts +11 -0
- package/src/network/processor/gossipHandlers.ts +31 -16
- package/src/network/processor/gossipQueues/index.ts +5 -0
- package/src/network/processor/index.ts +6 -5
- package/src/sync/range/batch.ts +54 -5
- package/src/sync/range/chain.ts +25 -5
- package/src/sync/unknownBlock.ts +0 -50
- package/src/sync/utils/downloadByRange.ts +21 -15
- package/src/sync/utils/downloadByRoot.ts +12 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
|
-
import {ExecutionStatus, PayloadExecutionStatus} from "@lodestar/fork-choice";
|
|
3
|
-
import {isStatePostGloas} from "@lodestar/state-transition";
|
|
4
|
-
import {fromHex} from "@lodestar/utils";
|
|
2
|
+
import {ExecutionStatus, PayloadExecutionStatus, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
|
|
3
|
+
import {DataAvailabilityStatus, isStatePostGloas} from "@lodestar/state-transition";
|
|
4
|
+
import {fromHex, isErrorAborted} from "@lodestar/utils";
|
|
5
|
+
import {ZERO_HASH_HEX} from "../../constants/index.js";
|
|
5
6
|
import {ExecutionPayloadStatus} from "../../execution/index.js";
|
|
6
7
|
import {isQueueErrorAborted} from "../../util/queue/index.js";
|
|
7
8
|
import {BeaconChain} from "../chain.js";
|
|
@@ -85,12 +86,14 @@ function toForkChoiceExecutionStatus(status: ExecutionPayloadStatus): PayloadExe
|
|
|
85
86
|
* 4. Verify envelope (fields against state, signature, and EL in parallel where possible)
|
|
86
87
|
* 5. Persist verified payload envelope to hot DB (waits for write-queue space for backpressure)
|
|
87
88
|
* 6. Update fork choice (transitions the block's PENDING variant to FULL)
|
|
88
|
-
* 7.
|
|
89
|
-
* 8.
|
|
89
|
+
* 7. Queue notifyForkchoiceUpdate to engine api
|
|
90
|
+
* 8. Record metrics for payload envelope and column sources
|
|
91
|
+
* 9. Emit `execution_payload` event
|
|
90
92
|
*/
|
|
91
93
|
export async function importExecutionPayload(
|
|
92
94
|
this: BeaconChain,
|
|
93
95
|
payloadInput: PayloadEnvelopeInput,
|
|
96
|
+
dataAvailabilityStatus: DataAvailabilityStatus,
|
|
94
97
|
opts: ImportPayloadOpts = {}
|
|
95
98
|
): Promise<void> {
|
|
96
99
|
const signedEnvelope = payloadInput.getPayloadEnvelope();
|
|
@@ -219,15 +222,33 @@ export async function importExecutionPayload(
|
|
|
219
222
|
|
|
220
223
|
// 6. Update fork choice, transitions the block's PENDING variant to FULL
|
|
221
224
|
const execStatus = toForkChoiceExecutionStatus(execResult.status);
|
|
222
|
-
this.forkChoice.onExecutionPayload(
|
|
225
|
+
this.forkChoice.onExecutionPayload(
|
|
226
|
+
blockRootHex,
|
|
227
|
+
blockHashHex,
|
|
228
|
+
envelope.payload.blockNumber,
|
|
229
|
+
execStatus,
|
|
230
|
+
dataAvailabilityStatus
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// 7. Queue notifyForkchoiceUpdate to engine api
|
|
234
|
+
const head = this.forkChoice.getHead();
|
|
235
|
+
if (!this.opts.disableImportExecutionFcU && blockRootHex === head.blockRoot) {
|
|
236
|
+
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
|
|
237
|
+
const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
|
|
238
|
+
this.executionEngine.notifyForkchoiceUpdate(fork, blockHashHex, safeBlockHash, finalizedBlockHash).catch((e) => {
|
|
239
|
+
if (!isErrorAborted(e) && !isQueueErrorAborted(e)) {
|
|
240
|
+
this.logger.error("Error pushing notifyForkchoiceUpdate()", {blockHashHex, finalizedBlockHash}, e);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
223
244
|
|
|
224
|
-
//
|
|
245
|
+
// 8. Record metrics for payload envelope and column sources
|
|
225
246
|
this.metrics?.importPayload.bySource.inc({source: payloadInput.getPayloadEnvelopeSource().source});
|
|
226
247
|
for (const {source} of payloadInput.getSampledColumnsWithSource()) {
|
|
227
248
|
this.metrics?.importPayload.columnsBySource.inc({source});
|
|
228
249
|
}
|
|
229
250
|
|
|
230
|
-
//
|
|
251
|
+
// 9. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
|
|
231
252
|
if (this.clock.currentSlot - slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
|
|
232
253
|
this.emitter.emit(routes.events.EventType.executionPayload, {
|
|
233
254
|
slot,
|
|
@@ -261,6 +282,6 @@ export async function processExecutionPayload(
|
|
|
261
282
|
signal: AbortSignal,
|
|
262
283
|
opts: ImportPayloadOpts = {}
|
|
263
284
|
): Promise<void> {
|
|
264
|
-
await verifyPayloadsDataAvailability([payloadInput], signal);
|
|
265
|
-
await importExecutionPayload.call(this, payloadInput, opts);
|
|
285
|
+
const {dataAvailabilityStatuses} = await verifyPayloadsDataAvailability([payloadInput], signal);
|
|
286
|
+
await importExecutionPayload.call(this, payloadInput, dataAvailabilityStatuses[0], opts);
|
|
266
287
|
}
|
|
@@ -90,8 +90,14 @@ export async function processBlocks(
|
|
|
90
90
|
|
|
91
91
|
// Fully verify a block to be imported immediately after. Does not produce any side-effects besides adding intermediate
|
|
92
92
|
// states in the state cache through regen.
|
|
93
|
-
const {
|
|
94
|
-
|
|
93
|
+
const {
|
|
94
|
+
postStates,
|
|
95
|
+
blockDAStatuses,
|
|
96
|
+
payloadDAStatuses,
|
|
97
|
+
proposerBalanceDeltas,
|
|
98
|
+
segmentExecStatus,
|
|
99
|
+
indexedAttestationsByBlock,
|
|
100
|
+
} = await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, payloadEnvelopes, opts);
|
|
95
101
|
|
|
96
102
|
// If segmentExecStatus has lvhForkchoice then, the entire segment should be invalid
|
|
97
103
|
// and we need to further propagate
|
|
@@ -110,7 +116,7 @@ export async function processBlocks(
|
|
|
110
116
|
parentBlockSlot: parentSlots[i],
|
|
111
117
|
executionStatus: executionStatuses[i],
|
|
112
118
|
// start supporting optimistic syncing/processing
|
|
113
|
-
dataAvailabilityStatus:
|
|
119
|
+
dataAvailabilityStatus: blockDAStatuses[i],
|
|
114
120
|
proposerBalanceDelta: proposerBalanceDeltas[i],
|
|
115
121
|
indexedAttestations: indexedAttestationsByBlock[i],
|
|
116
122
|
// TODO: Make this param mandatory and capture in gossip
|
|
@@ -130,9 +136,11 @@ export async function processBlocks(
|
|
|
130
136
|
throw new Error(`Payload envelope for slot ${slot} not complete after DA verification`);
|
|
131
137
|
}
|
|
132
138
|
// we already awaited DA in verifyBlocksInEpoch for this segment
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
const payloadDA = payloadDAStatuses.get(slot);
|
|
140
|
+
if (payloadDA === undefined) {
|
|
141
|
+
throw new Error(`Missing payload DA status for slot ${slot}`);
|
|
142
|
+
}
|
|
143
|
+
await importExecutionPayload.call(this, payloadInput, payloadDA, {validSignature: false});
|
|
136
144
|
}
|
|
137
145
|
|
|
138
146
|
await nextEventLoop();
|
|
@@ -64,6 +64,7 @@ export class PayloadEnvelopeInput {
|
|
|
64
64
|
readonly proposerIndex: ValidatorIndex;
|
|
65
65
|
readonly bid: gloas.ExecutionPayloadBid;
|
|
66
66
|
readonly versionedHashes: VersionedHashes;
|
|
67
|
+
readonly daOutOfRange: boolean;
|
|
67
68
|
|
|
68
69
|
private columnsCache = new Map<ColumnIndex, ColumnWithSource>();
|
|
69
70
|
|
|
@@ -87,6 +88,7 @@ export class PayloadEnvelopeInput {
|
|
|
87
88
|
sampledColumns: ColumnIndex[];
|
|
88
89
|
custodyColumns: ColumnIndex[];
|
|
89
90
|
timeCreatedSec: number;
|
|
91
|
+
daOutOfRange: boolean;
|
|
90
92
|
}) {
|
|
91
93
|
this.blockRootHex = props.blockRootHex;
|
|
92
94
|
this.slot = props.slot;
|
|
@@ -97,13 +99,14 @@ export class PayloadEnvelopeInput {
|
|
|
97
99
|
this.sampledColumns = props.sampledColumns;
|
|
98
100
|
this.custodyColumns = props.custodyColumns;
|
|
99
101
|
this.timeCreatedSec = props.timeCreatedSec;
|
|
102
|
+
this.daOutOfRange = props.daOutOfRange;
|
|
100
103
|
this.payloadEnvelopeDataPromise = createPromise();
|
|
101
104
|
this.allDataPromise = createPromise();
|
|
102
105
|
this.columnsDataPromise = createPromise();
|
|
103
106
|
|
|
104
107
|
const noBlobs = props.bid.blobKzgCommitments.length === 0;
|
|
105
108
|
const noSampledColumns = props.sampledColumns.length === 0;
|
|
106
|
-
const hasAllData = noBlobs || noSampledColumns;
|
|
109
|
+
const hasAllData = props.daOutOfRange || noBlobs || noSampledColumns;
|
|
107
110
|
|
|
108
111
|
if (hasAllData) {
|
|
109
112
|
this.state = {hasPayload: false, hasAllData: true, hasComputedAllData: true};
|
|
@@ -125,6 +128,7 @@ export class PayloadEnvelopeInput {
|
|
|
125
128
|
sampledColumns: props.sampledColumns,
|
|
126
129
|
custodyColumns: props.custodyColumns,
|
|
127
130
|
timeCreatedSec: props.timeCreatedSec,
|
|
131
|
+
daOutOfRange: props.daOutOfRange,
|
|
128
132
|
});
|
|
129
133
|
}
|
|
130
134
|
|
|
@@ -152,6 +156,7 @@ export class PayloadEnvelopeInput {
|
|
|
152
156
|
throw new Error("Payload envelope beacon_block_root mismatch");
|
|
153
157
|
}
|
|
154
158
|
|
|
159
|
+
// TODO GLOAS: track source by metrics, maybe inside the seen cache
|
|
155
160
|
const source: SourceMeta = {
|
|
156
161
|
source: props.source,
|
|
157
162
|
seenTimestampSec: props.seenTimestampSec,
|
|
@@ -41,7 +41,8 @@ export async function verifyBlocksInEpoch(
|
|
|
41
41
|
postStates: IBeaconStateView[];
|
|
42
42
|
proposerBalanceDeltas: number[];
|
|
43
43
|
segmentExecStatus: SegmentExecStatus;
|
|
44
|
-
|
|
44
|
+
blockDAStatuses: DataAvailabilityStatus[];
|
|
45
|
+
payloadDAStatuses: Map<Slot, DataAvailabilityStatus>;
|
|
45
46
|
indexedAttestationsByBlock: IndexedAttestation[][];
|
|
46
47
|
}> {
|
|
47
48
|
const blocks = blockInputs.map((blockInput) => blockInput.getBlock());
|
|
@@ -116,8 +117,12 @@ export async function verifyBlocksInEpoch(
|
|
|
116
117
|
|
|
117
118
|
// Pick the data-availability source by fork:
|
|
118
119
|
// - Pre-Gloas: blob/Fulu-column data lives in IBlockInput → verifyBlocksDataAvailability.
|
|
119
|
-
// - Post-Gloas: verifyPayloadsDataAvailability
|
|
120
|
-
const daAvailabilityPromise
|
|
120
|
+
// - Post-Gloas: verifyPayloadsDataAvailability (payload-level DA, keyed by slot).
|
|
121
|
+
const daAvailabilityPromise: Promise<{
|
|
122
|
+
blockDAStatuses: DataAvailabilityStatus[];
|
|
123
|
+
payloadDAStatuses: Map<Slot, DataAvailabilityStatus>;
|
|
124
|
+
availableTime: number;
|
|
125
|
+
}> =
|
|
121
126
|
fork >= ForkSeq.gloas
|
|
122
127
|
? (async () => {
|
|
123
128
|
const payloadInputsForDa: PayloadEnvelopeInput[] = [];
|
|
@@ -125,19 +130,37 @@ export async function verifyBlocksInEpoch(
|
|
|
125
130
|
const pi = payloadEnvelopes?.get(input.slot);
|
|
126
131
|
if (pi !== undefined) payloadInputsForDa.push(pi);
|
|
127
132
|
}
|
|
128
|
-
await verifyPayloadsDataAvailability(
|
|
133
|
+
const {dataAvailabilityStatuses, availableTime} = await verifyPayloadsDataAvailability(
|
|
134
|
+
payloadInputsForDa,
|
|
135
|
+
abortController.signal
|
|
136
|
+
);
|
|
137
|
+
const payloadDAStatuses = new Map<Slot, DataAvailabilityStatus>();
|
|
138
|
+
for (let i = 0; i < payloadInputsForDa.length; i++) {
|
|
139
|
+
payloadDAStatuses.set(payloadInputsForDa[i].slot, dataAvailabilityStatuses[i]);
|
|
140
|
+
}
|
|
129
141
|
return {
|
|
130
142
|
// post-gloas, DataAvailabilityStatus is NotRequired for forkChoice.onBlock() ProtoBlock
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
blockDAStatuses: blockInputs.map(() => DataAvailabilityStatus.NotRequired),
|
|
144
|
+
payloadDAStatuses,
|
|
145
|
+
availableTime,
|
|
133
146
|
};
|
|
134
147
|
})()
|
|
135
|
-
:
|
|
148
|
+
: (async () => {
|
|
149
|
+
const {dataAvailabilityStatuses, availableTime} = await verifyBlocksDataAvailability(
|
|
150
|
+
blockInputs,
|
|
151
|
+
abortController.signal
|
|
152
|
+
);
|
|
153
|
+
return {
|
|
154
|
+
blockDAStatuses: dataAvailabilityStatuses,
|
|
155
|
+
payloadDAStatuses: new Map<Slot, DataAvailabilityStatus>(),
|
|
156
|
+
availableTime,
|
|
157
|
+
};
|
|
158
|
+
})();
|
|
136
159
|
|
|
137
160
|
// batch all I/O operations to reduce overhead
|
|
138
161
|
const [
|
|
139
162
|
segmentExecStatus,
|
|
140
|
-
{
|
|
163
|
+
{blockDAStatuses, payloadDAStatuses, availableTime},
|
|
141
164
|
{postStates, proposerBalanceDeltas, verifyStateTime},
|
|
142
165
|
{verifySignaturesTime},
|
|
143
166
|
] = await Promise.all([
|
|
@@ -258,7 +281,14 @@ export async function verifyBlocksInEpoch(
|
|
|
258
281
|
);
|
|
259
282
|
}
|
|
260
283
|
|
|
261
|
-
return {
|
|
284
|
+
return {
|
|
285
|
+
postStates,
|
|
286
|
+
blockDAStatuses,
|
|
287
|
+
payloadDAStatuses,
|
|
288
|
+
proposerBalanceDeltas,
|
|
289
|
+
segmentExecStatus,
|
|
290
|
+
indexedAttestationsByBlock,
|
|
291
|
+
};
|
|
262
292
|
} finally {
|
|
263
293
|
abortController.abort();
|
|
264
294
|
}
|
|
@@ -28,11 +28,14 @@ export async function verifyPayloadsDataAvailability(
|
|
|
28
28
|
await Promise.all(promises);
|
|
29
29
|
|
|
30
30
|
const availableTime = Math.max(0, Math.max(...payloadInputs.map((payloadInput) => payloadInput.getTimeComplete())));
|
|
31
|
-
const dataAvailabilityStatuses: DataAvailabilityStatus[] = payloadInputs.map((payloadInput) =>
|
|
32
|
-
payloadInput.
|
|
31
|
+
const dataAvailabilityStatuses: DataAvailabilityStatus[] = payloadInputs.map((payloadInput) => {
|
|
32
|
+
if (payloadInput.daOutOfRange) {
|
|
33
|
+
return DataAvailabilityStatus.OutOfRange;
|
|
34
|
+
}
|
|
35
|
+
return payloadInput.getBlobKzgCommitments().length === 0
|
|
33
36
|
? DataAvailabilityStatus.NotRequired
|
|
34
|
-
: DataAvailabilityStatus.Available
|
|
35
|
-
);
|
|
37
|
+
: DataAvailabilityStatus.Available;
|
|
38
|
+
});
|
|
36
39
|
|
|
37
40
|
return {dataAvailabilityStatuses, availableTime};
|
|
38
41
|
}
|
package/src/chain/chain.ts
CHANGED
|
@@ -106,6 +106,7 @@ import {
|
|
|
106
106
|
SeenExecutionPayloadBids,
|
|
107
107
|
SeenPayloadAttesters,
|
|
108
108
|
SeenPayloadEnvelopeInput,
|
|
109
|
+
SeenProposerPreferences,
|
|
109
110
|
SeenSyncCommitteeMessages,
|
|
110
111
|
} from "./seenCache/index.js";
|
|
111
112
|
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
|
|
@@ -186,6 +187,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
186
187
|
readonly seenPayloadAttesters = new SeenPayloadAttesters();
|
|
187
188
|
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
|
|
188
189
|
readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
|
|
190
|
+
readonly seenProposerPreferences = new SeenProposerPreferences();
|
|
189
191
|
readonly seenBlockProposers = new SeenBlockProposers();
|
|
190
192
|
readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
|
|
191
193
|
readonly seenContributionAndProof: SeenContributionAndProof;
|
|
@@ -334,6 +336,8 @@ export class BeaconChain implements IBeaconChain {
|
|
|
334
336
|
logger,
|
|
335
337
|
});
|
|
336
338
|
this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
|
|
339
|
+
config,
|
|
340
|
+
clock,
|
|
337
341
|
chainEvents: emitter,
|
|
338
342
|
signal,
|
|
339
343
|
serializedCache: this.serializedCache,
|
|
@@ -1437,6 +1441,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1437
1441
|
this.payloadAttestationPool.prune(slot);
|
|
1438
1442
|
this.executionPayloadBidPool.prune(slot);
|
|
1439
1443
|
this.seenExecutionPayloadBids.prune(slot);
|
|
1444
|
+
this.seenProposerPreferences.prune(slot);
|
|
1440
1445
|
this.seenAttestationDatas.onSlot(slot);
|
|
1441
1446
|
this.reprocessController.onSlot(slot);
|
|
1442
1447
|
|
package/src/chain/emitter.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {routes} from "@lodestar/api";
|
|
|
4
4
|
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
5
5
|
import {IBeaconStateView} from "@lodestar/state-transition";
|
|
6
6
|
import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
|
|
7
|
-
import {SignedExecutionPayloadEnvelope} from "@lodestar/types/gloas";
|
|
8
7
|
import {PeerIdStr} from "../util/peerId.js";
|
|
9
8
|
import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
|
|
10
9
|
import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
|
|
@@ -60,10 +59,6 @@ export enum ChainEvent {
|
|
|
60
59
|
* Post-gloas, missing parent could be a SignedBeaconBlock and/or a SignedExecutionPayloadEnvelope
|
|
61
60
|
*/
|
|
62
61
|
blockUnknownParent = "blockUnknownParent",
|
|
63
|
-
/**
|
|
64
|
-
* Trigger BlockInputSync to find a SignedBeaconBlock given a SignedExecutionPayloadEnvelop received
|
|
65
|
-
*/
|
|
66
|
-
envelopeUnknownBlock = "envelopeUnknownBlock",
|
|
67
62
|
/**
|
|
68
63
|
* Trigger BlockInputSync to find a SignedBeaconBlock with specified block root.
|
|
69
64
|
*/
|
|
@@ -92,11 +87,6 @@ type ApiEvents = {[K in routes.events.EventType]: (data: routes.events.EventData
|
|
|
92
87
|
|
|
93
88
|
export type ChainEventData = {
|
|
94
89
|
[ChainEvent.blockUnknownParent]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
|
|
95
|
-
[ChainEvent.envelopeUnknownBlock]: {
|
|
96
|
-
envelope: SignedExecutionPayloadEnvelope;
|
|
97
|
-
peer?: PeerIdStr;
|
|
98
|
-
source: BlockInputSource;
|
|
99
|
-
};
|
|
100
90
|
[ChainEvent.unknownBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
|
|
101
91
|
[ChainEvent.incompleteBlockInput]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
|
|
102
92
|
[ChainEvent.incompletePayloadEnvelope]: {
|
|
@@ -124,7 +114,6 @@ export type IChainEvents = ApiEvents & {
|
|
|
124
114
|
// Sync events that are chain->chain. Initiated from network requests but do not cross the network
|
|
125
115
|
// barrier so are considered ChainEvent(s).
|
|
126
116
|
[ChainEvent.blockUnknownParent]: (data: ChainEventData[ChainEvent.blockUnknownParent]) => void;
|
|
127
|
-
[ChainEvent.envelopeUnknownBlock]: (data: ChainEventData[ChainEvent.envelopeUnknownBlock]) => void;
|
|
128
117
|
[ChainEvent.unknownBlockRoot]: (data: ChainEventData[ChainEvent.unknownBlockRoot]) => void;
|
|
129
118
|
[ChainEvent.incompleteBlockInput]: (data: ChainEventData[ChainEvent.incompleteBlockInput]) => void;
|
|
130
119
|
[ChainEvent.incompletePayloadEnvelope]: (data: ChainEventData[ChainEvent.incompletePayloadEnvelope]) => void;
|
|
@@ -8,6 +8,7 @@ export * from "./executionPayloadBid.js";
|
|
|
8
8
|
export * from "./executionPayloadEnvelope.js";
|
|
9
9
|
export * from "./gossipValidation.js";
|
|
10
10
|
export * from "./payloadAttestation.js";
|
|
11
|
+
export * from "./proposerPreferences.js";
|
|
11
12
|
export * from "./proposerSlashingError.js";
|
|
12
13
|
export * from "./syncCommitteeError.js";
|
|
13
14
|
export * from "./voluntaryExitError.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {Slot, ValidatorIndex} from "@lodestar/types";
|
|
2
|
+
import {GossipActionError} from "./gossipValidation.js";
|
|
3
|
+
|
|
4
|
+
export enum ProposerPreferencesErrorCode {
|
|
5
|
+
INVALID_EPOCH = "PROPOSER_PREFERENCES_ERROR_INVALID_EPOCH",
|
|
6
|
+
PROPOSAL_SLOT_PASSED = "PROPOSER_PREFERENCES_ERROR_PROPOSAL_SLOT_PASSED",
|
|
7
|
+
INVALID_PROPOSER = "PROPOSER_PREFERENCES_ERROR_INVALID_PROPOSER",
|
|
8
|
+
ALREADY_KNOWN = "PROPOSER_PREFERENCES_ERROR_ALREADY_KNOWN",
|
|
9
|
+
INVALID_SIGNATURE = "PROPOSER_PREFERENCES_ERROR_INVALID_SIGNATURE",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ProposerPreferencesErrorType =
|
|
13
|
+
| {
|
|
14
|
+
code: ProposerPreferencesErrorCode.INVALID_EPOCH;
|
|
15
|
+
proposalSlot: Slot;
|
|
16
|
+
currentEpoch: number;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED;
|
|
20
|
+
proposalSlot: Slot;
|
|
21
|
+
currentSlot: Slot;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
code: ProposerPreferencesErrorCode.INVALID_PROPOSER;
|
|
25
|
+
proposalSlot: Slot;
|
|
26
|
+
validatorIndex: ValidatorIndex;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
code: ProposerPreferencesErrorCode.ALREADY_KNOWN;
|
|
30
|
+
proposalSlot: Slot;
|
|
31
|
+
validatorIndex: ValidatorIndex;
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
code: ProposerPreferencesErrorCode.INVALID_SIGNATURE;
|
|
35
|
+
proposalSlot: Slot;
|
|
36
|
+
validatorIndex: ValidatorIndex;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export class ProposerPreferencesError extends GossipActionError<ProposerPreferencesErrorType> {}
|
package/src/chain/interface.ts
CHANGED
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
SeenContributionAndProof,
|
|
62
62
|
SeenExecutionPayloadBids,
|
|
63
63
|
SeenPayloadAttesters,
|
|
64
|
+
SeenProposerPreferences,
|
|
64
65
|
SeenSyncCommitteeMessages,
|
|
65
66
|
} from "./seenCache/index.js";
|
|
66
67
|
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
|
|
@@ -131,6 +132,7 @@ export interface IBeaconChain {
|
|
|
131
132
|
readonly seenPayloadAttesters: SeenPayloadAttesters;
|
|
132
133
|
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
|
|
133
134
|
readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
|
|
135
|
+
readonly seenProposerPreferences: SeenProposerPreferences;
|
|
134
136
|
readonly seenBlockProposers: SeenBlockProposers;
|
|
135
137
|
readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
|
|
136
138
|
readonly seenContributionAndProof: SeenContributionAndProof;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {Signature, aggregateSignatures} from "@chainsafe/blst";
|
|
2
2
|
import {BitArray} from "@chainsafe/ssz";
|
|
3
3
|
import {ChainForkConfig} from "@lodestar/config";
|
|
4
|
-
import {MAX_COMMITTEES_PER_SLOT, PTC_SIZE} from "@lodestar/params";
|
|
4
|
+
import {MAX_COMMITTEES_PER_SLOT, MAX_PAYLOAD_ATTESTATIONS, PTC_SIZE} from "@lodestar/params";
|
|
5
5
|
import {RootHex, Slot, gloas} from "@lodestar/types";
|
|
6
6
|
import {MapDef, toRootHex} from "@lodestar/utils";
|
|
7
7
|
import {Metrics} from "../../metrics/metrics.js";
|
|
@@ -95,13 +95,9 @@ export class PayloadAttestationPool {
|
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* Get payload attestations to be included in a block.
|
|
98
|
-
* Pick the top `
|
|
98
|
+
* Pick the top `MAX_PAYLOAD_ATTESTATIONS` aggregates with the most votes.
|
|
99
99
|
*/
|
|
100
|
-
getPayloadAttestationsForBlock(
|
|
101
|
-
beaconBlockRoot: BlockRootHex,
|
|
102
|
-
slot: Slot,
|
|
103
|
-
maxAttestation: number
|
|
104
|
-
): gloas.PayloadAttestation[] {
|
|
100
|
+
getPayloadAttestationsForBlock(beaconBlockRoot: BlockRootHex, slot: Slot): gloas.PayloadAttestation[] {
|
|
105
101
|
const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.get(slot);
|
|
106
102
|
|
|
107
103
|
if (!aggregateByDataRootByBlockRoot) {
|
|
@@ -119,7 +115,32 @@ export class PayloadAttestationPool {
|
|
|
119
115
|
return Array.from(aggregateByDataRoot.values())
|
|
120
116
|
.slice()
|
|
121
117
|
.sort((a, b) => b.aggregationBits.getTrueBitIndexes().length - a.aggregationBits.getTrueBitIndexes().length)
|
|
122
|
-
.slice(0,
|
|
118
|
+
.slice(0, MAX_PAYLOAD_ATTESTATIONS)
|
|
119
|
+
.map(fastToPayloadAttestation);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getAll(slot?: Slot): gloas.PayloadAttestation[] {
|
|
123
|
+
const aggregates: AggregateFast[] = [];
|
|
124
|
+
|
|
125
|
+
const addAggregates = (aggregateByDataRootByBlockRoot: Map<BlockRootHex, Map<DataRootHex, AggregateFast>>) => {
|
|
126
|
+
for (const aggregateByDataRoot of aggregateByDataRootByBlockRoot.values()) {
|
|
127
|
+
aggregates.push(...aggregateByDataRoot.values());
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (slot !== undefined) {
|
|
132
|
+
const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.get(slot);
|
|
133
|
+
if (aggregateByDataRootByBlockRoot) {
|
|
134
|
+
addAggregates(aggregateByDataRootByBlockRoot);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
for (const aggregateByDataRootByBlockRoot of this.aggregateByDataRootByBlockRootBySlot.values()) {
|
|
138
|
+
addAggregates(aggregateByDataRootByBlockRoot);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return aggregates
|
|
143
|
+
.sort((a, b) => b.aggregationBits.getTrueBitIndexes().length - a.aggregationBits.getTrueBitIndexes().length)
|
|
123
144
|
.map(fastToPayloadAttestation);
|
|
124
145
|
}
|
|
125
146
|
|
|
@@ -4,13 +4,14 @@ import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
|
|
|
4
4
|
import {ForkPostBellatrix, ForkSeq, SLOTS_PER_EPOCH, isForkPostBellatrix} from "@lodestar/params";
|
|
5
5
|
import {
|
|
6
6
|
IBeaconStateView,
|
|
7
|
+
IBeaconStateViewBellatrix,
|
|
7
8
|
StateHashTreeRootSource,
|
|
8
9
|
computeEpochAtSlot,
|
|
9
10
|
computeTimeAtSlot,
|
|
10
11
|
isStatePostBellatrix,
|
|
11
12
|
isStatePostGloas,
|
|
12
13
|
} from "@lodestar/state-transition";
|
|
13
|
-
import {Bytes32, Slot
|
|
14
|
+
import {Bytes32, Slot} from "@lodestar/types";
|
|
14
15
|
import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
|
|
15
16
|
import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
|
|
16
17
|
import {BuilderStatus} from "../execution/builder/http.js";
|
|
@@ -165,19 +166,26 @@ export class PrepareNextSlotScheduler {
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
let parentBlockHash: Bytes32;
|
|
168
|
-
|
|
169
|
+
// Apply parent payload once here as it's reused by EL prep and SSE emit below
|
|
170
|
+
let stateAfterParentPayload: IBeaconStateViewBellatrix = updatedPrepareState;
|
|
169
171
|
if (isStatePostGloas(updatedPrepareState)) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
if (this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot)) {
|
|
173
|
+
parentBlockHash = updatedPrepareState.latestExecutionPayloadBid.blockHash;
|
|
174
|
+
// Skip applying parent payload unless we're proposing the next slot or have to emit payload_attributes events
|
|
175
|
+
if (feeRecipient !== undefined || this.chain.opts.emitPayloadAttributes === true) {
|
|
176
|
+
const parentExecutionRequests = await this.chain.getParentExecutionRequests(
|
|
177
|
+
updatedHead.slot,
|
|
178
|
+
updatedHead.blockRoot
|
|
179
|
+
);
|
|
180
|
+
stateAfterParentPayload = updatedPrepareState.withParentPayloadApplied(parentExecutionRequests);
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
parentBlockHash = updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
|
|
184
|
+
}
|
|
174
185
|
} else {
|
|
175
186
|
parentBlockHash = updatedPrepareState.latestExecutionPayloadHeader.blockHash;
|
|
176
187
|
}
|
|
177
188
|
|
|
178
|
-
// Reused by the SSE emit below to avoid a second DB lookup on cache miss
|
|
179
|
-
let parentExecutionRequests: electra.ExecutionRequests | undefined;
|
|
180
|
-
|
|
181
189
|
if (feeRecipient) {
|
|
182
190
|
const preparationTime =
|
|
183
191
|
computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
|
|
@@ -187,13 +195,6 @@ export class PrepareNextSlotScheduler {
|
|
|
187
195
|
const finalizedBlockHash =
|
|
188
196
|
this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
|
|
189
197
|
|
|
190
|
-
if (isExtendingPayload) {
|
|
191
|
-
parentExecutionRequests = await this.chain.getParentExecutionRequests(
|
|
192
|
-
updatedHead.slot,
|
|
193
|
-
updatedHead.blockRoot
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
198
|
// awaiting here instead of throwing an async call because there is no other task
|
|
198
199
|
// left for scheduler and this gives nice semantics to catch and log errors in the
|
|
199
200
|
// try/catch wrapper here.
|
|
@@ -205,9 +206,8 @@ export class PrepareNextSlotScheduler {
|
|
|
205
206
|
parentBlockHash,
|
|
206
207
|
safeBlockHash,
|
|
207
208
|
finalizedBlockHash,
|
|
208
|
-
|
|
209
|
-
feeRecipient
|
|
210
|
-
parentExecutionRequests
|
|
209
|
+
stateAfterParentPayload,
|
|
210
|
+
feeRecipient
|
|
211
211
|
);
|
|
212
212
|
this.logger.verbose("PrepareNextSlotScheduler prepared new payload", {
|
|
213
213
|
prepareSlot,
|
|
@@ -234,20 +234,12 @@ export class PrepareNextSlotScheduler {
|
|
|
234
234
|
(feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
|
|
235
235
|
this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
|
|
236
236
|
) {
|
|
237
|
-
// if we didn't fetch above (not proposing), SSE still needs it here
|
|
238
|
-
if (!parentExecutionRequests && isExtendingPayload) {
|
|
239
|
-
parentExecutionRequests = await this.chain.getParentExecutionRequests(
|
|
240
|
-
updatedHead.slot,
|
|
241
|
-
updatedHead.blockRoot
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
237
|
const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
|
|
245
|
-
prepareState:
|
|
238
|
+
prepareState: stateAfterParentPayload,
|
|
246
239
|
prepareSlot,
|
|
247
240
|
parentBlockRoot: fromHex(updatedHead.blockRoot),
|
|
248
241
|
parentBlockHash,
|
|
249
242
|
feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
|
|
250
|
-
parentExecutionRequests,
|
|
251
243
|
});
|
|
252
244
|
this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
|
|
253
245
|
}
|