@lodestar/beacon-node 1.47.0-dev.931ecbec99 → 1.47.0-dev.9572bcc698
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 +3 -2
- 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 +21 -2
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/events/index.d.ts.map +1 -1
- package/lib/api/impl/events/index.js +2 -0
- package/lib/api/impl/events/index.js.map +1 -1
- package/lib/api/rest/base.d.ts.map +1 -1
- package/lib/api/rest/base.js +7 -0
- package/lib/api/rest/base.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +10 -8
- 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 +14 -6
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.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 +10 -2
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +1 -0
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +6 -1
- package/lib/chain/errors/blockError.js.map +1 -1
- 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/deferredVoluntaryExitPool.d.ts +16 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
- package/lib/chain/opPools/index.d.ts +1 -0
- package/lib/chain/opPools/index.d.ts.map +1 -1
- package/lib/chain/opPools/index.js +1 -0
- package/lib/chain/opPools/index.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +2 -0
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +2 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/validation/voluntaryExit.d.ts +9 -1
- package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
- package/lib/chain/validation/voluntaryExit.js +46 -1
- package/lib/chain/validation/voluntaryExit.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +17 -4
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +20 -2
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +13 -0
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +37 -0
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +22 -43
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +5 -1
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/util.d.ts +0 -1
- package/lib/network/util.d.ts.map +1 -1
- package/lib/network/util.js +0 -4
- package/lib/network/util.js.map +1 -1
- package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
- package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
- package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
- package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +2 -0
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/batch.d.ts +23 -5
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +61 -22
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +2 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +23 -10
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/types.d.ts +32 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +33 -0
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +95 -51
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +16 -11
- 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 +3 -1
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/package.json +34 -34
- package/src/api/impl/beacon/blocks/index.ts +3 -2
- package/src/api/impl/beacon/pool/index.ts +22 -2
- package/src/api/impl/events/index.ts +3 -1
- package/src/api/rest/base.ts +7 -0
- package/src/chain/blocks/importExecutionPayload.ts +12 -10
- package/src/chain/blocks/index.ts +18 -6
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
- package/src/chain/chain.ts +10 -1
- package/src/chain/errors/blockError.ts +8 -2
- package/src/chain/interface.ts +2 -0
- package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
- package/src/chain/opPools/index.ts +1 -0
- package/src/chain/regen/interface.ts +2 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
- package/src/chain/validation/voluntaryExit.ts +59 -2
- package/src/metrics/metrics/lodestar.ts +27 -6
- package/src/network/gossip/topic.ts +38 -0
- package/src/network/network.ts +27 -55
- package/src/network/processor/gossipHandlers.ts +5 -1
- package/src/network/util.ts +0 -5
- package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
- package/src/node/nodejs.ts +3 -0
- package/src/sync/range/batch.ts +83 -28
- package/src/sync/range/chain.ts +26 -11
- package/src/sync/types.ts +33 -0
- package/src/sync/unknownBlock.ts +105 -55
- package/src/sync/utils/downloadByRange.ts +21 -12
- package/src/sync/utils/downloadByRoot.ts +3 -1
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/ChainSafe/lodestar/issues"
|
|
13
13
|
},
|
|
14
|
-
"version": "1.47.0-dev.
|
|
14
|
+
"version": "1.47.0-dev.9572bcc698",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
@@ -100,51 +100,51 @@
|
|
|
100
100
|
"check-readme": "pnpm exec tsx ../../scripts/check_readme.ts"
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
|
-
"@chainsafe/as-sha256": "^1.2.
|
|
103
|
+
"@chainsafe/as-sha256": "^1.2.5",
|
|
104
104
|
"@chainsafe/discv5": "^12.0.2",
|
|
105
105
|
"@chainsafe/enr": "^6.0.2",
|
|
106
106
|
"@chainsafe/libp2p-noise": "^17.0.0",
|
|
107
107
|
"@chainsafe/libp2p-quic": "^2.1.3",
|
|
108
108
|
"@chainsafe/lodestar-z": "catalog:",
|
|
109
|
-
"@chainsafe/persistent-merkle-tree": "^1.3.
|
|
109
|
+
"@chainsafe/persistent-merkle-tree": "^1.3.1",
|
|
110
110
|
"@chainsafe/prometheus-gc-stats": "^1.0.0",
|
|
111
111
|
"@chainsafe/snappy-wasm": "^0.5.0",
|
|
112
|
-
"@chainsafe/ssz": "^1.6.
|
|
112
|
+
"@chainsafe/ssz": "^1.6.3",
|
|
113
113
|
"@chainsafe/threads": "^1.11.3",
|
|
114
114
|
"@crate-crypto/node-eth-kzg": "0.9.1",
|
|
115
115
|
"@fastify/bearer-auth": "^10.0.1",
|
|
116
116
|
"@fastify/cors": "^10.0.1",
|
|
117
117
|
"@fastify/swagger": "^9.0.0",
|
|
118
118
|
"@fastify/swagger-ui": "^5.0.1",
|
|
119
|
-
"@libp2p/bootstrap": "^12.0.
|
|
120
|
-
"@libp2p/crypto": "^5.1.
|
|
121
|
-
"@libp2p/gossipsub": "^16.
|
|
122
|
-
"@libp2p/identify": "^4.
|
|
123
|
-
"@libp2p/interface": "^3.
|
|
124
|
-
"@libp2p/mdns": "^12.0.
|
|
125
|
-
"@libp2p/mplex": "^12.0.
|
|
126
|
-
"@libp2p/peer-id": "^6.0.
|
|
127
|
-
"@libp2p/prometheus-metrics": "^5.0.
|
|
128
|
-
"@libp2p/tcp": "^11.0.
|
|
129
|
-
"@lodestar/api": "^1.47.0-dev.
|
|
130
|
-
"@lodestar/config": "^1.47.0-dev.
|
|
131
|
-
"@lodestar/db": "^1.47.0-dev.
|
|
132
|
-
"@lodestar/fork-choice": "^1.47.0-dev.
|
|
133
|
-
"@lodestar/logger": "^1.47.0-dev.
|
|
134
|
-
"@lodestar/params": "^1.47.0-dev.
|
|
135
|
-
"@lodestar/reqresp": "^1.47.0-dev.
|
|
136
|
-
"@lodestar/state-transition": "^1.47.0-dev.
|
|
137
|
-
"@lodestar/types": "^1.47.0-dev.
|
|
138
|
-
"@lodestar/utils": "^1.47.0-dev.
|
|
139
|
-
"@lodestar/validator": "^1.47.0-dev.
|
|
119
|
+
"@libp2p/bootstrap": "^12.0.30",
|
|
120
|
+
"@libp2p/crypto": "^5.1.23",
|
|
121
|
+
"@libp2p/gossipsub": "^16.1.1",
|
|
122
|
+
"@libp2p/identify": "^4.1.13",
|
|
123
|
+
"@libp2p/interface": "^3.3.0",
|
|
124
|
+
"@libp2p/mdns": "^12.0.30",
|
|
125
|
+
"@libp2p/mplex": "^12.0.30",
|
|
126
|
+
"@libp2p/peer-id": "^6.0.15",
|
|
127
|
+
"@libp2p/prometheus-metrics": "^5.0.28",
|
|
128
|
+
"@libp2p/tcp": "^11.0.27",
|
|
129
|
+
"@lodestar/api": "^1.47.0-dev.9572bcc698",
|
|
130
|
+
"@lodestar/config": "^1.47.0-dev.9572bcc698",
|
|
131
|
+
"@lodestar/db": "^1.47.0-dev.9572bcc698",
|
|
132
|
+
"@lodestar/fork-choice": "^1.47.0-dev.9572bcc698",
|
|
133
|
+
"@lodestar/logger": "^1.47.0-dev.9572bcc698",
|
|
134
|
+
"@lodestar/params": "^1.47.0-dev.9572bcc698",
|
|
135
|
+
"@lodestar/reqresp": "^1.47.0-dev.9572bcc698",
|
|
136
|
+
"@lodestar/state-transition": "^1.47.0-dev.9572bcc698",
|
|
137
|
+
"@lodestar/types": "^1.47.0-dev.9572bcc698",
|
|
138
|
+
"@lodestar/utils": "^1.47.0-dev.9572bcc698",
|
|
139
|
+
"@lodestar/validator": "^1.47.0-dev.9572bcc698",
|
|
140
140
|
"@multiformats/multiaddr": "^13.0.1",
|
|
141
|
-
"datastore-core": "^
|
|
142
|
-
"datastore-level": "^
|
|
141
|
+
"datastore-core": "^12.0.1",
|
|
142
|
+
"datastore-level": "^13.0.1",
|
|
143
143
|
"deepmerge": "^4.3.1",
|
|
144
144
|
"fastify": "^5.8.1",
|
|
145
|
-
"interface-datastore": "^
|
|
145
|
+
"interface-datastore": "^10.0.1",
|
|
146
146
|
"jwt-simple": "0.5.6",
|
|
147
|
-
"libp2p": "3.
|
|
147
|
+
"libp2p": "3.3.9",
|
|
148
148
|
"multiformats": "^13.4.2",
|
|
149
149
|
"prom-client": "^15.1.0",
|
|
150
150
|
"qs": "^6.14.2",
|
|
@@ -155,10 +155,10 @@
|
|
|
155
155
|
},
|
|
156
156
|
"devDependencies": {
|
|
157
157
|
"@chainsafe/swap-or-not-shuffle": "^1.2.1",
|
|
158
|
-
"@libp2p/interface-internal": "^3.
|
|
159
|
-
"@libp2p/logger": "^6.2.
|
|
160
|
-
"@libp2p/utils": "^7.0
|
|
161
|
-
"@lodestar/spec-test-util": "^1.47.0-dev.
|
|
158
|
+
"@libp2p/interface-internal": "^3.1.12",
|
|
159
|
+
"@libp2p/logger": "^6.2.13",
|
|
160
|
+
"@libp2p/utils": "^7.4.0",
|
|
161
|
+
"@lodestar/spec-test-util": "^1.47.0-dev.9572bcc698",
|
|
162
162
|
"@types/js-yaml": "^4.0.5",
|
|
163
163
|
"@types/qs": "^6.9.7",
|
|
164
164
|
"@types/tmp": "^0.2.3",
|
|
@@ -175,5 +175,5 @@
|
|
|
175
175
|
"beacon",
|
|
176
176
|
"blockchain"
|
|
177
177
|
],
|
|
178
|
-
"gitHead": "
|
|
178
|
+
"gitHead": "8f76988606049ffbae2597f3730d7069b88cc44e"
|
|
179
179
|
}
|
|
@@ -128,7 +128,8 @@ export function getBeaconBlockApi({
|
|
|
128
128
|
forkName: fork,
|
|
129
129
|
sampledColumns: chain.custodyConfig.sampledColumns,
|
|
130
130
|
custodyColumns: chain.custodyConfig.custodyColumns,
|
|
131
|
-
|
|
131
|
+
seenTimestampSec,
|
|
132
|
+
source: PayloadEnvelopeInputSource.api,
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
135
|
|
|
@@ -401,7 +402,7 @@ export function getBeaconBlockApi({
|
|
|
401
402
|
for (let i = 0; i < dataColumnSidecars.length; i++) {
|
|
402
403
|
// + 1 because we publish to beacon_block first
|
|
403
404
|
const {sentPeers, alreadyPublished} = sentPeersArr[i + 1] as {sentPeers: number; alreadyPublished: boolean};
|
|
404
|
-
// sent peers could be 0
|
|
405
|
+
// sent peers could be 0, data_column_sidecar opts out of the zero peers publish error, see gossipTopicAllowPublishToZeroPeers
|
|
405
406
|
metrics?.dataColumns.sentPeersPerSubnet.observe(sentPeers);
|
|
406
407
|
// A duplicate publish (alreadyPublished=true) means the column is already propagating on the network —
|
|
407
408
|
// expected in self-build flows where peers gossip columns back to us before we publish the envelope.
|
|
@@ -100,7 +100,9 @@ export function getBeaconPoolApi({
|
|
|
100
100
|
const {indexedAttestation, subnet, attDataRootHex, committeeIndex, validatorCommitteeIndex, committeeSize} =
|
|
101
101
|
await validateGossipFnRetryUnknownRoot(validateFn, network, chain, slot, beaconBlockRoot);
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
try {
|
|
104
|
+
// `is_aggregator` only covers aggregating what we receive on the subnet via gossip, not what
|
|
105
|
+
// a validator client hands us directly, see https://github.com/ChainSafe/lodestar/issues/7548
|
|
104
106
|
const insertOutcome = chain.attestationPool.add(
|
|
105
107
|
committeeIndex,
|
|
106
108
|
attestation,
|
|
@@ -110,6 +112,10 @@ export function getBeaconPoolApi({
|
|
|
110
112
|
priority
|
|
111
113
|
);
|
|
112
114
|
metrics?.opPool.attestationPool.apiInsertOutcome.inc({insertOutcome});
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// The pool is a local optimization, failing to insert must not stop us from publishing a
|
|
117
|
+
// validated attestation, which the route requires us to do. Same handling as the gossip path
|
|
118
|
+
logger.debug("Error adding unaggregated attestation to pool", {subnet}, e as Error);
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
if (isForkPostElectra(fork)) {
|
|
@@ -173,7 +179,21 @@ export function getBeaconPoolApi({
|
|
|
173
179
|
},
|
|
174
180
|
|
|
175
181
|
async submitPoolVoluntaryExit({signedVoluntaryExit}) {
|
|
176
|
-
await validateApiVoluntaryExit(chain, signedVoluntaryExit);
|
|
182
|
+
const result = await validateApiVoluntaryExit(chain, signedVoluntaryExit);
|
|
183
|
+
|
|
184
|
+
if (result.status === "deferred") {
|
|
185
|
+
const currentEpoch = chain.clock.currentEpoch;
|
|
186
|
+
const inserted = chain.deferredVoluntaryExitPool.insert(signedVoluntaryExit, result.validity, currentEpoch);
|
|
187
|
+
if (!inserted) {
|
|
188
|
+
throw new ApiError(400, "Deferred voluntary exit pool is full or already contains this validator");
|
|
189
|
+
}
|
|
190
|
+
logger.info("Voluntary exit deferred until transient conditions are met", {
|
|
191
|
+
validatorIndex: signedVoluntaryExit.message.validatorIndex,
|
|
192
|
+
reason: result.validity,
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
177
197
|
chain.opPool.insertVoluntaryExit(signedVoluntaryExit);
|
|
178
198
|
chain.emitter.emit(routes.events.EventType.voluntaryExit, signedVoluntaryExit);
|
|
179
199
|
await network.publishVoluntaryExit(signedVoluntaryExit);
|
|
@@ -14,7 +14,9 @@ export function getEventsApi({
|
|
|
14
14
|
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
15
15
|
const handler = (data: any): void => {
|
|
16
16
|
try {
|
|
17
|
-
|
|
17
|
+
// TS only relates `{type, message}` to the `BeaconEvent` discriminated union while
|
|
18
|
+
// `EventType` stays within its ~25-member cap; without the cast, adding events trips TS2345.
|
|
19
|
+
onEvent({type: topic, message: data} as routes.events.BeaconEvent);
|
|
18
20
|
} catch (e) {
|
|
19
21
|
// `chain.emitter` emits synchronously, a throwing listener would propagate the error to
|
|
20
22
|
// whoever emitted the event, e.g. a gossip handler, and skip the remaining listeners.
|
package/src/api/rest/base.ts
CHANGED
|
@@ -104,6 +104,13 @@ export class RestApiServer {
|
|
|
104
104
|
|
|
105
105
|
// To parse our ApiError -> statusCode
|
|
106
106
|
server.setErrorHandler<FastifyError | Error>((err, _req, res) => {
|
|
107
|
+
if (res.raw.headersSent) {
|
|
108
|
+
// A status code can no longer be written, e.g. if a long-lived SSE stream failed mid-flight.
|
|
109
|
+
// End the response instead, else the socket is left open and the client never notices.
|
|
110
|
+
if (!res.raw.writableEnded && !res.raw.destroyed) res.raw.end();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
const stacktraces = opts.stacktraces ? err.stack?.split("\n") : undefined;
|
|
108
115
|
if ("validation" in err && err.validation) {
|
|
109
116
|
const {instancePath = "unknown", message} = err.validation?.[0] ?? {};
|
|
@@ -58,11 +58,12 @@ export type PayloadErrorType =
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export class PayloadError extends Error {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
constructor(
|
|
62
|
+
readonly payloadInput: PayloadEnvelopeInput,
|
|
63
|
+
readonly type: PayloadErrorType,
|
|
64
|
+
message?: string
|
|
65
|
+
) {
|
|
64
66
|
super(message ?? type.code);
|
|
65
|
-
this.type = type;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -125,7 +126,7 @@ export async function importExecutionPayload(
|
|
|
125
126
|
// 2. Get ProtoBlock for parent root lookup
|
|
126
127
|
const protoBlock = this.forkChoice.getBlockHexDefaultStatus(blockRootHex);
|
|
127
128
|
if (!protoBlock) {
|
|
128
|
-
throw new PayloadError({
|
|
129
|
+
throw new PayloadError(payloadInput, {
|
|
129
130
|
code: PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE,
|
|
130
131
|
blockRootHex,
|
|
131
132
|
});
|
|
@@ -140,13 +141,13 @@ export async function importExecutionPayload(
|
|
|
140
141
|
);
|
|
141
142
|
|
|
142
143
|
if (blockState == null) {
|
|
143
|
-
throw new PayloadError({
|
|
144
|
+
throw new PayloadError(payloadInput, {
|
|
144
145
|
code: PayloadErrorCode.MISS_BLOCK_STATE,
|
|
145
146
|
blockRootHex: protoBlock.blockRoot,
|
|
146
147
|
});
|
|
147
148
|
}
|
|
148
149
|
if (!isStatePostGloas(blockState)) {
|
|
149
|
-
throw new PayloadError({
|
|
150
|
+
throw new PayloadError(payloadInput, {
|
|
150
151
|
code: PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR,
|
|
151
152
|
message: `Expected gloas+ state for payload import, got fork=${blockState.forkName}`,
|
|
152
153
|
});
|
|
@@ -161,6 +162,7 @@ export async function importExecutionPayload(
|
|
|
161
162
|
});
|
|
162
163
|
} catch (e) {
|
|
163
164
|
throw new PayloadError(
|
|
165
|
+
payloadInput,
|
|
164
166
|
{
|
|
165
167
|
code: PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR,
|
|
166
168
|
message: (e as Error).message,
|
|
@@ -192,7 +194,7 @@ export async function importExecutionPayload(
|
|
|
192
194
|
|
|
193
195
|
// 4b. Check signature verification result
|
|
194
196
|
if (!signatureValid) {
|
|
195
|
-
throw new PayloadError({code: PayloadErrorCode.INVALID_SIGNATURE});
|
|
197
|
+
throw new PayloadError(payloadInput, {code: PayloadErrorCode.INVALID_SIGNATURE});
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
// 4c. Handle EL response
|
|
@@ -201,7 +203,7 @@ export async function importExecutionPayload(
|
|
|
201
203
|
break;
|
|
202
204
|
|
|
203
205
|
case ExecutionPayloadStatus.INVALID:
|
|
204
|
-
throw new PayloadError({
|
|
206
|
+
throw new PayloadError(payloadInput, {
|
|
205
207
|
code: PayloadErrorCode.EXECUTION_ENGINE_INVALID,
|
|
206
208
|
execStatus: execResult.status,
|
|
207
209
|
errorMessage: execResult.validationError ?? "",
|
|
@@ -214,7 +216,7 @@ export async function importExecutionPayload(
|
|
|
214
216
|
case ExecutionPayloadStatus.INVALID_BLOCK_HASH:
|
|
215
217
|
case ExecutionPayloadStatus.ELERROR:
|
|
216
218
|
case ExecutionPayloadStatus.UNAVAILABLE:
|
|
217
|
-
throw new PayloadError({
|
|
219
|
+
throw new PayloadError(payloadInput, {
|
|
218
220
|
code: PayloadErrorCode.EXECUTION_ENGINE_ERROR,
|
|
219
221
|
execStatus: execResult.status,
|
|
220
222
|
errorMessage: execResult.validationError ?? "",
|
|
@@ -8,7 +8,7 @@ import {BlockError, BlockErrorCode, isBlockErrorAborted} from "../errors/index.j
|
|
|
8
8
|
import {BlockProcessOpts} from "../options.js";
|
|
9
9
|
import {IBlockInput} from "./blockInput/types.js";
|
|
10
10
|
import {importBlock} from "./importBlock.js";
|
|
11
|
-
import {importExecutionPayload} from "./importExecutionPayload.js";
|
|
11
|
+
import {PayloadError, importExecutionPayload} from "./importExecutionPayload.js";
|
|
12
12
|
import {PayloadEnvelopeInput} from "./payloadEnvelopeInput/payloadEnvelopeInput.js";
|
|
13
13
|
import {FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
|
|
14
14
|
import {assertLinearChainSegment} from "./utils/chainSegment.js";
|
|
@@ -164,13 +164,21 @@ export async function processBlocks(
|
|
|
164
164
|
return; // Ignore
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
// above functions should only throw BlockError
|
|
168
|
-
const err =
|
|
167
|
+
// above functions should only throw BlockError, or PayloadError from the gloas payload import
|
|
168
|
+
const err = getBlockOrPayloadError(e, blocks[0].getBlock());
|
|
169
169
|
|
|
170
170
|
// TODO: De-duplicate with logic above
|
|
171
171
|
// ChainEvent.errorBlock
|
|
172
|
-
if (!(err instanceof BlockError)) {
|
|
173
|
-
this.logger.debug("
|
|
172
|
+
if (!(err instanceof BlockError) && !(err instanceof PayloadError)) {
|
|
173
|
+
this.logger.debug("Neither BlockError nor PayloadError received", {}, err);
|
|
174
|
+
} else if (err instanceof PayloadError) {
|
|
175
|
+
if (!opts.disableOnBlockError) {
|
|
176
|
+
this.logger.debug(
|
|
177
|
+
"Payload error",
|
|
178
|
+
{slot: err.payloadInput.slot, blockRoot: err.payloadInput.blockRootHex},
|
|
179
|
+
err
|
|
180
|
+
);
|
|
181
|
+
}
|
|
174
182
|
} else if (!opts.disableOnBlockError) {
|
|
175
183
|
this.logger.debug("Block error", {slot: err.signedBlock.message.slot}, err);
|
|
176
184
|
|
|
@@ -201,11 +209,15 @@ export async function processBlocks(
|
|
|
201
209
|
}
|
|
202
210
|
}
|
|
203
211
|
|
|
204
|
-
function
|
|
212
|
+
function getBlockOrPayloadError(e: unknown, block: SignedBeaconBlock): BlockError | PayloadError {
|
|
205
213
|
if (e instanceof BlockError) {
|
|
206
214
|
return e;
|
|
207
215
|
}
|
|
208
216
|
|
|
217
|
+
if (e instanceof PayloadError) {
|
|
218
|
+
return e;
|
|
219
|
+
}
|
|
220
|
+
|
|
209
221
|
if (e instanceof Error) {
|
|
210
222
|
const blockError = new BlockError(block, {code: BlockErrorCode.BEACON_CHAIN_ERROR, error: e});
|
|
211
223
|
blockError.stack = e.stack;
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ColumnWithSource,
|
|
10
10
|
CreateFromBidProps,
|
|
11
11
|
CreateFromBlockProps,
|
|
12
|
+
PayloadEnvelopeInputSource,
|
|
12
13
|
SourceMeta,
|
|
13
14
|
} from "./types.js";
|
|
14
15
|
|
|
@@ -71,6 +72,7 @@ export class PayloadEnvelopeInput {
|
|
|
71
72
|
readonly bid: gloas.ExecutionPayloadBid;
|
|
72
73
|
readonly versionedHashes: VersionedHashes;
|
|
73
74
|
readonly daOutOfRange: boolean;
|
|
75
|
+
readonly source: PayloadEnvelopeInputSource;
|
|
74
76
|
|
|
75
77
|
private columnsCache = new Map<ColumnIndex, ColumnWithSource>();
|
|
76
78
|
|
|
@@ -95,6 +97,7 @@ export class PayloadEnvelopeInput {
|
|
|
95
97
|
custodyColumns: ColumnIndex[];
|
|
96
98
|
timeCreatedSec: number;
|
|
97
99
|
daOutOfRange: boolean;
|
|
100
|
+
source: PayloadEnvelopeInputSource;
|
|
98
101
|
}) {
|
|
99
102
|
this.blockRootHex = props.blockRootHex;
|
|
100
103
|
this.slot = props.slot;
|
|
@@ -106,6 +109,7 @@ export class PayloadEnvelopeInput {
|
|
|
106
109
|
this.custodyColumns = props.custodyColumns;
|
|
107
110
|
this.timeCreatedSec = props.timeCreatedSec;
|
|
108
111
|
this.daOutOfRange = props.daOutOfRange;
|
|
112
|
+
this.source = props.source;
|
|
109
113
|
this.payloadEnvelopeDataPromise = createPromise();
|
|
110
114
|
this.allDataPromise = createPromise();
|
|
111
115
|
this.columnsDataPromise = createPromise();
|
|
@@ -133,8 +137,9 @@ export class PayloadEnvelopeInput {
|
|
|
133
137
|
bid,
|
|
134
138
|
sampledColumns: props.sampledColumns,
|
|
135
139
|
custodyColumns: props.custodyColumns,
|
|
136
|
-
timeCreatedSec: props.
|
|
140
|
+
timeCreatedSec: props.seenTimestampSec,
|
|
137
141
|
daOutOfRange: props.daOutOfRange,
|
|
142
|
+
source: props.source,
|
|
138
143
|
});
|
|
139
144
|
}
|
|
140
145
|
|
|
@@ -153,8 +158,9 @@ export class PayloadEnvelopeInput {
|
|
|
153
158
|
bid: props.bid,
|
|
154
159
|
sampledColumns: props.sampledColumns,
|
|
155
160
|
custodyColumns: props.custodyColumns,
|
|
156
|
-
timeCreatedSec: props.
|
|
161
|
+
timeCreatedSec: props.seenTimestampSec,
|
|
157
162
|
daOutOfRange: props.daOutOfRange,
|
|
163
|
+
source: props.source,
|
|
158
164
|
});
|
|
159
165
|
}
|
|
160
166
|
|
|
@@ -7,9 +7,24 @@ export enum PayloadEnvelopeInputSource {
|
|
|
7
7
|
engine = "engine",
|
|
8
8
|
byRange = "req_resp_by_range",
|
|
9
9
|
byRoot = "req_resp_by_root",
|
|
10
|
+
// Data-column reconstruction (KZG cell recovery), NOT a cache reload
|
|
10
11
|
recovery = "recovery",
|
|
12
|
+
// Entry reconstructed from the hot DB by SeenPayloadEnvelopeInput.getOrReload
|
|
13
|
+
reload = "reload",
|
|
14
|
+
// Entry seeded from a checkpoint anchor state's latestExecutionPayloadBid at chain init
|
|
15
|
+
anchorState = "anchor_state",
|
|
11
16
|
}
|
|
12
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Reason a PayloadEnvelopeInput is evicted from SeenPayloadEnvelopeInput. Used for the `pruned` metric
|
|
20
|
+
* label and the eviction log.
|
|
21
|
+
* - belowParent: pruned below the new head's parent (canonical, FULL, all-columns)
|
|
22
|
+
* - finalized: below the finalized slot
|
|
23
|
+
* - prune: explicit prune by root
|
|
24
|
+
* - cap: insertion-order backstop cap (MAX_PAYLOAD_ENVELOPE_INPUT_CACHE_SIZE)
|
|
25
|
+
*/
|
|
26
|
+
export type PayloadEnvelopeInputPruneReason = "belowParent" | "finalized" | "prune" | "cap";
|
|
27
|
+
|
|
13
28
|
export type SourceMeta = {
|
|
14
29
|
source: PayloadEnvelopeInputSource;
|
|
15
30
|
seenTimestampSec: number;
|
|
@@ -20,13 +35,12 @@ export type ColumnWithSource = SourceMeta & {
|
|
|
20
35
|
columnSidecar: gloas.DataColumnSidecar;
|
|
21
36
|
};
|
|
22
37
|
|
|
23
|
-
export type CreateFromBlockProps = {
|
|
38
|
+
export type CreateFromBlockProps = SourceMeta & {
|
|
24
39
|
blockRootHex: RootHex;
|
|
25
40
|
block: SignedBeaconBlock<ForkPostGloas>;
|
|
26
41
|
forkName: ForkName;
|
|
27
42
|
sampledColumns: ColumnIndex[];
|
|
28
43
|
custodyColumns: ColumnIndex[];
|
|
29
|
-
timeCreatedSec: number;
|
|
30
44
|
daOutOfRange: boolean;
|
|
31
45
|
};
|
|
32
46
|
|
|
@@ -35,7 +49,7 @@ export type CreateFromBlockProps = {
|
|
|
35
49
|
* the chain from a checkpoint anchor state — we have the bid via the state but not the
|
|
36
50
|
* full SignedBeaconBlock).
|
|
37
51
|
*/
|
|
38
|
-
export type CreateFromBidProps = {
|
|
52
|
+
export type CreateFromBidProps = SourceMeta & {
|
|
39
53
|
blockRootHex: RootHex;
|
|
40
54
|
slot: number;
|
|
41
55
|
forkName: ForkName;
|
|
@@ -43,7 +57,6 @@ export type CreateFromBidProps = {
|
|
|
43
57
|
bid: gloas.ExecutionPayloadBid;
|
|
44
58
|
sampledColumns: ColumnIndex[];
|
|
45
59
|
custodyColumns: ColumnIndex[];
|
|
46
|
-
timeCreatedSec: number;
|
|
47
60
|
daOutOfRange: boolean;
|
|
48
61
|
};
|
|
49
62
|
|
|
@@ -44,22 +44,31 @@ export async function verifyBlocksStateTransitionOnly(
|
|
|
44
44
|
// STFN - per_slot_processing() + per_block_processing()
|
|
45
45
|
// NOTE: `regen.getPreState()` should have dialed forward the state already caching checkpoint states
|
|
46
46
|
const useBlsBatchVerify = !opts?.disableBlsBatchVerify;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
let postState: IBeaconStateView;
|
|
48
|
+
try {
|
|
49
|
+
postState = preState.stateTransition(
|
|
50
|
+
block,
|
|
51
|
+
{
|
|
52
|
+
// NOTE: Assume valid for now while sending payload to execution engine in parallel
|
|
53
|
+
// Latter verifyBlocksInEpoch() will make sure that payload is indeed valid
|
|
54
|
+
executionPayloadStatus: ExecutionPayloadStatus.valid,
|
|
55
|
+
dataAvailabilityStatus,
|
|
56
|
+
// false because it's verified below with better error typing
|
|
57
|
+
verifyStateRoot: false,
|
|
58
|
+
// if block is trusted don't verify proposer or op signature
|
|
59
|
+
verifyProposer: !useBlsBatchVerify && !validSignatures && !validProposerSignature,
|
|
60
|
+
verifySignatures: !useBlsBatchVerify && !validSignatures,
|
|
61
|
+
dontTransferCache: false,
|
|
62
|
+
},
|
|
63
|
+
{metrics, validatorMonitor}
|
|
64
|
+
);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new BlockError(block, {
|
|
67
|
+
code: BlockErrorCode.PER_BLOCK_PROCESSING_ERROR,
|
|
68
|
+
blockRoot: blocks[i].blockRootHex,
|
|
69
|
+
error: e as Error,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
63
72
|
|
|
64
73
|
const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
|
|
65
74
|
source: StateHashTreeRootSource.blockTransition,
|
package/src/chain/chain.ts
CHANGED
|
@@ -79,6 +79,7 @@ import {CheckpointBalancesCache} from "./balancesCache.js";
|
|
|
79
79
|
import {BeaconProposerCache} from "./beaconProposerCache.js";
|
|
80
80
|
import {IBlockInput, isBlockInputBlobs, isBlockInputColumns} from "./blocks/blockInput/index.js";
|
|
81
81
|
import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js";
|
|
82
|
+
import {PayloadEnvelopeInputSource} from "./blocks/payloadEnvelopeInput/index.js";
|
|
82
83
|
import {PayloadEnvelopeProcessor} from "./blocks/payloadEnvelopeProcessor.js";
|
|
83
84
|
import {ImportPayloadOpts} from "./blocks/types.js";
|
|
84
85
|
import {persistBlockInput} from "./blocks/writeBlockInputToDb.js";
|
|
@@ -94,6 +95,7 @@ import {LightClientServer} from "./lightClient/index.js";
|
|
|
94
95
|
import {
|
|
95
96
|
AggregatedAttestationPool,
|
|
96
97
|
AttestationPool,
|
|
98
|
+
DeferredVoluntaryExitPool,
|
|
97
99
|
ExecutionPayloadBidPool,
|
|
98
100
|
OpPool,
|
|
99
101
|
PayloadAttestationPool,
|
|
@@ -193,6 +195,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
193
195
|
readonly payloadAttestationPool: PayloadAttestationPool;
|
|
194
196
|
readonly proposerPreferencesPool = new ProposerPreferencesPool();
|
|
195
197
|
readonly opPool: OpPool;
|
|
198
|
+
readonly deferredVoluntaryExitPool: DeferredVoluntaryExitPool;
|
|
196
199
|
|
|
197
200
|
// Gossip seen cache
|
|
198
201
|
readonly seenAttesters = new SeenAttesters();
|
|
@@ -320,6 +323,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
320
323
|
this.executionPayloadBidPool = new ExecutionPayloadBidPool();
|
|
321
324
|
this.payloadAttestationPool = new PayloadAttestationPool(config, clock, metrics);
|
|
322
325
|
this.opPool = new OpPool(config);
|
|
326
|
+
this.deferredVoluntaryExitPool = new DeferredVoluntaryExitPool(logger);
|
|
323
327
|
|
|
324
328
|
this.seenAggregatedAttestations = new SeenAggregatedAttestations(metrics);
|
|
325
329
|
this.seenContributionAndProof = new SeenContributionAndProof(metrics);
|
|
@@ -451,6 +455,9 @@ export class BeaconChain implements IBeaconChain {
|
|
|
451
455
|
chainEvents: emitter,
|
|
452
456
|
signal,
|
|
453
457
|
serializedCache: this.serializedCache,
|
|
458
|
+
db,
|
|
459
|
+
seenBlockInputCache: this.seenBlockInputCache,
|
|
460
|
+
custodyConfig: this.custodyConfig,
|
|
454
461
|
metrics,
|
|
455
462
|
logger,
|
|
456
463
|
});
|
|
@@ -466,7 +473,8 @@ export class BeaconChain implements IBeaconChain {
|
|
|
466
473
|
bid: anchorBid,
|
|
467
474
|
sampledColumns: this.custodyConfig.sampledColumns,
|
|
468
475
|
custodyColumns: this.custodyConfig.custodyColumns,
|
|
469
|
-
|
|
476
|
+
seenTimestampSec: Date.now() / 1000,
|
|
477
|
+
source: PayloadEnvelopeInputSource.anchorState,
|
|
470
478
|
});
|
|
471
479
|
}
|
|
472
480
|
|
|
@@ -1569,6 +1577,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1569
1577
|
// aggregatedAttestationPool tracks metrics on its own
|
|
1570
1578
|
metrics.opPool.attestationPool.size.set(this.attestationPool.getAttestationCount());
|
|
1571
1579
|
metrics.opPool.attesterSlashingPoolSize.set(this.opPool.attesterSlashingsSize);
|
|
1580
|
+
metrics.opPool.deferredVoluntaryExitPool.size.set(this.deferredVoluntaryExitPool.size());
|
|
1572
1581
|
metrics.opPool.proposerSlashingPoolSize.set(this.opPool.proposerSlashingsSize);
|
|
1573
1582
|
metrics.opPool.voluntaryExitPoolSize.set(this.opPool.voluntaryExitsSize);
|
|
1574
1583
|
metrics.opPool.syncCommitteeMessagePoolSize.set(this.syncCommitteeMessagePool.size);
|
|
@@ -136,7 +136,7 @@ export type BlockErrorType =
|
|
|
136
136
|
| {code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS}
|
|
137
137
|
| {code: BlockErrorCode.NON_LINEAR_SLOTS}
|
|
138
138
|
| {code: BlockErrorCode.ENVELOPE_BLOCK_ROOT_MISMATCH; envelopeBlockRoot: RootHex; blockRoot: RootHex}
|
|
139
|
-
| {code: BlockErrorCode.PER_BLOCK_PROCESSING_ERROR; error: Error}
|
|
139
|
+
| {code: BlockErrorCode.PER_BLOCK_PROCESSING_ERROR; blockRoot: RootHex; error: Error}
|
|
140
140
|
| {code: BlockErrorCode.BEACON_CHAIN_ERROR; error: Error}
|
|
141
141
|
| {code: BlockErrorCode.KNOWN_BAD_BLOCK}
|
|
142
142
|
| {code: BlockErrorCode.BLACKLISTED_BLOCK}
|
|
@@ -184,13 +184,19 @@ export function isBlockErrorAborted(e: unknown): e is BlockError {
|
|
|
184
184
|
export function renderBlockErrorType(type: BlockErrorType): Record<string, string | number | null> {
|
|
185
185
|
switch (type.code) {
|
|
186
186
|
case BlockErrorCode.PRESTATE_MISSING:
|
|
187
|
-
case BlockErrorCode.PER_BLOCK_PROCESSING_ERROR:
|
|
188
187
|
case BlockErrorCode.BEACON_CHAIN_ERROR:
|
|
189
188
|
return {
|
|
190
189
|
code: type.code,
|
|
191
190
|
error: type.error.message,
|
|
192
191
|
};
|
|
193
192
|
|
|
193
|
+
case BlockErrorCode.PER_BLOCK_PROCESSING_ERROR:
|
|
194
|
+
return {
|
|
195
|
+
code: type.code,
|
|
196
|
+
blockRoot: type.blockRoot,
|
|
197
|
+
error: type.error.message,
|
|
198
|
+
};
|
|
199
|
+
|
|
194
200
|
case BlockErrorCode.INVALID_SIGNATURE:
|
|
195
201
|
return {
|
|
196
202
|
code: type.code,
|
package/src/chain/interface.ts
CHANGED
|
@@ -45,6 +45,7 @@ import {LightClientServer} from "./lightClient/index.js";
|
|
|
45
45
|
import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool.js";
|
|
46
46
|
import {
|
|
47
47
|
AttestationPool,
|
|
48
|
+
DeferredVoluntaryExitPool,
|
|
48
49
|
ExecutionPayloadBidPool,
|
|
49
50
|
OpPool,
|
|
50
51
|
PayloadAttestationPool,
|
|
@@ -128,6 +129,7 @@ export interface IBeaconChain {
|
|
|
128
129
|
readonly payloadAttestationPool: PayloadAttestationPool;
|
|
129
130
|
readonly proposerPreferencesPool: ProposerPreferencesPool;
|
|
130
131
|
readonly opPool: OpPool;
|
|
132
|
+
readonly deferredVoluntaryExitPool: DeferredVoluntaryExitPool;
|
|
131
133
|
|
|
132
134
|
// Gossip seen cache
|
|
133
135
|
readonly seenAttesters: SeenAttesters;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {Logger} from "@lodestar/logger";
|
|
2
|
+
import {IBeaconStateView, VoluntaryExitValidity} from "@lodestar/state-transition";
|
|
3
|
+
import {Epoch, ValidatorIndex} from "@lodestar/types";
|
|
4
|
+
import {SignedVoluntaryExit} from "@lodestar/types/phase0";
|
|
5
|
+
import {isTransientExitValidity} from "../validation/voluntaryExit.js";
|
|
6
|
+
|
|
7
|
+
type DeferredEntry = {
|
|
8
|
+
exit: SignedVoluntaryExit;
|
|
9
|
+
validity: VoluntaryExitValidity;
|
|
10
|
+
insertedAtEpoch: Epoch;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class DeferredVoluntaryExitPool {
|
|
14
|
+
private pool = new Map<ValidatorIndex, DeferredEntry>();
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly logger: Logger,
|
|
18
|
+
private readonly maxSize = 1024,
|
|
19
|
+
private readonly maxDeferEpochs = 4096
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
insert(exit: SignedVoluntaryExit, validity: VoluntaryExitValidity, currentEpoch: Epoch): boolean {
|
|
23
|
+
if (!isTransientExitValidity(validity)) return false;
|
|
24
|
+
if (this.pool.size === this.maxSize) return false;
|
|
25
|
+
if (this.pool.has(exit.message.validatorIndex)) return false;
|
|
26
|
+
|
|
27
|
+
this.pool.set(exit.message.validatorIndex, {exit, validity, insertedAtEpoch: currentEpoch});
|
|
28
|
+
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
drainProcessableExits(state: IBeaconStateView): SignedVoluntaryExit[] {
|
|
33
|
+
const epoch = state.epoch;
|
|
34
|
+
const validExits: SignedVoluntaryExit[] = [];
|
|
35
|
+
for (const [validatorIndex, entry] of this.pool) {
|
|
36
|
+
try {
|
|
37
|
+
if (epoch - entry.insertedAtEpoch > this.maxDeferEpochs) {
|
|
38
|
+
this.pool.delete(validatorIndex);
|
|
39
|
+
this.logger.debug("Deferred voluntary exit expired", {validatorIndex, lastValidity: entry.validity});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const validity = state.getVoluntaryExitValidity(entry.exit, true);
|
|
43
|
+
if (validity === VoluntaryExitValidity.valid) {
|
|
44
|
+
validExits.push(entry.exit);
|
|
45
|
+
this.pool.delete(validatorIndex);
|
|
46
|
+
} else if (!isTransientExitValidity(validity)) {
|
|
47
|
+
this.logger.debug("Deferred voluntary exit became permanently invalid", {validatorIndex, validity});
|
|
48
|
+
this.pool.delete(validatorIndex);
|
|
49
|
+
}
|
|
50
|
+
// Else if still transient - keep
|
|
51
|
+
} catch (e) {
|
|
52
|
+
this.logger.debug("Processing deferred voluntary exit failed", {validatorIndex}, e as Error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return validExits;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
has(validatorIndex: ValidatorIndex): boolean {
|
|
59
|
+
return this.pool.has(validatorIndex);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
size(): number {
|
|
63
|
+
return this.pool.size;
|
|
64
|
+
}
|
|
65
|
+
}
|