@lodestar/beacon-node 1.50.0-dev.cea8fba405 → 1.50.0-dev.d99080f29a
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 +6 -1
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +4 -4
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +6 -2
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts +4 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +4 -3
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +4 -0
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/chain.d.ts +5 -4
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +14 -16
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.d.ts +5 -0
- package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.js +2 -0
- package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
- package/lib/chain/errors/gossipValidation.d.ts +3 -1
- package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
- package/lib/chain/errors/gossipValidation.js +2 -0
- package/lib/chain/errors/gossipValidation.js.map +1 -1
- package/lib/chain/initState.d.ts +2 -9
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +5 -38
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/opPools/opPool.d.ts +5 -4
- package/lib/chain/opPools/opPool.d.ts.map +1 -1
- package/lib/chain/opPools/opPool.js +62 -18
- package/lib/chain/opPools/opPool.js.map +1 -1
- package/lib/chain/opPools/utils.d.ts +4 -1
- package/lib/chain/opPools/utils.d.ts.map +1 -1
- package/lib/chain/opPools/utils.js +11 -1
- package/lib/chain/opPools/utils.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +5 -2
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +6 -2
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/validation/attesterSlashing.d.ts +4 -4
- package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
- package/lib/chain/validation/attesterSlashing.js +3 -0
- package/lib/chain/validation/attesterSlashing.js.map +1 -1
- package/lib/chain/validation/proposerSlashing.d.ts +3 -3
- package/lib/chain/validation/proposerSlashing.d.ts.map +1 -1
- package/lib/chain/validation/proposerSlashing.js +3 -0
- package/lib/chain/validation/proposerSlashing.js.map +1 -1
- package/lib/db/repositories/proposerSlashing.d.ts +1 -0
- package/lib/db/repositories/proposerSlashing.d.ts.map +1 -1
- package/lib/db/repositories/proposerSlashing.js +5 -1
- package/lib/db/repositories/proposerSlashing.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +7 -4
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +7 -3
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +7 -3
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +9 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +77 -10
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +50 -18
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +9 -5
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +88 -46
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/execution.d.ts +9 -11
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +5 -1
- package/lib/util/execution.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +7 -4
- package/src/api/impl/beacon/pool/index.ts +4 -4
- package/src/chain/archiveStore/archiveStore.ts +9 -3
- package/src/chain/archiveStore/utils/pruneHistory.ts +6 -4
- package/src/chain/blocks/importBlock.ts +4 -0
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
- package/src/chain/chain.ts +17 -19
- package/src/chain/errors/envelopeReconstructionError.ts +3 -0
- package/src/chain/errors/gossipValidation.ts +2 -0
- package/src/chain/initState.ts +8 -60
- package/src/chain/interface.ts +2 -2
- package/src/chain/opPools/opPool.ts +91 -18
- package/src/chain/opPools/utils.ts +31 -3
- package/src/chain/prepareNextSlot.ts +6 -3
- package/src/chain/produceBlock/produceBlockBody.ts +8 -1
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
- package/src/chain/validation/attesterSlashing.ts +7 -4
- package/src/chain/validation/proposerSlashing.ts +7 -4
- package/src/db/repositories/proposerSlashing.ts +6 -1
- package/src/index.ts +1 -1
- package/src/network/processor/gossipHandlers.ts +7 -4
- package/src/network/processor/gossipValidatorFn.ts +13 -3
- package/src/network/processor/index.ts +89 -10
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
- package/src/sync/range/range.ts +50 -18
- package/src/sync/unknownBlock.ts +99 -50
- package/src/util/execution.ts +16 -8
|
@@ -12,38 +12,54 @@ import {
|
|
|
12
12
|
} from "@lodestar/params";
|
|
13
13
|
import {
|
|
14
14
|
IBeaconStateView,
|
|
15
|
+
assertValidAttesterSlashing,
|
|
15
16
|
computeEpochAtSlot,
|
|
16
17
|
computeStartSlotAtEpoch,
|
|
17
18
|
getAttesterSlashableIndices,
|
|
19
|
+
getAttesterSlashingSignatureSets,
|
|
20
|
+
getProposerSlashingSignatureSets,
|
|
18
21
|
} from "@lodestar/state-transition";
|
|
19
22
|
import {
|
|
20
23
|
AttesterSlashing,
|
|
24
|
+
Domain,
|
|
21
25
|
Epoch,
|
|
22
26
|
SignedBeaconBlock,
|
|
27
|
+
Slot,
|
|
23
28
|
ValidatorIndex,
|
|
24
29
|
capella,
|
|
25
30
|
phase0,
|
|
31
|
+
ssz,
|
|
26
32
|
sszTypesFor,
|
|
27
33
|
} from "@lodestar/types";
|
|
28
|
-
import {fromHex, toHex, toRootHex} from "@lodestar/utils";
|
|
34
|
+
import {byteArrayEquals, fromHex, toHex, toRootHex} from "@lodestar/utils";
|
|
29
35
|
import {IBeaconDb} from "../../db/index.js";
|
|
30
36
|
import {Metrics} from "../../metrics/metrics.js";
|
|
31
37
|
import {SignedBLSToExecutionChangeVersioned} from "../../util/types.js";
|
|
38
|
+
import {IBlsVerifier} from "../bls/interface.js";
|
|
32
39
|
import {BlockType} from "../interface.js";
|
|
33
40
|
import {BlockProductionStep} from "../produceBlock/produceBlockBody.js";
|
|
34
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
getAttesterSlashingSignatureDomains,
|
|
43
|
+
getProposerSlashingSignatureDomain,
|
|
44
|
+
isValidBlsToExecutionChangeForBlockInclusion,
|
|
45
|
+
} from "./utils.js";
|
|
35
46
|
|
|
36
47
|
type HexRoot = string;
|
|
37
48
|
type AttesterSlashingCached = {
|
|
38
49
|
attesterSlashing: AttesterSlashing;
|
|
39
50
|
intersectingIndices: number[];
|
|
51
|
+
verifiedDomains: [Domain, Domain];
|
|
52
|
+
};
|
|
53
|
+
type ProposerSlashingCached = {
|
|
54
|
+
proposerSlashing: phase0.ProposerSlashing;
|
|
55
|
+
verifiedDomain: Domain;
|
|
40
56
|
};
|
|
41
57
|
|
|
42
58
|
export class OpPool {
|
|
43
59
|
/** Map of uniqueId(AttesterSlashing) -> AttesterSlashing */
|
|
44
60
|
private readonly attesterSlashings = new Map<HexRoot, AttesterSlashingCached>();
|
|
45
61
|
/** Map of to slash validator index -> ProposerSlashing */
|
|
46
|
-
private readonly proposerSlashings = new Map<ValidatorIndex,
|
|
62
|
+
private readonly proposerSlashings = new Map<ValidatorIndex, ProposerSlashingCached>();
|
|
47
63
|
/** Map of to exit validator index -> SignedVoluntaryExit */
|
|
48
64
|
private readonly voluntaryExits = new Map<ValidatorIndex, phase0.SignedVoluntaryExit>();
|
|
49
65
|
/** Set of seen attester slashing indexes. No need to prune */
|
|
@@ -68,7 +84,7 @@ export class OpPool {
|
|
|
68
84
|
return this.blsToExecutionChanges.size;
|
|
69
85
|
}
|
|
70
86
|
|
|
71
|
-
async fromPersisted(db: IBeaconDb): Promise<void> {
|
|
87
|
+
async fromPersisted(db: IBeaconDb, state: IBeaconStateView, bls: IBlsVerifier, currentSlot: Slot): Promise<void> {
|
|
72
88
|
const [attesterSlashings, proposerSlashings, voluntaryExits, blsToExecutionChanges] = await Promise.all([
|
|
73
89
|
db.attesterSlashing.entries(),
|
|
74
90
|
db.proposerSlashing.values(),
|
|
@@ -76,11 +92,42 @@ export class OpPool {
|
|
|
76
92
|
db.blsToExecutionChange.values(),
|
|
77
93
|
]);
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
// Persisted operations have no verification context. Rebuild it before they can enter the pool.
|
|
96
|
+
// The startup anchor may precede a fork whose slashings are already persisted.
|
|
97
|
+
for (const {key, value: attesterSlashing} of attesterSlashings) {
|
|
98
|
+
try {
|
|
99
|
+
assertValidAttesterSlashing(this.config, currentSlot, state.validatorCount, attesterSlashing, false);
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const verifiedDomains = getAttesterSlashingSignatureDomains(this.config, currentSlot, attesterSlashing);
|
|
104
|
+
if (
|
|
105
|
+
!(await bls.verifySignatureSets(getAttesterSlashingSignatureSets(this.config, currentSlot, attesterSlashing)))
|
|
106
|
+
) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
this.insertAttesterSlashing(ForkName.electra, attesterSlashing, verifiedDomains, key);
|
|
81
110
|
}
|
|
82
111
|
for (const proposerSlashing of proposerSlashings) {
|
|
83
|
-
|
|
112
|
+
const header1 = proposerSlashing.signedHeader1.message;
|
|
113
|
+
const header2 = proposerSlashing.signedHeader2.message;
|
|
114
|
+
// Startup may restore a state from before activation. Current slashability is checked
|
|
115
|
+
// when selecting operations, so it must not prevent restoring an otherwise valid slashing.
|
|
116
|
+
if (
|
|
117
|
+
header1.slot !== header2.slot ||
|
|
118
|
+
header1.proposerIndex !== header2.proposerIndex ||
|
|
119
|
+
header1.proposerIndex >= state.validatorCount ||
|
|
120
|
+
ssz.phase0.BeaconBlockHeaderBigint.equals(header1, header2)
|
|
121
|
+
) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const verifiedDomain = getProposerSlashingSignatureDomain(this.config, currentSlot, proposerSlashing);
|
|
125
|
+
if (
|
|
126
|
+
!(await bls.verifySignatureSets(getProposerSlashingSignatureSets(this.config, currentSlot, proposerSlashing)))
|
|
127
|
+
) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
this.insertProposerSlashing(proposerSlashing, verifiedDomain);
|
|
84
131
|
}
|
|
85
132
|
for (const voluntaryExit of voluntaryExits) {
|
|
86
133
|
this.insertVoluntaryExit(voluntaryExit);
|
|
@@ -102,8 +149,12 @@ export class OpPool {
|
|
|
102
149
|
),
|
|
103
150
|
persistDiff(
|
|
104
151
|
db.proposerSlashing,
|
|
105
|
-
Array.from(this.proposerSlashings.entries()).map(([key, value]) => ({key, value})),
|
|
106
|
-
(index) => index
|
|
152
|
+
Array.from(this.proposerSlashings.entries()).map(([key, value]) => ({key, value: value.proposerSlashing})),
|
|
153
|
+
(index) => index,
|
|
154
|
+
// A slashing invalidated by a fork may remain on disk after eviction from the pool.
|
|
155
|
+
// A new valid slashing for that validator has the same database key, so comparing
|
|
156
|
+
// keys alone would retain the invalid slashing and lose the replacement on restart.
|
|
157
|
+
{updateExisting: true}
|
|
107
158
|
),
|
|
108
159
|
persistDiff(
|
|
109
160
|
db.voluntaryExit,
|
|
@@ -143,7 +194,12 @@ export class OpPool {
|
|
|
143
194
|
}
|
|
144
195
|
|
|
145
196
|
/** Must be validated beforehand */
|
|
146
|
-
insertAttesterSlashing(
|
|
197
|
+
insertAttesterSlashing(
|
|
198
|
+
fork: ForkName,
|
|
199
|
+
attesterSlashing: AttesterSlashing,
|
|
200
|
+
verifiedDomains: [Domain, Domain],
|
|
201
|
+
rootHash?: Uint8Array
|
|
202
|
+
): void {
|
|
147
203
|
if (!rootHash) {
|
|
148
204
|
rootHash = sszTypesFor(fork).AttesterSlashing.hashTreeRoot(attesterSlashing);
|
|
149
205
|
}
|
|
@@ -153,6 +209,7 @@ export class OpPool {
|
|
|
153
209
|
this.attesterSlashings.set(toRootHex(rootHash), {
|
|
154
210
|
attesterSlashing,
|
|
155
211
|
intersectingIndices,
|
|
212
|
+
verifiedDomains,
|
|
156
213
|
});
|
|
157
214
|
for (const index of intersectingIndices) {
|
|
158
215
|
this.attesterSlashingIndexes.add(index);
|
|
@@ -160,8 +217,11 @@ export class OpPool {
|
|
|
160
217
|
}
|
|
161
218
|
|
|
162
219
|
/** Must be validated beforehand */
|
|
163
|
-
insertProposerSlashing(proposerSlashing: phase0.ProposerSlashing): void {
|
|
164
|
-
this.proposerSlashings.set(proposerSlashing.signedHeader1.message.proposerIndex,
|
|
220
|
+
insertProposerSlashing(proposerSlashing: phase0.ProposerSlashing, verifiedDomain: Domain): void {
|
|
221
|
+
this.proposerSlashings.set(proposerSlashing.signedHeader1.message.proposerIndex, {
|
|
222
|
+
proposerSlashing,
|
|
223
|
+
verifiedDomain,
|
|
224
|
+
});
|
|
165
225
|
}
|
|
166
226
|
|
|
167
227
|
/** Must be validated beforehand */
|
|
@@ -204,7 +264,14 @@ export class OpPool {
|
|
|
204
264
|
: metrics?.builderBlockProductionTimeSteps;
|
|
205
265
|
|
|
206
266
|
const endProposerSlashing = stepsMetrics?.startTimer();
|
|
207
|
-
for (const proposerSlashing of this.proposerSlashings
|
|
267
|
+
for (const [key, {proposerSlashing, verifiedDomain}] of this.proposerSlashings) {
|
|
268
|
+
// A fork can change the required domain even for verified slashings, including those with future-slot headers.
|
|
269
|
+
if (
|
|
270
|
+
!byteArrayEquals(verifiedDomain, getProposerSlashingSignatureDomain(this.config, state.slot, proposerSlashing))
|
|
271
|
+
) {
|
|
272
|
+
this.proposerSlashings.delete(key);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
208
275
|
const index = proposerSlashing.signedHeader1.message.proposerIndex;
|
|
209
276
|
const validator = state.getValidator(index);
|
|
210
277
|
if (!validator.slashed && validator.activationEpoch <= stateEpoch && stateEpoch < validator.withdrawableEpoch) {
|
|
@@ -223,7 +290,12 @@ export class OpPool {
|
|
|
223
290
|
const endAttesterSlashings = stepsMetrics?.startTimer();
|
|
224
291
|
const attesterSlashings: AttesterSlashing[] = [];
|
|
225
292
|
const maxAttesterSlashings = stateFork >= ForkSeq.electra ? MAX_ATTESTER_SLASHINGS_ELECTRA : MAX_ATTESTER_SLASHINGS;
|
|
226
|
-
attesterSlashing: for (const attesterSlashing of this.attesterSlashings
|
|
293
|
+
attesterSlashing: for (const [key, attesterSlashing] of this.attesterSlashings) {
|
|
294
|
+
const domains = getAttesterSlashingSignatureDomains(this.config, state.slot, attesterSlashing.attesterSlashing);
|
|
295
|
+
if (!attesterSlashing.verifiedDomains.every((domain, i) => byteArrayEquals(domain, domains[i]))) {
|
|
296
|
+
this.attesterSlashings.delete(key);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
227
299
|
/** Indices slashable in this attester slashing */
|
|
228
300
|
const slashableIndices = new Set<ValidatorIndex>();
|
|
229
301
|
for (let i = 0; i < attesterSlashing.intersectingIndices.length; i++) {
|
|
@@ -303,7 +375,7 @@ export class OpPool {
|
|
|
303
375
|
|
|
304
376
|
/** For beacon pool API */
|
|
305
377
|
getAllProposerSlashings(): phase0.ProposerSlashing[] {
|
|
306
|
-
return Array.from(this.proposerSlashings.values());
|
|
378
|
+
return Array.from(this.proposerSlashings.values()).map(({proposerSlashing}) => proposerSlashing);
|
|
307
379
|
}
|
|
308
380
|
|
|
309
381
|
/** For beacon pool API */
|
|
@@ -356,7 +428,7 @@ export class OpPool {
|
|
|
356
428
|
*/
|
|
357
429
|
private pruneProposerSlashings(headState: IBeaconStateView): void {
|
|
358
430
|
const finalizedEpoch = headState.finalizedCheckpoint.epoch;
|
|
359
|
-
for (const [key, proposerSlashing] of this.proposerSlashings.entries()) {
|
|
431
|
+
for (const [key, {proposerSlashing}] of this.proposerSlashings.entries()) {
|
|
360
432
|
const index = proposerSlashing.signedHeader1.message.proposerIndex;
|
|
361
433
|
if (headState.getValidator(index).exitEpoch <= finalizedEpoch) {
|
|
362
434
|
this.proposerSlashings.delete(key);
|
|
@@ -436,14 +508,15 @@ function isSlashableAtEpoch(validator: phase0.Validator, epoch: Epoch): boolean
|
|
|
436
508
|
async function persistDiff<K extends Id, V>(
|
|
437
509
|
dbRepo: Repository<K, V>,
|
|
438
510
|
items: {key: K; value: V}[],
|
|
439
|
-
serializeKey: (key: K) => number | string
|
|
511
|
+
serializeKey: (key: K) => number | string,
|
|
512
|
+
opts: {updateExisting?: boolean} = {}
|
|
440
513
|
): Promise<void> {
|
|
441
514
|
const persistedKeys = await dbRepo.keys();
|
|
442
515
|
const batch: DbBatch<K, V> = [];
|
|
443
516
|
|
|
444
517
|
const persistedKeysSerialized = new Set(persistedKeys.map(serializeKey));
|
|
445
518
|
for (const item of items) {
|
|
446
|
-
if (!persistedKeysSerialized.has(serializeKey(item.key))) {
|
|
519
|
+
if (opts.updateExisting || !persistedKeysSerialized.has(serializeKey(item.key))) {
|
|
447
520
|
batch.push({type: "put", key: item.key, value: item.value});
|
|
448
521
|
}
|
|
449
522
|
}
|
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
import {Signature} from "@chainsafe/lodestar-z/blst";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
+
import {BLS_WITHDRAWAL_PREFIX, DOMAIN_BEACON_ATTESTER, DOMAIN_BEACON_PROPOSER} from "@lodestar/params";
|
|
4
|
+
import {IBeaconStateView, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
5
|
+
import {AttesterSlashing, Domain, Slot, capella, phase0} from "@lodestar/types";
|
|
5
6
|
import {AggregateFast, AggregateFastElectra} from "./attestationPool.js";
|
|
6
7
|
|
|
8
|
+
export function getProposerSlashingSignatureDomain(
|
|
9
|
+
config: BeaconConfig,
|
|
10
|
+
stateSlot: Slot,
|
|
11
|
+
slashing: phase0.ProposerSlashing
|
|
12
|
+
): Domain {
|
|
13
|
+
return config.getDomain(stateSlot, DOMAIN_BEACON_PROPOSER, Number(slashing.signedHeader1.message.slot));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getAttesterSlashingSignatureDomains(
|
|
17
|
+
config: BeaconConfig,
|
|
18
|
+
stateSlot: Slot,
|
|
19
|
+
slashing: AttesterSlashing
|
|
20
|
+
): [Domain, Domain] {
|
|
21
|
+
return [
|
|
22
|
+
config.getDomain(
|
|
23
|
+
stateSlot,
|
|
24
|
+
DOMAIN_BEACON_ATTESTER,
|
|
25
|
+
computeStartSlotAtEpoch(Number(slashing.attestation1.data.target.epoch))
|
|
26
|
+
),
|
|
27
|
+
config.getDomain(
|
|
28
|
+
stateSlot,
|
|
29
|
+
DOMAIN_BEACON_ATTESTER,
|
|
30
|
+
computeStartSlotAtEpoch(Number(slashing.attestation2.data.target.epoch))
|
|
31
|
+
),
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
7
35
|
/**
|
|
8
36
|
* Prune a Map indexed by slot to keep the most recent slots, up to `slotsRetained`
|
|
9
37
|
*/
|
|
@@ -244,6 +244,10 @@ export class PrepareNextSlotScheduler {
|
|
|
244
244
|
parentBlockHash = preparedState.latestExecutionPayloadHeader.blockHash;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
// The payload_attributes event must carry the same hashes we send to the EL
|
|
248
|
+
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
|
|
249
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice);
|
|
250
|
+
|
|
247
251
|
let payloadAttributes: PayloadAttributes | undefined;
|
|
248
252
|
// If emitPayloadAttributes is true emit a SSE payloadAttributes event for
|
|
249
253
|
// every slot. Without the flag, only emit the event if we are proposing in the next slot.
|
|
@@ -256,6 +260,8 @@ export class PrepareNextSlotScheduler {
|
|
|
256
260
|
prepareSlot,
|
|
257
261
|
parentBlockRoot: fromHex(updatedHead.blockRoot),
|
|
258
262
|
parentBlockHash,
|
|
263
|
+
safeBlockHash,
|
|
264
|
+
finalizedBlockHash,
|
|
259
265
|
feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
|
|
260
266
|
});
|
|
261
267
|
this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
|
|
@@ -267,9 +273,6 @@ export class PrepareNextSlotScheduler {
|
|
|
267
273
|
computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
|
|
268
274
|
this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);
|
|
269
275
|
|
|
270
|
-
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
|
|
271
|
-
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice);
|
|
272
|
-
|
|
273
276
|
// awaiting here instead of throwing an async call because there is no other task
|
|
274
277
|
// left for scheduler and this gives nice semantics to catch and log errors in the
|
|
275
278
|
// try/catch wrapper here.
|
|
@@ -845,6 +845,8 @@ export function getPayloadAttributesForSSE(
|
|
|
845
845
|
prepareSlot,
|
|
846
846
|
parentBlockRoot,
|
|
847
847
|
parentBlockHash,
|
|
848
|
+
safeBlockHash,
|
|
849
|
+
finalizedBlockHash,
|
|
848
850
|
feeRecipient,
|
|
849
851
|
}: {
|
|
850
852
|
/**
|
|
@@ -855,6 +857,8 @@ export function getPayloadAttributesForSSE(
|
|
|
855
857
|
prepareSlot: Slot;
|
|
856
858
|
parentBlockRoot: Root;
|
|
857
859
|
parentBlockHash: Bytes32;
|
|
860
|
+
safeBlockHash: RootHex;
|
|
861
|
+
finalizedBlockHash: RootHex;
|
|
858
862
|
feeRecipient: string;
|
|
859
863
|
}
|
|
860
864
|
): SSEPayloadAttributes {
|
|
@@ -874,7 +878,10 @@ export function getPayloadAttributesForSSE(
|
|
|
874
878
|
payloadAttributes,
|
|
875
879
|
} as SSEPayloadAttributes;
|
|
876
880
|
|
|
877
|
-
if (
|
|
881
|
+
if (isForkPostGloas(fork)) {
|
|
882
|
+
(ssePayloadAttributes as gloas.SSEPayloadAttributes).safeBlockHash = fromHex(safeBlockHash);
|
|
883
|
+
(ssePayloadAttributes as gloas.SSEPayloadAttributes).finalizedBlockHash = fromHex(finalizedBlockHash);
|
|
884
|
+
} else {
|
|
878
885
|
// Removed in Gloas, builders can get the block number from the EL via the block hash if required
|
|
879
886
|
(ssePayloadAttributes as bellatrix.SSEPayloadAttributes).parentBlockNumber = prepareState.payloadBlockNumber;
|
|
880
887
|
}
|
|
@@ -47,8 +47,9 @@ export type SeenPayloadEnvelopeInputModules = {
|
|
|
47
47
|
* Created whenever we have a block because it needs block bid.
|
|
48
48
|
* Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
|
|
49
49
|
* (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* hold more during forks and range sync, or while an entry's payload is not FULL on the head's
|
|
51
|
+
* branch or its columns are still being gathered. Such entries are evicted on a later block
|
|
52
|
+
* import, on finalization or by the size cap.
|
|
52
53
|
*/
|
|
53
54
|
export class SeenPayloadEnvelopeInput {
|
|
54
55
|
private readonly config: ChainForkConfig;
|
|
@@ -264,18 +265,35 @@ export class SeenPayloadEnvelopeInput {
|
|
|
264
265
|
}
|
|
265
266
|
}
|
|
266
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Removes a PayloadEnvelopeInput whose envelope failed import with a definitive invalid verdict. The envelope
|
|
270
|
+
* cannot be replaced in place, sync recreates the entry via `getOrReload()` or `add()` to fetch another one.
|
|
271
|
+
* No-op if the cached entry is a different object, it may already be the recreated one.
|
|
272
|
+
*/
|
|
273
|
+
removeInvalid(payloadInput: PayloadEnvelopeInput): void {
|
|
274
|
+
if (this.payloadInputs.get(payloadInput.blockRootHex) === payloadInput) {
|
|
275
|
+
this.evictPayloadInput(payloadInput, "invalid");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
267
279
|
pruneBelowParent(parentBlock: ProtoBlock): void {
|
|
268
|
-
for (const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
for (const input of this.payloadInputs.values()) {
|
|
281
|
+
if (
|
|
282
|
+
input.slot < parentBlock.slot &&
|
|
283
|
+
// Don't evict while columns are still being gathered: writeDataColumnsToDb awaits the same
|
|
284
|
+
// hasComputedAllData() before persisting. Such entries are pruned by a later call.
|
|
285
|
+
input.hasComputedAllData() &&
|
|
286
|
+
// Only evict once the payload is FULL (revealed/imported) on the parent's branch. On an EMPTY/PENDING
|
|
287
|
+
// branch we may still need to download the FULL envelope (see #9475), and without the entry gossip
|
|
288
|
+
// for the block is ignored until by-root sync reloads it.
|
|
289
|
+
this.forkChoice.isDescendant(
|
|
290
|
+
input.blockRootHex,
|
|
291
|
+
PayloadStatus.FULL,
|
|
292
|
+
parentBlock.blockRoot,
|
|
293
|
+
parentBlock.payloadStatus
|
|
294
|
+
)
|
|
295
|
+
) {
|
|
296
|
+
this.evictPayloadInput(input, "belowParent");
|
|
279
297
|
}
|
|
280
298
|
}
|
|
281
299
|
}
|
|
@@ -4,14 +4,15 @@ import {
|
|
|
4
4
|
getAttesterSlashingSignatureSets,
|
|
5
5
|
isSlashableValidator,
|
|
6
6
|
} from "@lodestar/state-transition";
|
|
7
|
-
import {AttesterSlashing} from "@lodestar/types";
|
|
7
|
+
import {AttesterSlashing, Domain} from "@lodestar/types";
|
|
8
8
|
import {AttesterSlashingError, AttesterSlashingErrorCode, GossipAction} from "../errors/index.js";
|
|
9
9
|
import {IBeaconChain} from "../index.js";
|
|
10
|
+
import {getAttesterSlashingSignatureDomains} from "../opPools/utils.js";
|
|
10
11
|
|
|
11
12
|
export async function validateApiAttesterSlashing(
|
|
12
13
|
chain: IBeaconChain,
|
|
13
14
|
attesterSlashing: AttesterSlashing
|
|
14
|
-
): Promise<
|
|
15
|
+
): Promise<[Domain, Domain]> {
|
|
15
16
|
const prioritizeBls = true;
|
|
16
17
|
return validateAttesterSlashing(chain, attesterSlashing, prioritizeBls);
|
|
17
18
|
}
|
|
@@ -19,7 +20,7 @@ export async function validateApiAttesterSlashing(
|
|
|
19
20
|
export async function validateGossipAttesterSlashing(
|
|
20
21
|
chain: IBeaconChain,
|
|
21
22
|
attesterSlashing: AttesterSlashing
|
|
22
|
-
): Promise<
|
|
23
|
+
): Promise<[Domain, Domain]> {
|
|
23
24
|
return validateAttesterSlashing(chain, attesterSlashing);
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -27,7 +28,7 @@ export async function validateAttesterSlashing(
|
|
|
27
28
|
chain: IBeaconChain,
|
|
28
29
|
attesterSlashing: AttesterSlashing,
|
|
29
30
|
prioritizeBls = false
|
|
30
|
-
): Promise<
|
|
31
|
+
): Promise<[Domain, Domain]> {
|
|
31
32
|
// [IGNORE] At least one index in the intersection of the attesting indices of each attestation has not yet been seen
|
|
32
33
|
// in any prior attester_slashing (i.e.
|
|
33
34
|
// attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices
|
|
@@ -61,9 +62,11 @@ export async function validateAttesterSlashing(
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
const signatureSets = getAttesterSlashingSignatureSets(chain.config, state.slot, attesterSlashing);
|
|
65
|
+
const verifiedDomains = getAttesterSlashingSignatureDomains(chain.config, state.slot, attesterSlashing);
|
|
64
66
|
if (!(await chain.bls.verifySignatureSets(signatureSets, {batchable: true, priority: prioritizeBls}))) {
|
|
65
67
|
throw new AttesterSlashingError(GossipAction.REJECT, {
|
|
66
68
|
code: AttesterSlashingErrorCode.INVALID_SIGNATURE,
|
|
67
69
|
});
|
|
68
70
|
}
|
|
71
|
+
return verifiedDomains;
|
|
69
72
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {assertValidProposerSlashing, getProposerSlashingSignatureSets} from "@lodestar/state-transition";
|
|
2
|
-
import {phase0} from "@lodestar/types";
|
|
2
|
+
import {Domain, phase0} from "@lodestar/types";
|
|
3
3
|
import {GossipAction, ProposerSlashingError, ProposerSlashingErrorCode} from "../errors/index.js";
|
|
4
4
|
import {IBeaconChain} from "../index.js";
|
|
5
|
+
import {getProposerSlashingSignatureDomain} from "../opPools/utils.js";
|
|
5
6
|
|
|
6
7
|
export async function validateApiProposerSlashing(
|
|
7
8
|
chain: IBeaconChain,
|
|
8
9
|
proposerSlashing: phase0.ProposerSlashing
|
|
9
|
-
): Promise<
|
|
10
|
+
): Promise<Domain> {
|
|
10
11
|
const prioritizeBls = true;
|
|
11
12
|
return validateProposerSlashing(chain, proposerSlashing, prioritizeBls);
|
|
12
13
|
}
|
|
@@ -14,7 +15,7 @@ export async function validateApiProposerSlashing(
|
|
|
14
15
|
export async function validateGossipProposerSlashing(
|
|
15
16
|
chain: IBeaconChain,
|
|
16
17
|
proposerSlashing: phase0.ProposerSlashing
|
|
17
|
-
): Promise<
|
|
18
|
+
): Promise<Domain> {
|
|
18
19
|
return validateProposerSlashing(chain, proposerSlashing);
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -22,7 +23,7 @@ async function validateProposerSlashing(
|
|
|
22
23
|
chain: IBeaconChain,
|
|
23
24
|
proposerSlashing: phase0.ProposerSlashing,
|
|
24
25
|
prioritizeBls = false
|
|
25
|
-
): Promise<
|
|
26
|
+
): Promise<Domain> {
|
|
26
27
|
// [IGNORE] The proposer slashing is the first valid proposer slashing received for the proposer with index
|
|
27
28
|
// proposer_slashing.signed_header_1.message.proposer_index.
|
|
28
29
|
if (chain.opPool.hasSeenProposerSlashing(proposerSlashing.signedHeader1.message.proposerIndex)) {
|
|
@@ -46,9 +47,11 @@ async function validateProposerSlashing(
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
const signatureSets = getProposerSlashingSignatureSets(chain.config, state.slot, proposerSlashing);
|
|
50
|
+
const verifiedDomain = getProposerSlashingSignatureDomain(chain.config, state.slot, proposerSlashing);
|
|
49
51
|
if (!(await chain.bls.verifySignatureSets(signatureSets, {batchable: true, priority: prioritizeBls}))) {
|
|
50
52
|
throw new ProposerSlashingError(GossipAction.REJECT, {
|
|
51
53
|
code: ProposerSlashingErrorCode.INVALID_SIGNATURE,
|
|
52
54
|
});
|
|
53
55
|
}
|
|
56
|
+
return verifiedDomain;
|
|
54
57
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {Db, Repository} from "@lodestar/db";
|
|
2
|
+
import {BUCKET_LENGTH, Db, Repository} from "@lodestar/db";
|
|
3
3
|
import {ValidatorIndex, phase0, ssz} from "@lodestar/types";
|
|
4
|
+
import {bytesToInt} from "@lodestar/utils";
|
|
4
5
|
import {Bucket, getBucketNameByValue} from "../buckets.js";
|
|
5
6
|
|
|
6
7
|
export class ProposerSlashingRepository extends Repository<ValidatorIndex, phase0.ProposerSlashing> {
|
|
@@ -12,4 +13,8 @@ export class ProposerSlashingRepository extends Repository<ValidatorIndex, phase
|
|
|
12
13
|
getId(value: phase0.ProposerSlashing): ValidatorIndex {
|
|
13
14
|
return value.signedHeader1.message.proposerIndex;
|
|
14
15
|
}
|
|
16
|
+
|
|
17
|
+
decodeKey(data: Uint8Array): ValidatorIndex {
|
|
18
|
+
return bytesToInt(data.subarray(BUCKET_LENGTH), "be");
|
|
19
|
+
}
|
|
15
20
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
export type {RestApiServerMetrics, RestApiServerModules, RestApiServerOpts} from "./api/rest/base.js";
|
|
4
4
|
export {RestApiServer} from "./api/rest/base.js";
|
|
5
|
-
export {
|
|
5
|
+
export {persistAnchorState} from "./chain/index.js";
|
|
6
6
|
export {DbCPStateDatastore} from "./chain/stateCache/datastore/db.js";
|
|
7
7
|
export {FileCPStateDatastore} from "./chain/stateCache/datastore/file.js";
|
|
8
8
|
export {BeaconDb, type IBeaconDb} from "./db/index.js";
|
|
@@ -1065,12 +1065,12 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1065
1065
|
const {serializedData} = gossipData;
|
|
1066
1066
|
const {fork} = topic.boundary;
|
|
1067
1067
|
const attesterSlashing = sszDeserialize(topic, serializedData);
|
|
1068
|
-
await validateGossipAttesterSlashing(chain, attesterSlashing);
|
|
1068
|
+
const verifiedDomains = await validateGossipAttesterSlashing(chain, attesterSlashing);
|
|
1069
1069
|
|
|
1070
1070
|
// Handler - deferred to next event loop so the validation result propagates first
|
|
1071
1071
|
callInNextEventLoop(() => {
|
|
1072
1072
|
try {
|
|
1073
|
-
chain.opPool.insertAttesterSlashing(fork, attesterSlashing);
|
|
1073
|
+
chain.opPool.insertAttesterSlashing(fork, attesterSlashing, verifiedDomains);
|
|
1074
1074
|
chain.forkChoice.onAttesterSlashing(attesterSlashing);
|
|
1075
1075
|
chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
|
|
1076
1076
|
} catch (e) {
|
|
@@ -1089,12 +1089,12 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1089
1089
|
}: GossipHandlerParamGeneric<GossipType.proposer_slashing>) => {
|
|
1090
1090
|
const {serializedData} = gossipData;
|
|
1091
1091
|
const proposerSlashing = sszDeserialize(topic, serializedData);
|
|
1092
|
-
await validateGossipProposerSlashing(chain, proposerSlashing);
|
|
1092
|
+
const verifiedDomain = await validateGossipProposerSlashing(chain, proposerSlashing);
|
|
1093
1093
|
|
|
1094
1094
|
// Handler - deferred to next event loop so the validation result propagates first
|
|
1095
1095
|
callInNextEventLoop(() => {
|
|
1096
1096
|
try {
|
|
1097
|
-
chain.opPool.insertProposerSlashing(proposerSlashing);
|
|
1097
|
+
chain.opPool.insertProposerSlashing(proposerSlashing, verifiedDomain);
|
|
1098
1098
|
chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
|
|
1099
1099
|
} catch (e) {
|
|
1100
1100
|
logger.debug(
|
|
@@ -1341,6 +1341,9 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1341
1341
|
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1342
1342
|
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1343
1343
|
core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
|
|
1344
|
+
// The builder may have signed another envelope that is valid, keeping this one would make
|
|
1345
|
+
// by-root and range sync reuse it and never import the payload
|
|
1346
|
+
chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
|
|
1344
1347
|
// Misbehaving peer, but could highlight an issue in another client
|
|
1345
1348
|
logLevel = LogLevel.warn;
|
|
1346
1349
|
break;
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
VoluntaryExitErrorCode,
|
|
21
21
|
} from "../../chain/errors/index.js";
|
|
22
22
|
import {Metrics} from "../../metrics/index.js";
|
|
23
|
+
import {PeerIdStr} from "../../util/peerId.js";
|
|
23
24
|
import {INetworkCore} from "../core/index.js";
|
|
24
25
|
import {
|
|
25
26
|
BatchGossipHandlerFn,
|
|
@@ -40,6 +41,8 @@ export type ValidatorFnModules = {
|
|
|
40
41
|
core: INetworkCore;
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
export type OnFatalPeerFn = (peer: PeerIdStr) => void;
|
|
45
|
+
|
|
43
46
|
type RejectPeerActionRule = {default: PeerAction; byCode?: Record<string, PeerAction>};
|
|
44
47
|
|
|
45
48
|
/**
|
|
@@ -112,7 +115,7 @@ const gossipRejectPeerAction: Record<GossipType, RejectPeerActionRule> = {
|
|
|
112
115
|
[GossipType.light_client_optimistic_update]: {default: PeerAction.HighToleranceError},
|
|
113
116
|
};
|
|
114
117
|
|
|
115
|
-
function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
118
|
+
export function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
116
119
|
const rule = gossipRejectPeerAction[type];
|
|
117
120
|
return rule.byCode?.[code] ?? rule.default;
|
|
118
121
|
}
|
|
@@ -123,7 +126,8 @@ function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
|
123
126
|
*/
|
|
124
127
|
export function getGossipValidatorBatchFn(
|
|
125
128
|
gossipHandlers: GossipHandlers,
|
|
126
|
-
modules: ValidatorFnModules
|
|
129
|
+
modules: ValidatorFnModules,
|
|
130
|
+
onFatalPeer: OnFatalPeerFn
|
|
127
131
|
): GossipValidatorBatchFn {
|
|
128
132
|
const {logger, metrics, core} = modules;
|
|
129
133
|
|
|
@@ -173,6 +177,7 @@ export function getGossipValidatorBatchFn(
|
|
|
173
177
|
metrics?.networkProcessor.gossipAttestationRejectByReason.inc({reason: e.type.code});
|
|
174
178
|
const peerAction = rejectPeerAction(type, e.type.code);
|
|
175
179
|
core.reportPeer(propagationSource, peerAction, e.type.code);
|
|
180
|
+
if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
|
|
176
181
|
logger.debug(
|
|
177
182
|
`Gossip validation ${type} rejected`,
|
|
178
183
|
{peer: propagationSource, clientAgent, clientVersion, peerAction},
|
|
@@ -208,7 +213,11 @@ export function getGossipValidatorBatchFn(
|
|
|
208
213
|
*
|
|
209
214
|
* @see getGossipHandlers for reasoning on why GossipHandlerFn are used for gossip validation.
|
|
210
215
|
*/
|
|
211
|
-
export function getGossipValidatorFn(
|
|
216
|
+
export function getGossipValidatorFn(
|
|
217
|
+
gossipHandlers: GossipHandlers,
|
|
218
|
+
modules: ValidatorFnModules,
|
|
219
|
+
onFatalPeer: OnFatalPeerFn
|
|
220
|
+
): GossipValidatorFn {
|
|
212
221
|
const {logger, metrics, core} = modules;
|
|
213
222
|
|
|
214
223
|
return async function gossipValidatorFn({
|
|
@@ -260,6 +269,7 @@ export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: Va
|
|
|
260
269
|
metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
|
|
261
270
|
const peerAction = rejectPeerAction(type, e.type.code);
|
|
262
271
|
core.reportPeer(propagationSource, peerAction, e.type.code);
|
|
272
|
+
if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
|
|
263
273
|
logger.debug(
|
|
264
274
|
`Gossip validation ${type} rejected`,
|
|
265
275
|
{peer: propagationSource, clientAgent, clientVersion, peerAction},
|