@lodestar/state-transition 1.43.0-dev.4fb05c546d → 1.43.0-dev.4fe6b362c9
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/block/index.d.ts +2 -2
- package/lib/block/index.d.ts.map +1 -1
- package/lib/block/index.js +11 -4
- package/lib/block/index.js.map +1 -1
- package/lib/block/processConsolidationRequest.d.ts.map +1 -1
- package/lib/block/processConsolidationRequest.js +2 -1
- package/lib/block/processConsolidationRequest.js.map +1 -1
- package/lib/block/processDepositRequest.d.ts +3 -11
- package/lib/block/processDepositRequest.d.ts.map +1 -1
- package/lib/block/processDepositRequest.js +27 -35
- package/lib/block/processDepositRequest.js.map +1 -1
- package/lib/block/processParentExecutionPayload.d.ts +20 -0
- package/lib/block/processParentExecutionPayload.d.ts.map +1 -0
- package/lib/block/processParentExecutionPayload.js +101 -0
- package/lib/block/processParentExecutionPayload.js.map +1 -0
- package/lib/block/processWithdrawals.d.ts.map +1 -1
- package/lib/block/processWithdrawals.js +10 -4
- package/lib/block/processWithdrawals.js.map +1 -1
- package/lib/cache/epochCache.js +3 -3
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/epoch/processPendingDeposits.d.ts.map +1 -1
- package/lib/epoch/processPendingDeposits.js +4 -2
- package/lib/epoch/processPendingDeposits.js.map +1 -1
- package/lib/lightClient/spec/index.d.ts +22 -0
- package/lib/lightClient/spec/index.d.ts.map +1 -0
- package/lib/lightClient/spec/index.js +58 -0
- package/lib/lightClient/spec/index.js.map +1 -0
- package/lib/lightClient/spec/isBetterUpdate.d.ts +23 -0
- package/lib/lightClient/spec/isBetterUpdate.d.ts.map +1 -0
- package/lib/lightClient/spec/isBetterUpdate.js +66 -0
- package/lib/lightClient/spec/isBetterUpdate.js.map +1 -0
- package/lib/lightClient/spec/processLightClientUpdate.d.ts +12 -0
- package/lib/lightClient/spec/processLightClientUpdate.d.ts.map +1 -0
- package/lib/lightClient/spec/processLightClientUpdate.js +80 -0
- package/lib/lightClient/spec/processLightClientUpdate.js.map +1 -0
- package/lib/lightClient/spec/store.d.ts +45 -0
- package/lib/lightClient/spec/store.d.ts.map +1 -0
- package/lib/lightClient/spec/store.js +56 -0
- package/lib/lightClient/spec/store.js.map +1 -0
- package/lib/lightClient/spec/utils.d.ts +47 -0
- package/lib/lightClient/spec/utils.d.ts.map +1 -0
- package/lib/lightClient/spec/utils.js +197 -0
- package/lib/lightClient/spec/utils.js.map +1 -0
- package/lib/lightClient/spec/validateLightClientBootstrap.d.ts +4 -0
- package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -0
- package/lib/lightClient/spec/validateLightClientBootstrap.js +22 -0
- package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -0
- package/lib/lightClient/spec/validateLightClientUpdate.d.ts +5 -0
- package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -0
- package/lib/lightClient/spec/validateLightClientUpdate.js +88 -0
- package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -0
- package/lib/signatureSets/executionPayloadEnvelope.js +1 -1
- package/lib/signatureSets/executionPayloadEnvelope.js.map +1 -1
- package/lib/signatureSets/index.d.ts +1 -0
- package/lib/signatureSets/index.d.ts.map +1 -1
- package/lib/signatureSets/index.js +1 -0
- package/lib/signatureSets/index.js.map +1 -1
- package/lib/signatureSets/proposerPreferences.d.ts +4 -0
- package/lib/signatureSets/proposerPreferences.d.ts.map +1 -0
- package/lib/signatureSets/proposerPreferences.js +8 -0
- package/lib/signatureSets/proposerPreferences.js.map +1 -0
- package/lib/slot/upgradeStateToElectra.d.ts.map +1 -1
- package/lib/slot/upgradeStateToElectra.js +2 -2
- package/lib/slot/upgradeStateToElectra.js.map +1 -1
- package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
- package/lib/slot/upgradeStateToGloas.js +34 -28
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/stateView/beaconStateView.d.ts +20 -7
- package/lib/stateView/beaconStateView.d.ts.map +1 -1
- package/lib/stateView/beaconStateView.js +52 -16
- package/lib/stateView/beaconStateView.js.map +1 -1
- package/lib/stateView/interface.d.ts +14 -4
- package/lib/stateView/interface.d.ts.map +1 -1
- package/lib/stateView/interface.js.map +1 -1
- package/lib/util/computeAnchorCheckpoint.d.ts +1 -1
- package/lib/util/computeAnchorCheckpoint.d.ts.map +1 -1
- package/lib/util/computeAnchorCheckpoint.js +6 -19
- package/lib/util/computeAnchorCheckpoint.js.map +1 -1
- package/lib/util/epoch.d.ts.map +1 -1
- package/lib/util/epoch.js +6 -4
- package/lib/util/epoch.js.map +1 -1
- package/lib/util/gloas.d.ts +0 -1
- package/lib/util/gloas.d.ts.map +1 -1
- package/lib/util/gloas.js +0 -3
- package/lib/util/gloas.js.map +1 -1
- package/lib/util/index.d.ts +1 -0
- package/lib/util/index.d.ts.map +1 -1
- package/lib/util/index.js +1 -0
- package/lib/util/index.js.map +1 -1
- package/lib/util/loadState/loadState.js +4 -4
- package/lib/util/loadState/loadState.js.map +1 -1
- package/lib/util/pendingDepositsLookup.d.ts +40 -0
- package/lib/util/pendingDepositsLookup.d.ts.map +1 -0
- package/lib/util/pendingDepositsLookup.js +84 -0
- package/lib/util/pendingDepositsLookup.js.map +1 -0
- package/lib/util/shuffling.d.ts +6 -5
- package/lib/util/shuffling.d.ts.map +1 -1
- package/lib/util/shuffling.js +13 -15
- package/lib/util/shuffling.js.map +1 -1
- package/lib/util/validator.d.ts +14 -2
- package/lib/util/validator.d.ts.map +1 -1
- package/lib/util/validator.js +24 -2
- package/lib/util/validator.js.map +1 -1
- package/package.json +13 -8
- package/src/block/index.ts +12 -4
- package/src/block/processConsolidationRequest.ts +2 -1
- package/src/block/processDepositRequest.ts +29 -47
- package/src/block/processParentExecutionPayload.ts +117 -0
- package/src/block/processWithdrawals.ts +12 -4
- package/src/cache/epochCache.ts +3 -3
- package/src/epoch/processPendingDeposits.ts +5 -2
- package/src/lightClient/spec/index.ts +101 -0
- package/src/lightClient/spec/isBetterUpdate.ts +94 -0
- package/src/lightClient/spec/processLightClientUpdate.ts +119 -0
- package/src/lightClient/spec/store.ts +106 -0
- package/src/lightClient/spec/utils.ts +317 -0
- package/src/lightClient/spec/validateLightClientBootstrap.ts +39 -0
- package/src/lightClient/spec/validateLightClientUpdate.ts +145 -0
- package/src/signatureSets/executionPayloadEnvelope.ts +1 -1
- package/src/signatureSets/index.ts +1 -0
- package/src/signatureSets/proposerPreferences.ts +12 -0
- package/src/slot/upgradeStateToElectra.ts +4 -2
- package/src/slot/upgradeStateToGloas.ts +44 -44
- package/src/stateView/beaconStateView.ts +60 -25
- package/src/stateView/interface.ts +16 -7
- package/src/util/computeAnchorCheckpoint.ts +6 -19
- package/src/util/epoch.ts +13 -4
- package/src/util/gloas.ts +0 -4
- package/src/util/index.ts +1 -0
- package/src/util/loadState/loadState.ts +4 -4
- package/src/util/pendingDepositsLookup.ts +105 -0
- package/src/util/shuffling.ts +17 -15
- package/src/util/validator.ts +42 -2
- package/lib/block/processExecutionPayloadEnvelope.d.ts +0 -9
- package/lib/block/processExecutionPayloadEnvelope.d.ts.map +0 -1
- package/lib/block/processExecutionPayloadEnvelope.js +0 -106
- package/lib/block/processExecutionPayloadEnvelope.js.map +0 -1
- package/src/block/processExecutionPayloadEnvelope.ts +0 -175
package/src/block/index.ts
CHANGED
|
@@ -14,8 +14,8 @@ import {processBlockHeader} from "./processBlockHeader.js";
|
|
|
14
14
|
import {processEth1Data} from "./processEth1Data.js";
|
|
15
15
|
import {processExecutionPayload} from "./processExecutionPayload.js";
|
|
16
16
|
import {processExecutionPayloadBid} from "./processExecutionPayloadBid.js";
|
|
17
|
-
import {processExecutionPayloadEnvelope} from "./processExecutionPayloadEnvelope.js";
|
|
18
17
|
import {processOperations} from "./processOperations.js";
|
|
18
|
+
import {processParentExecutionPayload} from "./processParentExecutionPayload.js";
|
|
19
19
|
import {processPayloadAttestation} from "./processPayloadAttestation.js";
|
|
20
20
|
import {processRandao} from "./processRandao.js";
|
|
21
21
|
import {processSyncAggregate} from "./processSyncCommittee.js";
|
|
@@ -32,7 +32,7 @@ export {
|
|
|
32
32
|
processWithdrawals,
|
|
33
33
|
processExecutionPayloadBid,
|
|
34
34
|
processPayloadAttestation,
|
|
35
|
-
|
|
35
|
+
processParentExecutionPayload,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export * from "./externalData.js";
|
|
@@ -51,10 +51,16 @@ export function processBlock(
|
|
|
51
51
|
): void {
|
|
52
52
|
const {verifySignatures = true} = opts ?? {};
|
|
53
53
|
|
|
54
|
+
// Apply the parent's deferred payload effects before everything else. Must run before
|
|
55
|
+
// processBlockHeader and processExecutionPayloadBid so subsequent steps see the updated state.
|
|
56
|
+
if (fork >= ForkSeq.gloas) {
|
|
57
|
+
processParentExecutionPayload(state as CachedBeaconStateGloas, block as BeaconBlock<ForkPostGloas>);
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
processBlockHeader(state, block);
|
|
55
61
|
|
|
56
62
|
if (fork >= ForkSeq.gloas) {
|
|
57
|
-
//
|
|
63
|
+
// Parent payload's execution requests were already applied by processParentExecutionPayload above
|
|
58
64
|
processWithdrawals(fork, state as CachedBeaconStateGloas);
|
|
59
65
|
} else if (fork >= ForkSeq.capella) {
|
|
60
66
|
const fullOrBlindedPayload = getFullOrBlindedPayload(block);
|
|
@@ -67,7 +73,9 @@ export function processBlock(
|
|
|
67
73
|
|
|
68
74
|
// The call to the process_execution_payload must happen before the call to the process_randao as the former depends
|
|
69
75
|
// on the randao_mix computed with the reveal of the previous block.
|
|
70
|
-
//
|
|
76
|
+
// Post-gloas: process_execution_payload is not part of block processing. The parent's payload
|
|
77
|
+
// effects are applied earlier via processParentExecutionPayload, and each execution payload is
|
|
78
|
+
// verified out-of-band via verifyExecutionPayloadEnvelope when it arrives.
|
|
71
79
|
if (
|
|
72
80
|
fork < ForkSeq.gloas &&
|
|
73
81
|
fork >= ForkSeq.bellatrix &&
|
|
@@ -46,7 +46,8 @@ export function processConsolidationRequest(
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// If there is too little available consolidation churn limit, consolidation requests are ignored
|
|
49
|
-
|
|
49
|
+
const fork = state.config.getForkSeq(state.slot);
|
|
50
|
+
if (getConsolidationChurnLimit(fork, state.epochCtx) <= MIN_ACTIVATION_BALANCE) {
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {BeaconConfig} from "@lodestar/config";
|
|
2
1
|
import {FAR_FUTURE_EPOCH, ForkSeq, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
|
|
3
|
-
import {BLSPubkey, Bytes32,
|
|
2
|
+
import {BLSPubkey, Bytes32, UintNum64, electra, ssz} from "@lodestar/types";
|
|
4
3
|
import {toPubkeyHex} from "@lodestar/utils";
|
|
5
4
|
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
|
|
6
5
|
import {findBuilderIndexByPubkey, isBuilderWithdrawalCredential} from "../util/gloas.js";
|
|
7
6
|
import {computeEpochAtSlot, isValidatorKnown} from "../util/index.js";
|
|
7
|
+
import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
|
|
8
8
|
import {isValidDepositSignature} from "./processDeposit.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -76,53 +76,60 @@ function addBuilderToRegistry(
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// TODO GLOAS:
|
|
80
|
-
//
|
|
81
|
-
//
|
|
79
|
+
// TODO GLOAS: the PendingDepositsLookup is currently scoped to a single envelope of
|
|
80
|
+
// deposit-requests. We can track it as ephemeral within EpochCache and transfer to the next block
|
|
81
|
+
// transition to reuse cached signature verifications.
|
|
82
82
|
// See https://github.com/ChainSafe/lodestar/issues/9181
|
|
83
83
|
export function processDepositRequest(
|
|
84
84
|
fork: ForkSeq,
|
|
85
85
|
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
|
|
86
86
|
depositRequest: electra.DepositRequest,
|
|
87
|
-
|
|
87
|
+
pendingDepositsLookup?: PendingDepositsLookup
|
|
88
88
|
): void {
|
|
89
89
|
const {pubkey, withdrawalCredentials, amount, signature} = depositRequest;
|
|
90
90
|
|
|
91
|
-
// Check if this is a builder or validator deposit
|
|
92
91
|
if (fork >= ForkSeq.gloas) {
|
|
93
92
|
const stateGloas = state as CachedBeaconStateGloas;
|
|
94
|
-
const
|
|
95
|
-
pendingValidatorPubkeysCache ?? getPendingValidatorPubkeys(state.config, stateGloas);
|
|
93
|
+
const lookup = pendingDepositsLookup ?? PendingDepositsLookup.build(stateGloas);
|
|
96
94
|
const pubkeyHex = toPubkeyHex(pubkey);
|
|
97
95
|
const builderIndex = findBuilderIndexByPubkey(stateGloas, pubkey);
|
|
98
96
|
const validatorIndex = state.epochCtx.getValidatorIndex(pubkey);
|
|
99
97
|
|
|
100
|
-
// Regardless of the withdrawal credentials prefix, if a builder/validator
|
|
101
|
-
// already exists with this pubkey, apply the deposit to their balance
|
|
102
98
|
const isBuilder = builderIndex !== null;
|
|
103
99
|
const isValidator = isValidatorKnown(state, validatorIndex);
|
|
104
|
-
const isPendingValidator = pendingValidatorPubkeys.has(pubkeyHex);
|
|
105
100
|
|
|
106
|
-
if (isBuilder
|
|
107
|
-
//
|
|
101
|
+
if (isBuilder) {
|
|
102
|
+
// Top up an existing builder regardless of withdrawal credential prefix
|
|
108
103
|
applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot);
|
|
109
104
|
return;
|
|
110
105
|
}
|
|
111
106
|
|
|
112
|
-
//
|
|
113
|
-
// deposit requests for the same pubkey in this envelope must see it as a pending validator
|
|
107
|
+
// Only check the (expensive) "pending validator" condition when needed
|
|
114
108
|
if (
|
|
115
|
-
|
|
109
|
+
isBuilderWithdrawalCredential(withdrawalCredentials) &&
|
|
116
110
|
!isValidator &&
|
|
117
|
-
!
|
|
118
|
-
isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)
|
|
111
|
+
!lookup.hasPendingValidator(state.config, pubkeyHex)
|
|
119
112
|
) {
|
|
120
|
-
|
|
113
|
+
applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot);
|
|
114
|
+
return;
|
|
121
115
|
}
|
|
116
|
+
|
|
117
|
+
const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({
|
|
118
|
+
pubkey,
|
|
119
|
+
withdrawalCredentials,
|
|
120
|
+
amount,
|
|
121
|
+
signature,
|
|
122
|
+
slot: state.slot,
|
|
123
|
+
});
|
|
124
|
+
// Keep the lookup in sync with state.pendingDeposits so later deposit-requests
|
|
125
|
+
// in the same envelope see this deposit
|
|
126
|
+
lookup.add(pendingDeposit, pubkeyHex);
|
|
127
|
+
state.pendingDeposits.push(pendingDeposit);
|
|
128
|
+
return;
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
//
|
|
125
|
-
if (
|
|
131
|
+
// Pre-Gloas (Electra) path
|
|
132
|
+
if (state.depositRequestsStartIndex === UNSET_DEPOSIT_REQUESTS_START_INDEX) {
|
|
126
133
|
state.depositRequestsStartIndex = depositRequest.index;
|
|
127
134
|
}
|
|
128
135
|
|
|
@@ -136,28 +143,3 @@ export function processDepositRequest(
|
|
|
136
143
|
});
|
|
137
144
|
state.pendingDeposits.push(pendingDeposit);
|
|
138
145
|
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Build a set of pubkeys (hex-encoded) from pending deposits that have valid signatures.
|
|
142
|
-
* This is computed once and passed to each processDepositRequest call to avoid
|
|
143
|
-
* repeatedly iterating state.pendingDeposits.
|
|
144
|
-
*
|
|
145
|
-
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/beacon-chain.md#new-is_pending_validator
|
|
146
|
-
*/
|
|
147
|
-
export function getPendingValidatorPubkeys(config: BeaconConfig, state: CachedBeaconStateGloas): Set<PubkeyHex> {
|
|
148
|
-
const result = new Set<PubkeyHex>();
|
|
149
|
-
for (const pendingDeposit of state.pendingDeposits.getAllReadonly()) {
|
|
150
|
-
if (
|
|
151
|
-
isValidDepositSignature(
|
|
152
|
-
config,
|
|
153
|
-
pendingDeposit.pubkey,
|
|
154
|
-
pendingDeposit.withdrawalCredentials,
|
|
155
|
-
pendingDeposit.amount,
|
|
156
|
-
pendingDeposit.signature
|
|
157
|
-
)
|
|
158
|
-
) {
|
|
159
|
-
result.add(toPubkeyHex(pendingDeposit.pubkey));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return result;
|
|
163
|
-
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {ForkPostGloas, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
2
|
+
import {BeaconBlock, electra, ssz} from "@lodestar/types";
|
|
3
|
+
import {byteArrayEquals, toRootHex} from "@lodestar/utils";
|
|
4
|
+
import {CachedBeaconStateGloas} from "../types.js";
|
|
5
|
+
import {computeEpochAtSlot} from "../util/epoch.js";
|
|
6
|
+
import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
|
|
7
|
+
import {processConsolidationRequest} from "./processConsolidationRequest.js";
|
|
8
|
+
import {processDepositRequest} from "./processDepositRequest.js";
|
|
9
|
+
import {processWithdrawalRequest} from "./processWithdrawalRequest.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Process parent execution payload effects as the first step of processBlock.
|
|
13
|
+
*
|
|
14
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-process_parent_execution_payload
|
|
15
|
+
*/
|
|
16
|
+
export function processParentExecutionPayload(state: CachedBeaconStateGloas, block: BeaconBlock<ForkPostGloas>): void {
|
|
17
|
+
const bid = block.body.signedExecutionPayloadBid.message;
|
|
18
|
+
const parentBid = state.latestExecutionPayloadBid;
|
|
19
|
+
const requests = block.body.parentExecutionRequests;
|
|
20
|
+
|
|
21
|
+
const isParentBlockFull = byteArrayEquals(bid.parentBlockHash, parentBid.blockHash);
|
|
22
|
+
if (!isParentBlockFull) {
|
|
23
|
+
// Parent was EMPTY -- no execution requests expected
|
|
24
|
+
assertEmptyExecutionRequests(requests);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Parent was FULL -- verify the bid commitment and apply the payload
|
|
29
|
+
const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(requests);
|
|
30
|
+
if (!byteArrayEquals(requestsRoot, parentBid.executionRequestsRoot)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Parent execution requests root mismatch actual=${toRootHex(requestsRoot)} expected=${toRootHex(parentBid.executionRequestsRoot)}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
applyParentExecutionPayload(state, requests);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Process the parent's execution requests, queue the builder payment, update payload availability,
|
|
41
|
+
* and update the latest block hash.
|
|
42
|
+
*
|
|
43
|
+
* Called from processParentExecutionPayload during block processing, and from the validator during
|
|
44
|
+
* block production before computing withdrawals.
|
|
45
|
+
*
|
|
46
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-apply_parent_execution_payload
|
|
47
|
+
*/
|
|
48
|
+
export function applyParentExecutionPayload(state: CachedBeaconStateGloas, requests: electra.ExecutionRequests): void {
|
|
49
|
+
const fork = state.config.getForkSeq(state.slot);
|
|
50
|
+
const parentBid = state.latestExecutionPayloadBid;
|
|
51
|
+
const parentSlot = parentBid.slot;
|
|
52
|
+
const parentEpoch = computeEpochAtSlot(parentSlot);
|
|
53
|
+
const currentEpoch = computeEpochAtSlot(state.slot);
|
|
54
|
+
|
|
55
|
+
// Process execution requests from parent's payload. The execution
|
|
56
|
+
// requests are processed at state.slot (child's slot), not the parent's slot.
|
|
57
|
+
if (requests.deposits.length > 0) {
|
|
58
|
+
const pendingDepositsLookup = PendingDepositsLookup.build(state);
|
|
59
|
+
for (const deposit of requests.deposits) {
|
|
60
|
+
processDepositRequest(fork, state, deposit, pendingDepositsLookup);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const withdrawal of requests.withdrawals) {
|
|
65
|
+
processWithdrawalRequest(fork, state, withdrawal);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const consolidation of requests.consolidations) {
|
|
69
|
+
processConsolidationRequest(state, consolidation);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Settle the builder payment
|
|
73
|
+
if (parentEpoch === currentEpoch) {
|
|
74
|
+
settleBuilderPayment(state, SLOTS_PER_EPOCH + (parentSlot % SLOTS_PER_EPOCH));
|
|
75
|
+
} else if (parentEpoch === currentEpoch - 1) {
|
|
76
|
+
settleBuilderPayment(state, parentSlot % SLOTS_PER_EPOCH);
|
|
77
|
+
} else if (parentBid.value > 0) {
|
|
78
|
+
// Parent is older than the previous epoch, its payment entry has been evicted from
|
|
79
|
+
// builder_pending_payments. Append the withdrawal directly.
|
|
80
|
+
state.builderPendingWithdrawals.push(
|
|
81
|
+
ssz.gloas.BuilderPendingWithdrawal.toViewDU({
|
|
82
|
+
feeRecipient: parentBid.feeRecipient,
|
|
83
|
+
amount: parentBid.value,
|
|
84
|
+
builderIndex: parentBid.builderIndex,
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Update parent payload availability and latest block hash
|
|
90
|
+
state.executionPayloadAvailability.set(parentSlot % SLOTS_PER_HISTORICAL_ROOT, true);
|
|
91
|
+
state.latestBlockHash = parentBid.blockHash;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Settle a builder payment at the given index: move its withdrawal (if any) to the
|
|
96
|
+
* pending withdrawals list and clear the payment slot.
|
|
97
|
+
*
|
|
98
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-settle_builder_payment
|
|
99
|
+
*/
|
|
100
|
+
function settleBuilderPayment(state: CachedBeaconStateGloas, paymentIndex: number): void {
|
|
101
|
+
if (paymentIndex >= state.builderPendingPayments.length) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Invalid builder payment index paymentIndex=${paymentIndex} limit=${state.builderPendingPayments.length}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const payment = state.builderPendingPayments.get(paymentIndex).clone();
|
|
107
|
+
if (payment.withdrawal.amount > 0) {
|
|
108
|
+
state.builderPendingWithdrawals.push(payment.withdrawal);
|
|
109
|
+
}
|
|
110
|
+
state.builderPendingPayments.set(paymentIndex, ssz.gloas.BuilderPendingPayment.defaultViewDU());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function assertEmptyExecutionRequests(requests: electra.ExecutionRequests): void {
|
|
114
|
+
if (requests.deposits.length !== 0 || requests.withdrawals.length !== 0 || requests.consolidations.length !== 0) {
|
|
115
|
+
throw new Error("Parent execution requests must be empty when parent block is EMPTY");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
convertBuilderIndexToValidatorIndex,
|
|
16
16
|
convertValidatorIndexToBuilderIndex,
|
|
17
17
|
isBuilderIndex,
|
|
18
|
-
isParentBlockFull,
|
|
19
18
|
} from "../util/gloas.js";
|
|
20
19
|
import {
|
|
21
20
|
decreaseBalance,
|
|
@@ -32,8 +31,15 @@ export function processWithdrawals(
|
|
|
32
31
|
payload?: capella.FullOrBlindedExecutionPayload
|
|
33
32
|
): void {
|
|
34
33
|
// Return early if the parent block is empty
|
|
35
|
-
if (fork >= ForkSeq.gloas
|
|
36
|
-
|
|
34
|
+
if (fork >= ForkSeq.gloas) {
|
|
35
|
+
const stateGloas = state as CachedBeaconStateGloas;
|
|
36
|
+
const isParentBlockEmpty = !byteArrayEquals(
|
|
37
|
+
stateGloas.latestBlockHash,
|
|
38
|
+
stateGloas.latestExecutionPayloadBid.blockHash
|
|
39
|
+
);
|
|
40
|
+
if (isParentBlockEmpty) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
// processedBuilderWithdrawalsCount is withdrawals coming from builder payment since gloas (EIP-7732)
|
|
@@ -48,7 +54,9 @@ export function processWithdrawals(
|
|
|
48
54
|
} = getExpectedWithdrawals(fork, state);
|
|
49
55
|
const numWithdrawals = expectedWithdrawals.length;
|
|
50
56
|
|
|
51
|
-
//
|
|
57
|
+
// Pre-gloas verifies the payload's withdrawals against expectedWithdrawals here.
|
|
58
|
+
// Post-gloas, the payload arrives later as an envelope and that consistency check
|
|
59
|
+
// happens in verifyExecutionPayloadEnvelope against state.payloadExpectedWithdrawals.
|
|
52
60
|
if (fork < ForkSeq.gloas) {
|
|
53
61
|
if (payload === undefined) {
|
|
54
62
|
throw Error("payload is required for pre-gloas processWithdrawals");
|
package/src/cache/epochCache.ts
CHANGED
|
@@ -38,9 +38,9 @@ import {
|
|
|
38
38
|
computeEpochAtSlot,
|
|
39
39
|
computeProposers,
|
|
40
40
|
computeSyncPeriodAtEpoch,
|
|
41
|
-
getActivationChurnLimit,
|
|
42
41
|
getChurnLimit,
|
|
43
42
|
getSeed,
|
|
43
|
+
getValidatorActivationChurnLimit,
|
|
44
44
|
isActiveValidator,
|
|
45
45
|
isAggregatorFromCommitteeLength,
|
|
46
46
|
} from "../util/index.js";
|
|
@@ -478,7 +478,7 @@ export class EpochCache {
|
|
|
478
478
|
// the first block of the epoch process_block() call. So churnLimit must be computed at the end of the before epoch
|
|
479
479
|
// transition and the result is valid until the end of the next epoch transition
|
|
480
480
|
const churnLimit = getChurnLimit(config, currentShuffling.activeIndices.length);
|
|
481
|
-
const activationChurnLimit =
|
|
481
|
+
const activationChurnLimit = getValidatorActivationChurnLimit(
|
|
482
482
|
config,
|
|
483
483
|
config.getForkSeq(state.slot),
|
|
484
484
|
currentShuffling.activeIndices.length
|
|
@@ -652,7 +652,7 @@ export class EpochCache {
|
|
|
652
652
|
// the first block of the epoch process_block() call. So churnLimit must be computed at the end of the before epoch
|
|
653
653
|
// transition and the result is valid until the end of the next epoch transition
|
|
654
654
|
this.churnLimit = getChurnLimit(this.config, this.currentShuffling.activeIndices.length);
|
|
655
|
-
this.activationChurnLimit =
|
|
655
|
+
this.activationChurnLimit = getValidatorActivationChurnLimit(
|
|
656
656
|
this.config,
|
|
657
657
|
this.config.getForkSeq(state.slot),
|
|
658
658
|
this.currentShuffling.activeIndices.length
|
|
@@ -5,7 +5,7 @@ import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js";
|
|
|
5
5
|
import {increaseBalance} from "../util/balance.js";
|
|
6
6
|
import {hasCompoundingWithdrawalCredential, isValidatorKnown} from "../util/electra.js";
|
|
7
7
|
import {computeStartSlotAtEpoch} from "../util/epoch.js";
|
|
8
|
-
import {getActivationExitChurnLimit} from "../util/validator.js";
|
|
8
|
+
import {getActivationChurnLimit, getActivationExitChurnLimit} from "../util/validator.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Starting from Electra:
|
|
@@ -17,8 +17,11 @@ import {getActivationExitChurnLimit} from "../util/validator.js";
|
|
|
17
17
|
* TODO Electra: Update ssz library to support batch push to `pendingDeposits`
|
|
18
18
|
*/
|
|
19
19
|
export function processPendingDeposits(state: CachedBeaconStateElectra, cache: EpochTransitionCache): void {
|
|
20
|
+
const fork = state.config.getForkSeq(state.slot);
|
|
20
21
|
const nextEpoch = state.epochCtx.epoch + 1;
|
|
21
|
-
const
|
|
22
|
+
const churnLimit =
|
|
23
|
+
fork >= ForkSeq.gloas ? getActivationChurnLimit(state.epochCtx) : getActivationExitChurnLimit(state.epochCtx);
|
|
24
|
+
const availableForProcessing = state.depositBalanceToConsume + BigInt(churnLimit);
|
|
22
25
|
let processedAmount = 0;
|
|
23
26
|
let nextDepositIndex = 0;
|
|
24
27
|
const depositsToPostpone = [];
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
2
|
+
import {UPDATE_TIMEOUT} from "@lodestar/params";
|
|
3
|
+
import {
|
|
4
|
+
LightClientBootstrap,
|
|
5
|
+
LightClientFinalityUpdate,
|
|
6
|
+
LightClientOptimisticUpdate,
|
|
7
|
+
LightClientUpdate,
|
|
8
|
+
Slot,
|
|
9
|
+
} from "@lodestar/types";
|
|
10
|
+
import {computeSyncPeriodAtSlot} from "../../util/epoch.js";
|
|
11
|
+
import {
|
|
12
|
+
type ProcessUpdateOpts,
|
|
13
|
+
getSyncCommitteeAtPeriod,
|
|
14
|
+
processLightClientUpdate,
|
|
15
|
+
} from "./processLightClientUpdate.js";
|
|
16
|
+
import {type ILightClientStore, LightClientStore, type LightClientStoreEvents} from "./store.js";
|
|
17
|
+
import {ZERO_HEADER, ZERO_SYNC_COMMITTEE, getZeroFinalityBranch, getZeroSyncCommitteeBranch} from "./utils.js";
|
|
18
|
+
|
|
19
|
+
export type {LightClientUpdateSummary} from "./isBetterUpdate.js";
|
|
20
|
+
export {isBetterUpdate, toLightClientUpdateSummary} from "./isBetterUpdate.js";
|
|
21
|
+
export {
|
|
22
|
+
type ProcessUpdateOpts,
|
|
23
|
+
getSyncCommitteeAtPeriod,
|
|
24
|
+
isSafeLightClientUpdate,
|
|
25
|
+
processLightClientUpdate,
|
|
26
|
+
} from "./processLightClientUpdate.js";
|
|
27
|
+
export {
|
|
28
|
+
type ILightClientStore,
|
|
29
|
+
LightClientStore,
|
|
30
|
+
type LightClientStoreEvents,
|
|
31
|
+
type LightClientUpdateWithSummary,
|
|
32
|
+
type SyncCommitteeFast,
|
|
33
|
+
} from "./store.js";
|
|
34
|
+
export {
|
|
35
|
+
getSafetyThreshold,
|
|
36
|
+
isFinalityUpdate,
|
|
37
|
+
isSyncCommitteeUpdate,
|
|
38
|
+
isValidLightClientHeader,
|
|
39
|
+
normalizeMerkleBranch,
|
|
40
|
+
upgradeLightClientFinalityUpdate,
|
|
41
|
+
upgradeLightClientHeader,
|
|
42
|
+
upgradeLightClientOptimisticUpdate,
|
|
43
|
+
upgradeLightClientStore,
|
|
44
|
+
upgradeLightClientUpdate,
|
|
45
|
+
} from "./utils.js";
|
|
46
|
+
export {validateLightClientBootstrap} from "./validateLightClientBootstrap.js";
|
|
47
|
+
export {validateLightClientUpdate} from "./validateLightClientUpdate.js";
|
|
48
|
+
|
|
49
|
+
export class LightclientSpec {
|
|
50
|
+
readonly store: ILightClientStore;
|
|
51
|
+
readonly config: BeaconConfig;
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
config: BeaconConfig,
|
|
55
|
+
private readonly opts: ProcessUpdateOpts & LightClientStoreEvents,
|
|
56
|
+
bootstrap: LightClientBootstrap
|
|
57
|
+
) {
|
|
58
|
+
this.store = new LightClientStore(config, bootstrap, opts);
|
|
59
|
+
this.config = config;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onUpdate(currentSlot: Slot, update: LightClientUpdate): void {
|
|
63
|
+
processLightClientUpdate(this.config, this.store, currentSlot, this.opts, update);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onFinalityUpdate(currentSlot: Slot, finalityUpdate: LightClientFinalityUpdate): void {
|
|
67
|
+
this.onUpdate(currentSlot, {
|
|
68
|
+
attestedHeader: finalityUpdate.attestedHeader,
|
|
69
|
+
nextSyncCommittee: ZERO_SYNC_COMMITTEE,
|
|
70
|
+
nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(this.config.getForkName(finalityUpdate.signatureSlot)),
|
|
71
|
+
finalizedHeader: finalityUpdate.finalizedHeader,
|
|
72
|
+
finalityBranch: finalityUpdate.finalityBranch,
|
|
73
|
+
syncAggregate: finalityUpdate.syncAggregate,
|
|
74
|
+
signatureSlot: finalityUpdate.signatureSlot,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onOptimisticUpdate(currentSlot: Slot, optimisticUpdate: LightClientOptimisticUpdate): void {
|
|
79
|
+
this.onUpdate(currentSlot, {
|
|
80
|
+
attestedHeader: optimisticUpdate.attestedHeader,
|
|
81
|
+
nextSyncCommittee: ZERO_SYNC_COMMITTEE,
|
|
82
|
+
nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(this.config.getForkName(optimisticUpdate.signatureSlot)),
|
|
83
|
+
finalizedHeader: {beacon: ZERO_HEADER},
|
|
84
|
+
finalityBranch: getZeroFinalityBranch(this.config.getForkName(optimisticUpdate.signatureSlot)),
|
|
85
|
+
syncAggregate: optimisticUpdate.syncAggregate,
|
|
86
|
+
signatureSlot: optimisticUpdate.signatureSlot,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
forceUpdate(currentSlot: Slot): void {
|
|
91
|
+
for (const bestValidUpdate of this.store.bestValidUpdates.values()) {
|
|
92
|
+
if (currentSlot > bestValidUpdate.update.finalizedHeader.beacon.slot + UPDATE_TIMEOUT) {
|
|
93
|
+
const updatePeriod = computeSyncPeriodAtSlot(bestValidUpdate.update.signatureSlot);
|
|
94
|
+
// Simulate process_light_client_store_force_update() by forcing to apply a bestValidUpdate
|
|
95
|
+
// https://github.com/ethereum/consensus-specs/blob/a57e15636013eeba3610ff3ade41781dba1bb0cd/specs/altair/light-client/sync-protocol.md?plain=1#L394
|
|
96
|
+
// Call for `updatePeriod + 1` to force the update at `update.signatureSlot` to be applied
|
|
97
|
+
getSyncCommitteeAtPeriod(this.store, updatePeriod + 1, this.opts);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {SYNC_COMMITTEE_SIZE} from "@lodestar/params";
|
|
2
|
+
import {LightClientUpdate, Slot} from "@lodestar/types";
|
|
3
|
+
import {computeSyncPeriodAtSlot} from "../../util/epoch.js";
|
|
4
|
+
import {isFinalityUpdate, isSyncCommitteeUpdate, sumBits} from "./utils.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wrapper type for `isBetterUpdate()` so we can apply its logic without requiring the full LightClientUpdate type.
|
|
8
|
+
*/
|
|
9
|
+
export type LightClientUpdateSummary = {
|
|
10
|
+
activeParticipants: number;
|
|
11
|
+
attestedHeaderSlot: Slot;
|
|
12
|
+
signatureSlot: Slot;
|
|
13
|
+
finalizedHeaderSlot: Slot;
|
|
14
|
+
/** `if update.next_sync_committee_branch != [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]` */
|
|
15
|
+
isSyncCommitteeUpdate: boolean;
|
|
16
|
+
/** `if update.finality_branch != [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]` */
|
|
17
|
+
isFinalityUpdate: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the update with more bits. On ties, prevUpdate is the better
|
|
22
|
+
*
|
|
23
|
+
* https://github.com/ethereum/consensus-specs/blob/be3c774069e16e89145660be511c1b183056017e/specs/altair/light-client/sync-protocol.md#is_better_update
|
|
24
|
+
*/
|
|
25
|
+
export function isBetterUpdate(newUpdate: LightClientUpdateSummary, oldUpdate: LightClientUpdateSummary): boolean {
|
|
26
|
+
// Compare supermajority (> 2/3) sync committee participation
|
|
27
|
+
const newNumActiveParticipants = newUpdate.activeParticipants;
|
|
28
|
+
const oldNumActiveParticipants = oldUpdate.activeParticipants;
|
|
29
|
+
const newHasSupermajority = newNumActiveParticipants * 3 >= SYNC_COMMITTEE_SIZE * 2;
|
|
30
|
+
const oldHasSupermajority = oldNumActiveParticipants * 3 >= SYNC_COMMITTEE_SIZE * 2;
|
|
31
|
+
if (newHasSupermajority !== oldHasSupermajority) {
|
|
32
|
+
return newHasSupermajority;
|
|
33
|
+
}
|
|
34
|
+
if (!newHasSupermajority && newNumActiveParticipants !== oldNumActiveParticipants) {
|
|
35
|
+
return newNumActiveParticipants > oldNumActiveParticipants;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Compare presence of relevant sync committee
|
|
39
|
+
const newHasRelevantSyncCommittee =
|
|
40
|
+
newUpdate.isSyncCommitteeUpdate &&
|
|
41
|
+
computeSyncPeriodAtSlot(newUpdate.attestedHeaderSlot) === computeSyncPeriodAtSlot(newUpdate.signatureSlot);
|
|
42
|
+
const oldHasRelevantSyncCommittee =
|
|
43
|
+
oldUpdate.isSyncCommitteeUpdate &&
|
|
44
|
+
computeSyncPeriodAtSlot(oldUpdate.attestedHeaderSlot) === computeSyncPeriodAtSlot(oldUpdate.signatureSlot);
|
|
45
|
+
if (newHasRelevantSyncCommittee !== oldHasRelevantSyncCommittee) {
|
|
46
|
+
return newHasRelevantSyncCommittee;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Compare indication of any finality
|
|
50
|
+
const newHasFinality = newUpdate.isFinalityUpdate;
|
|
51
|
+
const oldHasFinality = oldUpdate.isFinalityUpdate;
|
|
52
|
+
if (newHasFinality !== oldHasFinality) {
|
|
53
|
+
return newHasFinality;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Compare sync committee finality
|
|
57
|
+
if (newHasFinality) {
|
|
58
|
+
const newHasSyncCommitteeFinality =
|
|
59
|
+
computeSyncPeriodAtSlot(newUpdate.finalizedHeaderSlot) === computeSyncPeriodAtSlot(newUpdate.attestedHeaderSlot);
|
|
60
|
+
const oldHasSyncCommitteeFinality =
|
|
61
|
+
computeSyncPeriodAtSlot(oldUpdate.finalizedHeaderSlot) === computeSyncPeriodAtSlot(oldUpdate.attestedHeaderSlot);
|
|
62
|
+
if (newHasSyncCommitteeFinality !== oldHasSyncCommitteeFinality) {
|
|
63
|
+
return newHasSyncCommitteeFinality;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Tiebreaker 1: Sync committee participation beyond supermajority
|
|
68
|
+
if (newNumActiveParticipants !== oldNumActiveParticipants) {
|
|
69
|
+
return newNumActiveParticipants > oldNumActiveParticipants;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Tiebreaker 2: Prefer older data (fewer changes to best)
|
|
73
|
+
if (newUpdate.attestedHeaderSlot !== oldUpdate.attestedHeaderSlot) {
|
|
74
|
+
return newUpdate.attestedHeaderSlot < oldUpdate.attestedHeaderSlot;
|
|
75
|
+
}
|
|
76
|
+
return newUpdate.signatureSlot < oldUpdate.signatureSlot;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isSafeLightClientUpdate(update: LightClientUpdateSummary): boolean {
|
|
80
|
+
return (
|
|
81
|
+
update.activeParticipants * 3 >= SYNC_COMMITTEE_SIZE * 2 && update.isFinalityUpdate && update.isSyncCommitteeUpdate
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function toLightClientUpdateSummary(update: LightClientUpdate): LightClientUpdateSummary {
|
|
86
|
+
return {
|
|
87
|
+
activeParticipants: sumBits(update.syncAggregate.syncCommitteeBits),
|
|
88
|
+
attestedHeaderSlot: update.attestedHeader.beacon.slot,
|
|
89
|
+
signatureSlot: update.signatureSlot,
|
|
90
|
+
finalizedHeaderSlot: update.finalizedHeader.beacon.slot,
|
|
91
|
+
isSyncCommitteeUpdate: isSyncCommitteeUpdate(update),
|
|
92
|
+
isFinalityUpdate: isFinalityUpdate(update),
|
|
93
|
+
};
|
|
94
|
+
}
|