@lodestar/beacon-node 1.50.0-dev.f2d839a25c → 1.50.0-dev.fc618d94e9
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/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js +13 -0
- package/lib/api/impl/beacon/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/api/impl/validator/index.d.ts +1 -0
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +17 -1
- package/lib/api/impl/validator/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 +7 -3
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
- package/lib/chain/archiveStore/interface.d.ts +1 -1
- package/lib/chain/archiveStore/interface.d.ts.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/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +1 -1
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
- package/lib/chain/chain.d.ts +6 -4
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +43 -16
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +7 -1
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +5 -0
- package/lib/chain/emitter.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/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -0
- package/lib/chain/forkChoice/index.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/proposerPreferencesPool.d.ts +4 -4
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +14 -11
- package/lib/chain/opPools/proposerPreferencesPool.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/options.d.ts +1 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -1
- package/lib/chain/options.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/computeNewStateRoot.js +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.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/regen/regen.d.ts +2 -0
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +10 -4
- package/lib/chain/regen/regen.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/chain/validatorMonitor.d.ts +5 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +64 -54
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +2 -0
- package/lib/db/beacon.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/execution/engine/disabled.d.ts +0 -2
- package/lib/execution/engine/disabled.d.ts.map +1 -1
- package/lib/execution/engine/disabled.js +0 -6
- package/lib/execution/engine/disabled.js.map +1 -1
- package/lib/execution/engine/http.d.ts +1 -3
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +2 -17
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +2 -4
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/mock.d.ts +0 -2
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +0 -8
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/types.d.ts +0 -8
- package/lib/execution/engine/types.d.ts.map +1 -1
- package/lib/execution/engine/types.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/metrics/metrics.d.ts +7 -3
- package/lib/metrics/metrics.d.ts.map +1 -1
- package/lib/metrics/metrics.js +4 -4
- package/lib/metrics/metrics.js.map +1 -1
- package/lib/network/metadata.d.ts +1 -1
- package/lib/network/metadata.d.ts.map +1 -1
- package/lib/network/metadata.js +17 -17
- package/lib/network/metadata.js.map +1 -1
- package/lib/network/peers/datastore.d.ts +9 -9
- package/lib/network/peers/datastore.d.ts.map +1 -1
- package/lib/network/peers/datastore.js +36 -36
- package/lib/network/peers/datastore.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.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +41 -26
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +25 -3
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/batch.d.ts +6 -0
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +10 -0
- 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 +5 -1
- package/lib/sync/range/chain.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 +11 -11
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +6 -2
- package/lib/util/execution.js.map +1 -1
- package/package.json +16 -16
- package/src/api/impl/beacon/blocks/index.ts +7 -4
- package/src/api/impl/beacon/index.ts +15 -0
- package/src/api/impl/beacon/pool/index.ts +4 -4
- package/src/api/impl/validator/index.ts +16 -2
- package/src/chain/archiveStore/archiveStore.ts +10 -4
- package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
- package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
- package/src/chain/archiveStore/historicalState/types.ts +2 -2
- package/src/chain/archiveStore/historicalState/worker.ts +1 -2
- package/src/chain/archiveStore/interface.ts +1 -1
- 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/blocks/verifyBlock.ts +1 -0
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
- package/src/chain/chain.ts +50 -19
- package/src/chain/emitter.ts +7 -1
- package/src/chain/errors/envelopeReconstructionError.ts +3 -0
- package/src/chain/errors/gossipValidation.ts +2 -0
- package/src/chain/forkChoice/index.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/proposerPreferencesPool.ts +14 -10
- package/src/chain/opPools/utils.ts +31 -3
- package/src/chain/options.ts +2 -2
- package/src/chain/prepareNextSlot.ts +6 -3
- package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
- package/src/chain/produceBlock/produceBlockBody.ts +8 -1
- package/src/chain/regen/regen.ts +12 -4
- 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/chain/validatorMonitor.ts +75 -57
- package/src/db/beacon.ts +2 -0
- package/src/db/repositories/proposerSlashing.ts +6 -1
- package/src/execution/engine/disabled.ts +0 -8
- package/src/execution/engine/http.ts +1 -30
- package/src/execution/engine/interface.ts +2 -6
- package/src/execution/engine/mock.ts +1 -19
- package/src/execution/engine/types.ts +0 -10
- package/src/index.ts +1 -1
- package/src/metrics/metrics.ts +21 -6
- package/src/network/metadata.ts +17 -17
- package/src/network/peers/datastore.ts +36 -36
- 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 +42 -28
- package/src/node/nodejs.ts +31 -3
- package/src/sync/range/batch.ts +11 -0
- package/src/sync/range/chain.ts +6 -1
- package/src/sync/range/range.ts +50 -18
- package/src/sync/unknownBlock.ts +99 -50
- package/src/util/execution.ts +17 -9
|
@@ -16,23 +16,23 @@ type AwaitGenerator<T, TReturn = any, TNext = any> = Generator<T, TReturn, TNext
|
|
|
16
16
|
* This has a memory issue because all peer data stays in memory and loaded at startup time
|
|
17
17
|
* This is written for libp2p >=0.35, we maintain the same mechanism but with bounded data structure
|
|
18
18
|
* This datastore includes a memory datastore and fallback to db datastore
|
|
19
|
-
* Use an in-memory datastore with last accessed time and
|
|
19
|
+
* Use an in-memory datastore with last accessed time and maxMemoryItems, on start it's empty (lazy load)
|
|
20
20
|
* - get: Search in-memory datastore first, if not found search from db.
|
|
21
21
|
* - If found from db, add back to the in-memory datastore
|
|
22
22
|
* - Update lastAccessedMs
|
|
23
|
-
* - put: move oldest items from memory to db if there are more than
|
|
24
|
-
* - update memory datastore, only update db datastore if there are at least
|
|
23
|
+
* - put: move oldest items from memory to db if there are more than maxMemoryItems items in memory
|
|
24
|
+
* - update memory datastore, only update db datastore if there are at least threshold dirty items
|
|
25
25
|
* - Update lastAccessedMs
|
|
26
26
|
*/
|
|
27
27
|
export class Eth2PeerDataStore extends BaseDatastore {
|
|
28
|
-
private
|
|
29
|
-
private
|
|
28
|
+
private dbDatastore: LevelDatastore;
|
|
29
|
+
private memoryDatastore: Map<string, MemoryItem>;
|
|
30
30
|
/** Same to PersistentPeerStore of the old libp2p implementation */
|
|
31
|
-
private
|
|
31
|
+
private dirtyItems = new Set<string>();
|
|
32
32
|
/** If there are more dirty items than threshold, commit data to db */
|
|
33
|
-
private
|
|
33
|
+
private threshold: number;
|
|
34
34
|
/** If there are more memory items than this, prune oldest ones from memory and move to db */
|
|
35
|
-
private
|
|
35
|
+
private maxMemoryItems: number;
|
|
36
36
|
|
|
37
37
|
constructor(
|
|
38
38
|
dbDatastore: LevelDatastore | string,
|
|
@@ -47,18 +47,18 @@ export class Eth2PeerDataStore extends BaseDatastore {
|
|
|
47
47
|
throw Error(`Threshold ${threshold} should be at most maxMemoryItems ${maxMemoryItems}`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
this.
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
50
|
+
this.dbDatastore = typeof dbDatastore === "string" ? new LevelDatastore(dbDatastore) : dbDatastore;
|
|
51
|
+
this.memoryDatastore = new Map();
|
|
52
|
+
this.threshold = threshold;
|
|
53
|
+
this.maxMemoryItems = maxMemoryItems;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async open(): Promise<void> {
|
|
57
|
-
return this.
|
|
57
|
+
return this.dbDatastore.open();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
async close(): Promise<void> {
|
|
61
|
-
return this.
|
|
61
|
+
return this.dbDatastore.close();
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
async put(key: Key, val: Uint8Array, _options?: AbortOptions): Promise<Key> {
|
|
@@ -67,23 +67,23 @@ export class Eth2PeerDataStore extends BaseDatastore {
|
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Same interface to put with "fromDb" option, if this item is updated back from db
|
|
70
|
-
* Move oldest items from memory data store to db if it's over this.
|
|
70
|
+
* Move oldest items from memory data store to db if it's over this.maxMemoryItems
|
|
71
71
|
*/
|
|
72
72
|
async _put(key: Key, val: Uint8Array, fromDb = false): Promise<Key> {
|
|
73
|
-
while (this.
|
|
73
|
+
while (this.memoryDatastore.size >= this.maxMemoryItems) {
|
|
74
74
|
// it's likely this is called only 1 time
|
|
75
75
|
await this.pruneMemoryDatastore();
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
const keyStr = key.toString();
|
|
79
|
-
const memoryItem = this.
|
|
79
|
+
const memoryItem = this.memoryDatastore.get(keyStr);
|
|
80
80
|
if (memoryItem) {
|
|
81
81
|
// update existing
|
|
82
82
|
memoryItem.lastAccessedMs = Date.now();
|
|
83
83
|
memoryItem.data = val;
|
|
84
84
|
} else {
|
|
85
85
|
// new
|
|
86
|
-
this.
|
|
86
|
+
this.memoryDatastore.set(keyStr, {data: val, lastAccessedMs: Date.now()});
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (!fromDb) await this._addDirtyItem(keyStr);
|
|
@@ -98,15 +98,15 @@ export class Eth2PeerDataStore extends BaseDatastore {
|
|
|
98
98
|
*/
|
|
99
99
|
async get(key: Key, options?: AbortOptions): Promise<Uint8Array> {
|
|
100
100
|
const keyStr = key.toString();
|
|
101
|
-
const memoryItem = this.
|
|
101
|
+
const memoryItem = this.memoryDatastore.get(keyStr);
|
|
102
102
|
if (memoryItem) {
|
|
103
103
|
memoryItem.lastAccessedMs = Date.now();
|
|
104
104
|
return memoryItem.data;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// this throws error if not found
|
|
108
|
-
const dbValue = await this.
|
|
109
|
-
// don't call this.
|
|
108
|
+
const dbValue = await this.dbDatastore.get(key, options);
|
|
109
|
+
// don't call this.memoryDatastore.set directly
|
|
110
110
|
// we want to get through prune() logic with fromDb as true
|
|
111
111
|
await this._put(key, dbValue, true);
|
|
112
112
|
return dbValue;
|
|
@@ -125,30 +125,30 @@ export class Eth2PeerDataStore extends BaseDatastore {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
async delete(key: Key, options?: AbortOptions): Promise<void> {
|
|
128
|
-
this.
|
|
129
|
-
await this.
|
|
128
|
+
this.memoryDatastore.delete(key.toString());
|
|
129
|
+
await this.dbDatastore.delete(key, options);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
async *_all(q: Query, options?: AbortOptions): AwaitGenerator<Pair> {
|
|
133
|
-
for (const [key, value] of this.
|
|
133
|
+
for (const [key, value] of this.memoryDatastore.entries()) {
|
|
134
134
|
yield {
|
|
135
135
|
key: new Key(key),
|
|
136
136
|
value: value.data,
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
-
yield* this.
|
|
139
|
+
yield* this.dbDatastore.query(q, options);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
async *_allKeys(q: KeyQuery, options?: AbortOptions): AwaitGenerator<Key> {
|
|
143
|
-
for (const key of this.
|
|
143
|
+
for (const key of this.memoryDatastore.keys()) {
|
|
144
144
|
yield new Key(key);
|
|
145
145
|
}
|
|
146
|
-
yield* this.
|
|
146
|
+
yield* this.dbDatastore.queryKeys(q, options);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
private async _addDirtyItem(keyStr: string): Promise<void> {
|
|
150
|
-
this.
|
|
151
|
-
if (this.
|
|
150
|
+
this.dirtyItems.add(keyStr);
|
|
151
|
+
if (this.dirtyItems.size >= this.threshold) {
|
|
152
152
|
try {
|
|
153
153
|
await this._commitData();
|
|
154
154
|
} catch (_e) {}
|
|
@@ -156,15 +156,15 @@ export class Eth2PeerDataStore extends BaseDatastore {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
private async _commitData(): Promise<void> {
|
|
159
|
-
const batch = this.
|
|
160
|
-
for (const keyStr of this.
|
|
161
|
-
const memoryItem = this.
|
|
159
|
+
const batch = this.dbDatastore.batch();
|
|
160
|
+
for (const keyStr of this.dirtyItems) {
|
|
161
|
+
const memoryItem = this.memoryDatastore.get(keyStr);
|
|
162
162
|
if (memoryItem) {
|
|
163
163
|
batch.put(new Key(keyStr), memoryItem.data);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
await batch.commit();
|
|
167
|
-
this.
|
|
167
|
+
this.dirtyItems.clear();
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
@@ -175,7 +175,7 @@ export class Eth2PeerDataStore extends BaseDatastore {
|
|
|
175
175
|
let oldestKey: string | undefined = undefined;
|
|
176
176
|
let oldestValue: Uint8Array | undefined = undefined;
|
|
177
177
|
|
|
178
|
-
for (const [key, value] of this.
|
|
178
|
+
for (const [key, value] of this.memoryDatastore) {
|
|
179
179
|
if (value.lastAccessedMs < oldestAccessedMs) {
|
|
180
180
|
oldestAccessedMs = value.lastAccessedMs;
|
|
181
181
|
oldestKey = key;
|
|
@@ -184,8 +184,8 @@ export class Eth2PeerDataStore extends BaseDatastore {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
if (oldestKey && oldestValue) {
|
|
187
|
-
await this.
|
|
188
|
-
this.
|
|
187
|
+
await this.dbDatastore.put(new Key(oldestKey), oldestValue);
|
|
188
|
+
this.memoryDatastore.delete(oldestKey);
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
}
|
|
@@ -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},
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getParentRootFromSignedBeaconBlockSerialized,
|
|
23
23
|
getPayloadPresentFromPayloadAttestationMessageSerialized,
|
|
24
24
|
} from "../../util/sszBytes.js";
|
|
25
|
+
import {INetworkCore} from "../core/index.js";
|
|
25
26
|
import {NetworkEvent, NetworkEventBus} from "../events.js";
|
|
26
27
|
import {
|
|
27
28
|
GossipHandlers,
|
|
@@ -34,7 +35,12 @@ import {MAX_PEERS_PER_ROOT} from "./constants.js";
|
|
|
34
35
|
import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
|
|
35
36
|
import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
|
|
36
37
|
import {createGossipQueues} from "./gossipQueues/index.js";
|
|
37
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
ValidatorFnModules,
|
|
40
|
+
getGossipValidatorBatchFn,
|
|
41
|
+
getGossipValidatorFn,
|
|
42
|
+
rejectPeerAction,
|
|
43
|
+
} from "./gossipValidatorFn.js";
|
|
38
44
|
import {PendingGossipsubMessage} from "./types.js";
|
|
39
45
|
|
|
40
46
|
export * from "./types.js";
|
|
@@ -102,6 +108,12 @@ type SearchedRootEntry = {
|
|
|
102
108
|
*/
|
|
103
109
|
const DEFAULT_EARLIEST_PERMISSIBLE_SLOT_DISTANCE = 32;
|
|
104
110
|
|
|
111
|
+
/**
|
|
112
|
+
* The network worker bans a Fatal peer only on its next PeerManager heartbeat (30s). Until then the main thread
|
|
113
|
+
* would keep validating that peer's messages, so drop them here. 3 slots (36s) outlasts one heartbeat.
|
|
114
|
+
*/
|
|
115
|
+
export const FATAL_PEER_EXPIRY_SLOTS = 3;
|
|
116
|
+
|
|
105
117
|
type WorkOpts = {
|
|
106
118
|
bypassQueue?: boolean;
|
|
107
119
|
};
|
|
@@ -232,6 +244,7 @@ type SearchUnknownRootTarget =
|
|
|
232
244
|
*/
|
|
233
245
|
export class NetworkProcessor {
|
|
234
246
|
private readonly chain: IBeaconChain;
|
|
247
|
+
private readonly core: INetworkCore;
|
|
235
248
|
private readonly events: NetworkEventBus;
|
|
236
249
|
private readonly logger: Logger;
|
|
237
250
|
private readonly metrics: Metrics | null;
|
|
@@ -256,23 +269,31 @@ export class NetworkProcessor {
|
|
|
256
269
|
() => new MapDef<RootHex, SearchedRootEntry>(() => ({}))
|
|
257
270
|
);
|
|
258
271
|
private bufferedRootsBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
|
|
272
|
+
/** peer -> clock slot when it was Fatal'ed */
|
|
273
|
+
private readonly fatalPeers = new Map<PeerIdStr, Slot>();
|
|
259
274
|
|
|
260
275
|
constructor(
|
|
261
276
|
modules: NetworkProcessorModules,
|
|
262
277
|
private readonly opts: NetworkProcessorOpts
|
|
263
278
|
) {
|
|
264
|
-
const {chain, events, logger, metrics} = modules;
|
|
279
|
+
const {chain, core, events, logger, metrics} = modules;
|
|
265
280
|
this.chain = chain;
|
|
281
|
+
this.core = core;
|
|
266
282
|
this.events = events;
|
|
267
283
|
this.metrics = metrics;
|
|
268
284
|
this.logger = logger;
|
|
269
285
|
this.events = events;
|
|
270
286
|
this.gossipQueues = createGossipQueues();
|
|
271
287
|
this.gossipTopicConcurrency = mapValues(this.gossipQueues, () => 0);
|
|
272
|
-
this.gossipValidatorFn = getGossipValidatorFn(
|
|
288
|
+
this.gossipValidatorFn = getGossipValidatorFn(
|
|
289
|
+
modules.gossipHandlers ?? getGossipHandlers(modules, opts),
|
|
290
|
+
modules,
|
|
291
|
+
this.onFatalPeer
|
|
292
|
+
);
|
|
273
293
|
this.gossipValidatorBatchFn = getGossipValidatorBatchFn(
|
|
274
294
|
modules.gossipHandlers ?? getGossipHandlers(modules, opts),
|
|
275
|
-
modules
|
|
295
|
+
modules,
|
|
296
|
+
this.onFatalPeer
|
|
276
297
|
);
|
|
277
298
|
|
|
278
299
|
events.on(NetworkEvent.pendingGossipsubMessage, this.onPendingGossipsubMessage);
|
|
@@ -406,19 +427,47 @@ export class NetworkProcessor {
|
|
|
406
427
|
}
|
|
407
428
|
}
|
|
408
429
|
|
|
430
|
+
private onFatalPeer = (peer: PeerIdStr): void => {
|
|
431
|
+
this.fatalPeers.set(peer, this.chain.clock.currentSlot);
|
|
432
|
+
};
|
|
433
|
+
|
|
409
434
|
private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
|
|
410
435
|
const topicType = message.topic.type;
|
|
436
|
+
if (this.fatalPeers.has(message.propagationSource)) {
|
|
437
|
+
this.metrics?.networkProcessor.gossipValidationError.inc({topic: topicType, error: GossipErrorCode.FATAL_PEER});
|
|
438
|
+
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
411
442
|
const extractBlockSlotRootFn = this.extractBlockSlotRootFns[topicType];
|
|
443
|
+
if (extractBlockSlotRootFn === undefined) {
|
|
444
|
+
// some messages don't have slot and root
|
|
445
|
+
this.pushPendingGossipsubMessageToQueue(message);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
412
448
|
|
|
413
449
|
// 1st extract round: make sure slot is in range and if block root is not available
|
|
414
450
|
// proactively search for it + queue the message
|
|
415
|
-
const slotRoot = extractBlockSlotRootFn
|
|
416
|
-
? extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork)
|
|
417
|
-
: null;
|
|
451
|
+
const slotRoot = extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork);
|
|
418
452
|
if (slotRoot === null) {
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
|
|
453
|
+
// DOS protection: null means malformed bytes or slot >= 2^32, neither is forwarded by honest peers.
|
|
454
|
+
const {propagationSource, clientAgent, clientVersion} = message;
|
|
455
|
+
const code = GossipErrorCode.INVALID_SLOT;
|
|
456
|
+
const peerAction = rejectPeerAction(topicType, code);
|
|
457
|
+
this.metrics?.networkProcessor.gossipValidationReject.inc({topic: topicType});
|
|
458
|
+
this.core.reportPeer(propagationSource, peerAction, code);
|
|
459
|
+
this.logger.debug(`Gossip validation ${topicType} rejected due to null slot`, {
|
|
460
|
+
peer: propagationSource,
|
|
461
|
+
clientAgent,
|
|
462
|
+
clientVersion,
|
|
463
|
+
peerAction,
|
|
464
|
+
code,
|
|
465
|
+
});
|
|
466
|
+
this.events.emit(NetworkEvent.gossipMessageValidationResult, {
|
|
467
|
+
msgId: message.msgId,
|
|
468
|
+
propagationSource,
|
|
469
|
+
acceptance: TopicValidatorResult.Reject,
|
|
470
|
+
});
|
|
422
471
|
return;
|
|
423
472
|
}
|
|
424
473
|
|
|
@@ -872,6 +921,10 @@ export class NetworkProcessor {
|
|
|
872
921
|
for (const slot of this.bufferedRootsBySlot.keys()) {
|
|
873
922
|
if (slot <= minSlot) this.bufferedRootsBySlot.delete(slot);
|
|
874
923
|
}
|
|
924
|
+
|
|
925
|
+
for (const [peer, slot] of this.fatalPeers) {
|
|
926
|
+
if (clockSlot - slot > FATAL_PEER_EXPIRY_SLOTS) this.fatalPeers.delete(peer);
|
|
927
|
+
}
|
|
875
928
|
};
|
|
876
929
|
|
|
877
930
|
private executeWork(): void {
|
|
@@ -927,6 +980,32 @@ export class NetworkProcessor {
|
|
|
927
980
|
private async processPendingGossipsubMessage(
|
|
928
981
|
messageOrArray: PendingGossipsubMessage | PendingGossipsubMessage[]
|
|
929
982
|
): Promise<void> {
|
|
983
|
+
// drop messages from peers Fatal'ed after these were queued or started awaiting a block/envelope
|
|
984
|
+
// No need to report the dropped jobs to gossip. They will be eventually pruned from the mcache
|
|
985
|
+
if (this.fatalPeers.size > 0) {
|
|
986
|
+
if (Array.isArray(messageOrArray)) {
|
|
987
|
+
const allowed: PendingGossipsubMessage[] = [];
|
|
988
|
+
for (const msg of messageOrArray) {
|
|
989
|
+
if (this.fatalPeers.has(msg.propagationSource)) {
|
|
990
|
+
this.metrics?.networkProcessor.gossipValidationError.inc({
|
|
991
|
+
topic: msg.topic.type,
|
|
992
|
+
error: GossipErrorCode.FATAL_PEER,
|
|
993
|
+
});
|
|
994
|
+
} else {
|
|
995
|
+
allowed.push(msg);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (allowed.length === 0) return;
|
|
999
|
+
messageOrArray = allowed;
|
|
1000
|
+
} else if (this.fatalPeers.has(messageOrArray.propagationSource)) {
|
|
1001
|
+
this.metrics?.networkProcessor.gossipValidationError.inc({
|
|
1002
|
+
topic: messageOrArray.topic.type,
|
|
1003
|
+
error: GossipErrorCode.FATAL_PEER,
|
|
1004
|
+
});
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
930
1009
|
const nowSec = Date.now() / 1000;
|
|
931
1010
|
if (Array.isArray(messageOrArray)) {
|
|
932
1011
|
for (const msg of messageOrArray) {
|
|
@@ -6,6 +6,7 @@ import {toRootHex} from "@lodestar/utils";
|
|
|
6
6
|
import {EnvelopeReconstructionError} from "../../../chain/errors/index.js";
|
|
7
7
|
import {IBeaconChain} from "../../../chain/index.js";
|
|
8
8
|
import {IBeaconDb} from "../../../db/index.js";
|
|
9
|
+
import {MAX_BODIES_PER_REQUEST} from "../../../util/execution.js";
|
|
9
10
|
import {ExecutionPayloadEnvelopesByRootRequest} from "../../../util/types.js";
|
|
10
11
|
import {prettyPrintPeerId} from "../../util.js";
|
|
11
12
|
|
|
@@ -19,10 +20,15 @@ export async function* onExecutionPayloadEnvelopesByRoot(
|
|
|
19
20
|
// The gloas req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve.
|
|
20
21
|
// Archival nodes may still serve older retained payloads to allow genesis sync.
|
|
21
22
|
|
|
22
|
-
// Resolve slots first so archived
|
|
23
|
+
// Resolve slots first so archived envelopes can be reconstructed in EL batches.
|
|
24
|
+
// Duplicate roots are served once, since each archived envelope costs an EL fetch and a rebuild.
|
|
23
25
|
const requests: {blockSlot: Slot; blockRootHex: RootHex}[] = [];
|
|
26
|
+
const seenRoots = new Set<RootHex>();
|
|
24
27
|
for (const root of requestBody) {
|
|
25
28
|
const rootHex = toRootHex(root);
|
|
29
|
+
if (seenRoots.has(rootHex)) continue;
|
|
30
|
+
seenRoots.add(rootHex);
|
|
31
|
+
|
|
26
32
|
const block = chain.forkChoice.getBlockHexDefaultStatus(rootHex);
|
|
27
33
|
// If the block is not in fork choice, it may be finalized. Attempt to find its slot in block archive
|
|
28
34
|
const slot = block ? block.slot : await db.blockArchive.getSlotByRoot(root);
|
|
@@ -41,35 +47,43 @@ export async function* onExecutionPayloadEnvelopesByRoot(
|
|
|
41
47
|
requests.push({blockSlot: slot, blockRootHex: rootHex});
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
50
|
+
// Yield between EL batches so the first response does not wait for the entire request.
|
|
51
|
+
let yielded = 0;
|
|
52
|
+
for (let i = 0; i < requests.length; i += MAX_BODIES_PER_REQUEST) {
|
|
53
|
+
const batch = requests.slice(i, i + MAX_BODIES_PER_REQUEST);
|
|
54
|
+
let envelopesBytes: (Uint8Array | null)[];
|
|
55
|
+
try {
|
|
56
|
+
// By-root allows omission, so an envelope that cannot be rebuilt is left out rather than failing the response.
|
|
57
|
+
envelopesBytes = await chain.getSerializedExecutionPayloadEnvelopes(batch, "omit");
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (e instanceof EnvelopeReconstructionError) {
|
|
60
|
+
// By-root permits partial responses, so an EL failure can end the response here.
|
|
61
|
+
if (yielded > 0) return;
|
|
62
|
+
throw new ResponseError(
|
|
63
|
+
RespStatus.RESOURCE_UNAVAILABLE,
|
|
64
|
+
`Failed to reconstruct archived envelopes: ${e.message}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
throw e;
|
|
54
68
|
}
|
|
55
|
-
throw e;
|
|
56
|
-
}
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
for (let j = 0; j < batch.length; j++) {
|
|
71
|
+
const {blockSlot, blockRootHex} = batch[j];
|
|
72
|
+
const envelopeBytes = envelopesBytes[j];
|
|
73
|
+
if (envelopeBytes) {
|
|
74
|
+
yielded++;
|
|
75
|
+
yield {
|
|
76
|
+
data: envelopeBytes,
|
|
77
|
+
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(blockSlot)),
|
|
78
|
+
};
|
|
79
|
+
} else {
|
|
80
|
+
chain.logger.debug("Cannot serve ExecutionPayloadEnvelopesByRoot: envelope not found", {
|
|
81
|
+
slot: blockSlot,
|
|
82
|
+
root: blockRootHex,
|
|
83
|
+
peer: prettyPrintPeerId(peerId),
|
|
84
|
+
client: peerClient,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
73
87
|
}
|
|
74
88
|
}
|
|
75
89
|
}
|
package/src/node/nodejs.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import {setMaxListeners} from "node:events";
|
|
2
2
|
import {PrivateKey} from "@libp2p/interface";
|
|
3
3
|
import {Registry} from "prom-client";
|
|
4
|
+
import {
|
|
5
|
+
init as initNativeMetrics,
|
|
6
|
+
registerLocalValidator as registerNativeLocalValidator,
|
|
7
|
+
scrapeMetrics as scrapeNativeMetrics,
|
|
8
|
+
unregisterLocalValidator as unregisterNativeLocalValidator,
|
|
9
|
+
} from "@chainsafe/lodestar-z/metrics";
|
|
4
10
|
import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
|
|
5
11
|
import {hasher} from "@chainsafe/persistent-merkle-tree";
|
|
6
12
|
import {BeaconApiMethods} from "@lodestar/api/beacon/server";
|
|
@@ -171,7 +177,13 @@ export class BeaconNode {
|
|
|
171
177
|
// monitoring relies on metrics data
|
|
172
178
|
opts.monitoring.endpoint
|
|
173
179
|
) {
|
|
174
|
-
|
|
180
|
+
if (opts.chain.nativeStateTransition) {
|
|
181
|
+
initNativeMetrics();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries, {
|
|
185
|
+
includeStateTransitionMetrics: !opts.chain.nativeStateTransition,
|
|
186
|
+
});
|
|
175
187
|
initBeaconMetrics(metrics, anchorState);
|
|
176
188
|
// Since the db is instantiated before this, metrics must be injected manually afterwards
|
|
177
189
|
db.setMetrics(metrics.db, metrics.flatFileStore);
|
|
@@ -185,7 +197,13 @@ export class BeaconNode {
|
|
|
185
197
|
config,
|
|
186
198
|
anchorState.genesisTime,
|
|
187
199
|
logger.child({module: LoggerModule.vmon}),
|
|
188
|
-
opts.validatorMonitor
|
|
200
|
+
opts.validatorMonitor,
|
|
201
|
+
opts.chain.nativeStateTransition
|
|
202
|
+
? {
|
|
203
|
+
registerLocalValidator: registerNativeLocalValidator,
|
|
204
|
+
unregisterLocalValidator: unregisterNativeLocalValidator,
|
|
205
|
+
}
|
|
206
|
+
: null
|
|
189
207
|
)
|
|
190
208
|
: null;
|
|
191
209
|
|
|
@@ -303,7 +321,17 @@ export class BeaconNode {
|
|
|
303
321
|
const metricsServer = opts.metrics.enabled
|
|
304
322
|
? await getHttpMetricsServer(opts.metrics, {
|
|
305
323
|
register: (metrics as Metrics).register,
|
|
306
|
-
getOtherMetrics: async () =>
|
|
324
|
+
getOtherMetrics: async () => {
|
|
325
|
+
const otherMetrics = await Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]);
|
|
326
|
+
if (opts.chain.nativeStateTransition) {
|
|
327
|
+
try {
|
|
328
|
+
otherMetrics.push(scrapeNativeMetrics());
|
|
329
|
+
} catch (e) {
|
|
330
|
+
logger.warn("Failed to scrape native state-transition metrics", {}, e as Error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return otherMetrics;
|
|
334
|
+
},
|
|
307
335
|
logger: logger.child({module: LoggerModule.metrics}),
|
|
308
336
|
})
|
|
309
337
|
: null;
|
package/src/sync/range/batch.ts
CHANGED
|
@@ -773,6 +773,7 @@ export class Batch {
|
|
|
773
773
|
// remove any downloaded blocks and re-attempt
|
|
774
774
|
// TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
|
|
775
775
|
this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
776
|
+
this.resetRequests();
|
|
776
777
|
}
|
|
777
778
|
|
|
778
779
|
private onProcessingError(attempt: FailedAttempt): void {
|
|
@@ -784,6 +785,16 @@ export class Batch {
|
|
|
784
785
|
// remove any downloaded blocks and re-attempt
|
|
785
786
|
// TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
|
|
786
787
|
this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
788
|
+
this.resetRequests();
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* After a partial download `requests` only cover the data still missing from the downloaded blocks,
|
|
793
|
+
* e.g. a single column. Once those blocks are dropped, request the whole batch again, otherwise
|
|
794
|
+
* every peer fails the retry for lack of blocks to validate the data against.
|
|
795
|
+
*/
|
|
796
|
+
private resetRequests(): void {
|
|
797
|
+
this.requests = this.getRequests([]);
|
|
787
798
|
}
|
|
788
799
|
|
|
789
800
|
/** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -225,6 +225,9 @@ export class SyncChain {
|
|
|
225
225
|
Math.floor((localFinalizedEpoch - this.lastEpochWithProcessBlocks) / EPOCHS_PER_BATCH) * EPOCHS_PER_BATCH;
|
|
226
226
|
this.advanceChain(lastEpochWithProcessBlocksAligned);
|
|
227
227
|
|
|
228
|
+
// stopSyncing() cleared the retry timer, but peers can still be in rate limit backoff
|
|
229
|
+
this.scheduleRateLimitBackoffRetry();
|
|
230
|
+
|
|
228
231
|
// Potentially download new batches and process pending
|
|
229
232
|
this.triggerBatchDownloader();
|
|
230
233
|
this.triggerBatchProcessor();
|
|
@@ -417,8 +420,10 @@ export class SyncChain {
|
|
|
417
420
|
this.rateLimitBackoffTimeout = setTimeout(
|
|
418
421
|
() => {
|
|
419
422
|
this.rateLimitBackoffTimeout = undefined;
|
|
420
|
-
|
|
423
|
+
// Must run before the downloader, it drops expired peers without triggering a download. If it ran
|
|
424
|
+
// after, a backoff expiring in between would leave the peer idle with no retry scheduled.
|
|
421
425
|
this.scheduleRateLimitBackoffRetry();
|
|
426
|
+
this.triggerBatchDownloader();
|
|
422
427
|
},
|
|
423
428
|
Math.max(0, retryAt - now)
|
|
424
429
|
);
|