@lodestar/state-transition 1.44.0-dev.6ef8199cfa → 1.44.0-dev.762dd5f186
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 -0
- package/lib/block/index.d.ts.map +1 -1
- package/lib/block/index.js +3 -1
- package/lib/block/index.js.map +1 -1
- package/lib/block/processBuilderDepositRequest.d.ts +10 -0
- package/lib/block/processBuilderDepositRequest.d.ts.map +1 -0
- package/lib/block/processBuilderDepositRequest.js +27 -0
- package/lib/block/processBuilderDepositRequest.js.map +1 -0
- package/lib/block/processBuilderExitRequest.d.ts +13 -0
- package/lib/block/processBuilderExitRequest.d.ts.map +1 -0
- package/lib/block/processBuilderExitRequest.js +29 -0
- package/lib/block/processBuilderExitRequest.js.map +1 -0
- package/lib/block/processDepositRequest.d.ts +2 -8
- package/lib/block/processDepositRequest.d.ts.map +1 -1
- package/lib/block/processDepositRequest.js +3 -98
- package/lib/block/processDepositRequest.js.map +1 -1
- package/lib/block/processExecutionPayloadBid.d.ts +2 -3
- package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
- package/lib/block/processExecutionPayloadBid.js +16 -8
- package/lib/block/processExecutionPayloadBid.js.map +1 -1
- package/lib/block/processOperations.d.ts.map +1 -1
- package/lib/block/processOperations.js +3 -2
- package/lib/block/processOperations.js.map +1 -1
- package/lib/block/processParentExecutionPayload.d.ts +2 -2
- package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
- package/lib/block/processParentExecutionPayload.js +16 -8
- package/lib/block/processParentExecutionPayload.js.map +1 -1
- package/lib/block/processProposerSlashing.d.ts.map +1 -1
- package/lib/block/processProposerSlashing.js +9 -1
- package/lib/block/processProposerSlashing.js.map +1 -1
- package/lib/block/processVoluntaryExit.d.ts +1 -1
- package/lib/block/processVoluntaryExit.d.ts.map +1 -1
- package/lib/block/processVoluntaryExit.js +1 -35
- package/lib/block/processVoluntaryExit.js.map +1 -1
- package/lib/cache/epochCache.d.ts.map +1 -1
- package/lib/cache/epochCache.js +10 -7
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/epoch/processPendingDeposits.d.ts.map +1 -1
- package/lib/epoch/processPendingDeposits.js +4 -3
- package/lib/epoch/processPendingDeposits.js.map +1 -1
- package/lib/epoch/processProposerLookahead.d.ts.map +1 -1
- package/lib/epoch/processProposerLookahead.js +7 -2
- package/lib/epoch/processProposerLookahead.js.map +1 -1
- package/lib/slot/upgradeStateToGloas.js +35 -29
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/stateView/beaconStateView.d.ts +5 -2
- package/lib/stateView/beaconStateView.d.ts.map +1 -1
- package/lib/stateView/beaconStateView.js +14 -0
- package/lib/stateView/beaconStateView.js.map +1 -1
- package/lib/stateView/interface.d.ts +30 -2
- package/lib/stateView/interface.d.ts.map +1 -1
- package/lib/stateView/interface.js.map +1 -1
- package/lib/stateView/nativeBeaconStateView.d.ts +230 -0
- package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -0
- package/lib/stateView/nativeBeaconStateView.js +717 -0
- package/lib/stateView/nativeBeaconStateView.js.map +1 -0
- package/lib/stateView/stateViewFactory.d.ts.map +1 -1
- package/lib/stateView/stateViewFactory.js +2 -0
- package/lib/stateView/stateViewFactory.js.map +1 -1
- package/lib/util/epochShuffling.js +1 -1
- package/lib/util/epochShuffling.js.map +1 -1
- package/lib/util/gloas.d.ts +31 -0
- package/lib/util/gloas.d.ts.map +1 -1
- package/lib/util/gloas.js +83 -1
- package/lib/util/gloas.js.map +1 -1
- package/lib/util/shuffling.d.ts +2 -2
- package/lib/util/shuffling.d.ts.map +1 -1
- package/lib/util/shuffling.js +4 -3
- package/lib/util/shuffling.js.map +1 -1
- package/package.json +7 -8
- package/src/block/index.ts +6 -1
- package/src/block/processBuilderDepositRequest.ts +43 -0
- package/src/block/processBuilderExitRequest.ts +39 -0
- package/src/block/processDepositRequest.ts +4 -124
- package/src/block/processExecutionPayloadBid.ts +24 -9
- package/src/block/processOperations.ts +3 -2
- package/src/block/processParentExecutionPayload.ts +23 -11
- package/src/block/processProposerSlashing.ts +9 -4
- package/src/block/processVoluntaryExit.ts +2 -61
- package/src/cache/epochCache.ts +10 -7
- package/src/epoch/processPendingDeposits.ts +2 -0
- package/src/epoch/processProposerLookahead.ts +8 -1
- package/src/slot/upgradeStateToGloas.ts +49 -33
- package/src/stateView/beaconStateView.ts +18 -1
- package/src/stateView/interface.ts +34 -1
- package/src/stateView/nativeBeaconStateView.ts +915 -0
- package/src/stateView/stateViewFactory.ts +2 -0
- package/src/util/epochShuffling.ts +1 -1
- package/src/util/gloas.ts +104 -1
- package/src/util/shuffling.ts +4 -3
|
@@ -1,135 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {toPubkeyHex} from "@lodestar/utils";
|
|
1
|
+
import {ForkSeq, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
|
|
2
|
+
import {electra, ssz} from "@lodestar/types";
|
|
4
3
|
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
|
|
5
|
-
import {findBuilderIndexByPubkey, isBuilderWithdrawalCredential} from "../util/gloas.js";
|
|
6
|
-
import {computeEpochAtSlot, isValidatorKnown} from "../util/index.js";
|
|
7
|
-
import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
|
|
8
|
-
import {isValidDepositSignature} from "./processDeposit.js";
|
|
9
4
|
|
|
10
|
-
/**
|
|
11
|
-
* Apply a deposit for a builder. Either increases balance for existing builder or adds new builder to registry.
|
|
12
|
-
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-apply_deposit_for_builder
|
|
13
|
-
*/
|
|
14
|
-
export function applyDepositForBuilder(
|
|
15
|
-
state: CachedBeaconStateGloas,
|
|
16
|
-
pubkey: BLSPubkey,
|
|
17
|
-
withdrawalCredentials: Bytes32,
|
|
18
|
-
amount: UintNum64,
|
|
19
|
-
signature: Bytes32,
|
|
20
|
-
slot: UintNum64
|
|
21
|
-
): void {
|
|
22
|
-
const builderIndex = findBuilderIndexByPubkey(state, pubkey);
|
|
23
|
-
|
|
24
|
-
if (builderIndex !== null) {
|
|
25
|
-
// Existing builder - increase balance
|
|
26
|
-
const builder = state.builders.get(builderIndex);
|
|
27
|
-
builder.balance += amount;
|
|
28
|
-
} else {
|
|
29
|
-
// New builder - verify signature and add to registry
|
|
30
|
-
if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) {
|
|
31
|
-
addBuilderToRegistry(state, pubkey, withdrawalCredentials, amount, slot);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Add a new builder to the builders registry.
|
|
38
|
-
* Reuses slots from exited and fully withdrawn builders if available.
|
|
39
|
-
*/
|
|
40
|
-
function addBuilderToRegistry(
|
|
41
|
-
state: CachedBeaconStateGloas,
|
|
42
|
-
pubkey: BLSPubkey,
|
|
43
|
-
withdrawalCredentials: Bytes32,
|
|
44
|
-
amount: UintNum64,
|
|
45
|
-
slot: UintNum64
|
|
46
|
-
): void {
|
|
47
|
-
const currentEpoch = computeEpochAtSlot(state.slot);
|
|
48
|
-
const depositEpoch = computeEpochAtSlot(slot);
|
|
49
|
-
|
|
50
|
-
// Try to find a reusable slot from an exited builder with zero balance
|
|
51
|
-
let builderIndex = state.builders.length;
|
|
52
|
-
for (let i = 0; i < state.builders.length; i++) {
|
|
53
|
-
const builder = state.builders.getReadonly(i);
|
|
54
|
-
if (builder.withdrawableEpoch <= currentEpoch && builder.balance === 0) {
|
|
55
|
-
builderIndex = i;
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Create new builder
|
|
61
|
-
const newBuilder = ssz.gloas.Builder.toViewDU({
|
|
62
|
-
pubkey,
|
|
63
|
-
version: withdrawalCredentials[0],
|
|
64
|
-
executionAddress: withdrawalCredentials.subarray(12),
|
|
65
|
-
balance: amount,
|
|
66
|
-
depositEpoch: depositEpoch,
|
|
67
|
-
withdrawableEpoch: FAR_FUTURE_EPOCH,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if (builderIndex < state.builders.length) {
|
|
71
|
-
// Reuse existing slot
|
|
72
|
-
state.builders.set(builderIndex, newBuilder);
|
|
73
|
-
} else {
|
|
74
|
-
// Append to end
|
|
75
|
-
state.builders.push(newBuilder);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
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
|
-
// See https://github.com/ChainSafe/lodestar/issues/9181
|
|
83
5
|
export function processDepositRequest(
|
|
84
6
|
fork: ForkSeq,
|
|
85
7
|
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
|
|
86
|
-
depositRequest: electra.DepositRequest
|
|
87
|
-
pendingDepositsLookup?: PendingDepositsLookup
|
|
8
|
+
depositRequest: electra.DepositRequest
|
|
88
9
|
): void {
|
|
89
10
|
const {pubkey, withdrawalCredentials, amount, signature} = depositRequest;
|
|
90
11
|
|
|
91
|
-
if (fork
|
|
92
|
-
const stateGloas = state as CachedBeaconStateGloas;
|
|
93
|
-
const lookup = pendingDepositsLookup ?? PendingDepositsLookup.build(stateGloas);
|
|
94
|
-
const pubkeyHex = toPubkeyHex(pubkey);
|
|
95
|
-
const builderIndex = findBuilderIndexByPubkey(stateGloas, pubkey);
|
|
96
|
-
const validatorIndex = state.epochCtx.getValidatorIndex(pubkey);
|
|
97
|
-
|
|
98
|
-
const isBuilder = builderIndex !== null;
|
|
99
|
-
const isValidator = isValidatorKnown(state, validatorIndex);
|
|
100
|
-
|
|
101
|
-
if (isBuilder) {
|
|
102
|
-
// Top up an existing builder regardless of withdrawal credential prefix
|
|
103
|
-
applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Only check the (expensive) "pending validator" condition when needed
|
|
108
|
-
if (
|
|
109
|
-
isBuilderWithdrawalCredential(withdrawalCredentials) &&
|
|
110
|
-
!isValidator &&
|
|
111
|
-
!lookup.hasPendingValidator(state.config, pubkeyHex)
|
|
112
|
-
) {
|
|
113
|
-
applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot);
|
|
114
|
-
return;
|
|
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;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Pre-Gloas (Electra) path
|
|
132
|
-
if (state.depositRequestsStartIndex === UNSET_DEPOSIT_REQUESTS_START_INDEX) {
|
|
12
|
+
if (fork === ForkSeq.electra && state.depositRequestsStartIndex === UNSET_DEPOSIT_REQUESTS_START_INDEX) {
|
|
133
13
|
state.depositRequestsStartIndex = depositRequest.index;
|
|
134
14
|
}
|
|
135
15
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {PublicKey, Signature, verify} from "@chainsafe/blst";
|
|
2
|
-
import {BUILDER_INDEX_SELF_BUILD,
|
|
3
|
-
import {
|
|
2
|
+
import {BUILDER_INDEX_SELF_BUILD, GENESIS_SLOT, PAYLOAD_BUILDER_VERSION, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
3
|
+
import {gloas, ssz} from "@lodestar/types";
|
|
4
4
|
import {byteArrayEquals, toHex, toRootHex} from "@lodestar/utils";
|
|
5
5
|
import {G2_POINT_AT_INFINITY} from "../constants/constants.js";
|
|
6
6
|
import {getExecutionPayloadBidSigningRoot} from "../signatureSets/executionPayloadBid.js";
|
|
7
7
|
import {CachedBeaconStateGloas} from "../types.js";
|
|
8
8
|
import {canBuilderCoverBid, isActiveBuilder} from "../util/gloas.js";
|
|
9
|
-
import {getCurrentEpoch, getRandaoMix} from "../util/index.js";
|
|
9
|
+
import {getBlockRootAtSlot, getCurrentEpoch, getRandaoMix} from "../util/index.js";
|
|
10
10
|
|
|
11
|
-
export function processExecutionPayloadBid(
|
|
12
|
-
|
|
11
|
+
export function processExecutionPayloadBid(
|
|
12
|
+
state: CachedBeaconStateGloas,
|
|
13
|
+
signedBid: gloas.SignedExecutionPayloadBid
|
|
14
|
+
): void {
|
|
13
15
|
const bid = signedBid.message;
|
|
14
16
|
const {builderIndex, value: amount} = bid;
|
|
15
17
|
|
|
@@ -31,6 +33,13 @@ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block:
|
|
|
31
33
|
throw Error(`Invalid execution payload bid: builder ${builderIndex} is not active`);
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
// Verify that the builder is a payload builder
|
|
37
|
+
if (builder.version !== PAYLOAD_BUILDER_VERSION) {
|
|
38
|
+
throw Error(
|
|
39
|
+
`Invalid execution payload bid: builder ${builderIndex} version ${builder.version} != ${PAYLOAD_BUILDER_VERSION}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
// Verify that the builder has funds to cover the bid
|
|
35
44
|
if (!canBuilderCoverBid(state, builderIndex, amount)) {
|
|
36
45
|
throw Error(`Invalid execution payload bid: builder ${builderIndex} has insufficient balance`);
|
|
@@ -42,8 +51,12 @@ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block:
|
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
if (bid.slot !==
|
|
46
|
-
throw Error(`Bid slot ${bid.slot} does not match
|
|
54
|
+
if (bid.slot !== state.slot) {
|
|
55
|
+
throw Error(`Bid slot ${bid.slot} does not match state slot ${state.slot}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (state.slot <= GENESIS_SLOT) {
|
|
59
|
+
throw Error(`Execution payload bid not allowed at genesis slot ${state.slot}`);
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
if (!byteArrayEquals(bid.parentBlockHash, state.latestBlockHash)) {
|
|
@@ -52,9 +65,10 @@ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block:
|
|
|
52
65
|
);
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
|
|
68
|
+
const parentBlockRoot = getBlockRootAtSlot(state, state.slot - 1);
|
|
69
|
+
if (!byteArrayEquals(bid.parentBlockRoot, parentBlockRoot)) {
|
|
56
70
|
throw Error(
|
|
57
|
-
`Parent block root ${toRootHex(bid.parentBlockRoot)} of bid does not match
|
|
71
|
+
`Parent block root ${toRootHex(bid.parentBlockRoot)} of bid does not match state's parent block root ${toRootHex(parentBlockRoot)}`
|
|
58
72
|
);
|
|
59
73
|
}
|
|
60
74
|
|
|
@@ -79,6 +93,7 @@ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block:
|
|
|
79
93
|
amount,
|
|
80
94
|
builderIndex,
|
|
81
95
|
}),
|
|
96
|
+
proposerIndex: state.epochCtx.getBeaconProposer(state.slot),
|
|
82
97
|
});
|
|
83
98
|
|
|
84
99
|
state.builderPendingPayments.set(SLOTS_PER_EPOCH + (bid.slot % SLOTS_PER_EPOCH), pendingPaymentView);
|
|
@@ -39,8 +39,9 @@ export function processOperations(
|
|
|
39
39
|
opts: ProcessBlockOpts = {verifySignatures: true},
|
|
40
40
|
metrics?: BeaconStateTransitionMetrics | null
|
|
41
41
|
): void {
|
|
42
|
-
// verify that outstanding deposits are processed up to the maximum number of deposits
|
|
43
|
-
|
|
42
|
+
// verify that outstanding deposits are processed up to the maximum number of deposits.
|
|
43
|
+
// From Fulu the eth1 bridge deposit mechanism was removed, so blocks must not contain any deposits.
|
|
44
|
+
const maxDeposits = fork >= ForkSeq.fulu ? 0 : getEth1DepositCount(state);
|
|
44
45
|
if (body.deposits.length !== maxDeposits) {
|
|
45
46
|
throw new Error(
|
|
46
47
|
`Block contains incorrect number of deposits: depositCount=${body.deposits.length} expected=${maxDeposits}`
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {ForkPostGloas, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
2
|
-
import {BeaconBlock,
|
|
2
|
+
import {BeaconBlock, gloas, ssz} from "@lodestar/types";
|
|
3
3
|
import {byteArrayEquals, toRootHex} from "@lodestar/utils";
|
|
4
4
|
import {CachedBeaconStateGloas} from "../types.js";
|
|
5
5
|
import {computeEpochAtSlot} from "../util/epoch.js";
|
|
6
|
-
import {
|
|
6
|
+
import {processBuilderDepositRequest} from "./processBuilderDepositRequest.js";
|
|
7
|
+
import {processBuilderExitRequest} from "./processBuilderExitRequest.js";
|
|
7
8
|
import {processConsolidationRequest} from "./processConsolidationRequest.js";
|
|
8
9
|
import {processDepositRequest} from "./processDepositRequest.js";
|
|
9
10
|
import {processWithdrawalRequest} from "./processWithdrawalRequest.js";
|
|
@@ -26,7 +27,7 @@ export function processParentExecutionPayload(state: CachedBeaconStateGloas, blo
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
// Parent was FULL -- verify the bid commitment and apply the payload
|
|
29
|
-
const requestsRoot = ssz.
|
|
30
|
+
const requestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(requests);
|
|
30
31
|
if (!byteArrayEquals(requestsRoot, parentBid.executionRequestsRoot)) {
|
|
31
32
|
throw new Error(
|
|
32
33
|
`Parent execution requests root mismatch actual=${toRootHex(requestsRoot)} expected=${toRootHex(parentBid.executionRequestsRoot)}`
|
|
@@ -45,7 +46,7 @@ export function processParentExecutionPayload(state: CachedBeaconStateGloas, blo
|
|
|
45
46
|
*
|
|
46
47
|
* 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
|
*/
|
|
48
|
-
export function applyParentExecutionPayload(state: CachedBeaconStateGloas, requests:
|
|
49
|
+
export function applyParentExecutionPayload(state: CachedBeaconStateGloas, requests: gloas.ExecutionRequests): void {
|
|
49
50
|
const fork = state.config.getForkSeq(state.slot);
|
|
50
51
|
const parentBid = state.latestExecutionPayloadBid;
|
|
51
52
|
const parentSlot = parentBid.slot;
|
|
@@ -54,11 +55,8 @@ export function applyParentExecutionPayload(state: CachedBeaconStateGloas, reque
|
|
|
54
55
|
|
|
55
56
|
// Process execution requests from parent's payload. The execution
|
|
56
57
|
// requests are processed at state.slot (child's slot), not the parent's slot.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
for (const deposit of requests.deposits) {
|
|
60
|
-
processDepositRequest(fork, state, deposit, pendingDepositsLookup);
|
|
61
|
-
}
|
|
58
|
+
for (const deposit of requests.deposits) {
|
|
59
|
+
processDepositRequest(fork, state, deposit);
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
for (const withdrawal of requests.withdrawals) {
|
|
@@ -69,6 +67,14 @@ export function applyParentExecutionPayload(state: CachedBeaconStateGloas, reque
|
|
|
69
67
|
processConsolidationRequest(state, consolidation);
|
|
70
68
|
}
|
|
71
69
|
|
|
70
|
+
for (const builderDeposit of requests.builderDeposits) {
|
|
71
|
+
processBuilderDepositRequest(state, builderDeposit);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const builderExit of requests.builderExits) {
|
|
75
|
+
processBuilderExitRequest(state, builderExit);
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
// Settle the builder payment
|
|
73
79
|
if (parentEpoch === currentEpoch) {
|
|
74
80
|
settleBuilderPayment(state, SLOTS_PER_EPOCH + (parentSlot % SLOTS_PER_EPOCH));
|
|
@@ -110,8 +116,14 @@ function settleBuilderPayment(state: CachedBeaconStateGloas, paymentIndex: numbe
|
|
|
110
116
|
state.builderPendingPayments.set(paymentIndex, ssz.gloas.BuilderPendingPayment.defaultViewDU());
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
function assertEmptyExecutionRequests(requests:
|
|
114
|
-
if (
|
|
119
|
+
function assertEmptyExecutionRequests(requests: gloas.ExecutionRequests): void {
|
|
120
|
+
if (
|
|
121
|
+
requests.deposits.length !== 0 ||
|
|
122
|
+
requests.withdrawals.length !== 0 ||
|
|
123
|
+
requests.consolidations.length !== 0 ||
|
|
124
|
+
requests.builderDeposits.length !== 0 ||
|
|
125
|
+
requests.builderExits.length !== 0
|
|
126
|
+
) {
|
|
115
127
|
throw new Error("Parent execution requests must be empty when parent block is EMPTY");
|
|
116
128
|
}
|
|
117
129
|
}
|
|
@@ -32,6 +32,10 @@ export function processProposerSlashing(
|
|
|
32
32
|
);
|
|
33
33
|
|
|
34
34
|
if (fork >= ForkSeq.gloas) {
|
|
35
|
+
// Remove the BuilderPendingPayment corresponding to this proposal if it is still in the
|
|
36
|
+
// 2-epoch window. Only clear it when the slashed validator is the proposer associated with
|
|
37
|
+
// the payment; otherwise an unrelated same-slot equivocation could grief an honest proposer's
|
|
38
|
+
// payment.
|
|
35
39
|
const slot = Number(proposerSlashing.signedHeader1.message.slot);
|
|
36
40
|
const proposalEpoch = computeEpochAtSlot(slot);
|
|
37
41
|
const currentEpoch = state.epochCtx.epoch;
|
|
@@ -45,10 +49,11 @@ export function processProposerSlashing(
|
|
|
45
49
|
: undefined;
|
|
46
50
|
|
|
47
51
|
if (paymentIndex !== undefined) {
|
|
48
|
-
(state as CachedBeaconStateGloas).builderPendingPayments
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
const builderPendingPayments = (state as CachedBeaconStateGloas).builderPendingPayments;
|
|
53
|
+
const payment = builderPendingPayments.get(paymentIndex);
|
|
54
|
+
if (payment.proposerIndex === proposerSlashing.signedHeader1.message.proposerIndex) {
|
|
55
|
+
builderPendingPayments.set(paymentIndex, ssz.gloas.BuilderPendingPayment.defaultViewDU());
|
|
56
|
+
}
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
|
|
@@ -2,14 +2,7 @@ import {FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params";
|
|
|
2
2
|
import {phase0} from "@lodestar/types";
|
|
3
3
|
import {verifyVoluntaryExitSignature} from "../signatureSets/index.js";
|
|
4
4
|
import {BeaconStateView} from "../stateView/beaconStateView.js";
|
|
5
|
-
import {CachedBeaconStateAllForks, CachedBeaconStateElectra
|
|
6
|
-
import {
|
|
7
|
-
convertValidatorIndexToBuilderIndex,
|
|
8
|
-
getPendingBalanceToWithdrawForBuilder,
|
|
9
|
-
initiateBuilderExit,
|
|
10
|
-
isActiveBuilder,
|
|
11
|
-
isBuilderIndex,
|
|
12
|
-
} from "../util/gloas.js";
|
|
5
|
+
import {CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js";
|
|
13
6
|
import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/index.js";
|
|
14
7
|
import {initiateValidatorExit} from "./index.js";
|
|
15
8
|
|
|
@@ -24,7 +17,7 @@ export enum VoluntaryExitValidity {
|
|
|
24
17
|
}
|
|
25
18
|
|
|
26
19
|
/**
|
|
27
|
-
* Process a VoluntaryExit operation. Initiates the exit of a validator
|
|
20
|
+
* Process a VoluntaryExit operation. Initiates the exit of a validator.
|
|
28
21
|
*
|
|
29
22
|
* PERF: Work depends on number of VoluntaryExit per block. On regular networks the average is 0 / block.
|
|
30
23
|
*/
|
|
@@ -34,21 +27,11 @@ export function processVoluntaryExit(
|
|
|
34
27
|
signedVoluntaryExit: phase0.SignedVoluntaryExit,
|
|
35
28
|
verifySignature = true
|
|
36
29
|
): void {
|
|
37
|
-
const voluntaryExit = signedVoluntaryExit.message;
|
|
38
|
-
|
|
39
30
|
const validity = getVoluntaryExitValidity(fork, state, signedVoluntaryExit, verifySignature);
|
|
40
31
|
if (validity !== VoluntaryExitValidity.valid) {
|
|
41
32
|
throw Error(`Invalid voluntary exit at forkSeq=${fork} reason=${validity}`);
|
|
42
33
|
}
|
|
43
34
|
|
|
44
|
-
if (fork >= ForkSeq.gloas && isBuilderIndex(voluntaryExit.validatorIndex)) {
|
|
45
|
-
initiateBuilderExit(
|
|
46
|
-
state as CachedBeaconStateGloas,
|
|
47
|
-
convertValidatorIndexToBuilderIndex(voluntaryExit.validatorIndex)
|
|
48
|
-
);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
35
|
const validator = state.validators.get(signedVoluntaryExit.message.validatorIndex);
|
|
53
36
|
initiateValidatorExit(fork, state, validator);
|
|
54
37
|
}
|
|
@@ -67,51 +50,9 @@ export function getVoluntaryExitValidity(
|
|
|
67
50
|
return VoluntaryExitValidity.earlyEpoch;
|
|
68
51
|
}
|
|
69
52
|
|
|
70
|
-
// Check if this is a builder exit
|
|
71
|
-
if (fork >= ForkSeq.gloas && isBuilderIndex(voluntaryExit.validatorIndex)) {
|
|
72
|
-
return getBuilderVoluntaryExitValidity(state as CachedBeaconStateGloas, signedVoluntaryExit, verifySignature);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
53
|
return getValidatorVoluntaryExitValidity(fork, state, signedVoluntaryExit, verifySignature);
|
|
76
54
|
}
|
|
77
55
|
|
|
78
|
-
function getBuilderVoluntaryExitValidity(
|
|
79
|
-
state: CachedBeaconStateGloas,
|
|
80
|
-
signedVoluntaryExit: phase0.SignedVoluntaryExit,
|
|
81
|
-
verifySignature: boolean
|
|
82
|
-
): VoluntaryExitValidity {
|
|
83
|
-
const {config, epochCtx} = state;
|
|
84
|
-
const builderIndex = convertValidatorIndexToBuilderIndex(signedVoluntaryExit.message.validatorIndex);
|
|
85
|
-
|
|
86
|
-
if (builderIndex >= state.builders.length) {
|
|
87
|
-
return VoluntaryExitValidity.inactive;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const builder = state.builders.getReadonly(builderIndex);
|
|
91
|
-
|
|
92
|
-
// Verify the builder is active
|
|
93
|
-
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
|
|
94
|
-
return builder.withdrawableEpoch !== FAR_FUTURE_EPOCH
|
|
95
|
-
? VoluntaryExitValidity.alreadyExited
|
|
96
|
-
: VoluntaryExitValidity.inactive;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Only exit builder if it has no pending withdrawals in the queue
|
|
100
|
-
if (getPendingBalanceToWithdrawForBuilder(state, builderIndex) !== 0) {
|
|
101
|
-
return VoluntaryExitValidity.pendingWithdrawals;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Verify signature
|
|
105
|
-
if (
|
|
106
|
-
verifySignature &&
|
|
107
|
-
!verifyVoluntaryExitSignature(config, epochCtx.pubkeyCache, new BeaconStateView(state), signedVoluntaryExit)
|
|
108
|
-
) {
|
|
109
|
-
return VoluntaryExitValidity.invalidSignature;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return VoluntaryExitValidity.valid;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
56
|
function getValidatorVoluntaryExitValidity(
|
|
116
57
|
fork: ForkSeq,
|
|
117
58
|
state: CachedBeaconStateAllForks,
|
package/src/cache/epochCache.ts
CHANGED
|
@@ -782,14 +782,17 @@ export class EpochCache {
|
|
|
782
782
|
*/
|
|
783
783
|
getBeaconProposer(slot: Slot): ValidatorIndex {
|
|
784
784
|
const epoch = computeEpochAtSlot(slot);
|
|
785
|
-
if (epoch
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
});
|
|
785
|
+
if (epoch === this.currentShuffling.epoch) {
|
|
786
|
+
return this.proposers[slot % SLOTS_PER_EPOCH];
|
|
787
|
+
}
|
|
788
|
+
if (epoch === this.nextEpoch) {
|
|
789
|
+
return this.getBeaconProposersNextEpoch()[slot % SLOTS_PER_EPOCH];
|
|
791
790
|
}
|
|
792
|
-
|
|
791
|
+
throw new EpochCacheError({
|
|
792
|
+
code: EpochCacheErrorCode.PROPOSER_EPOCH_MISMATCH,
|
|
793
|
+
currentEpoch: this.currentShuffling.epoch,
|
|
794
|
+
requestedEpoch: epoch,
|
|
795
|
+
});
|
|
793
796
|
}
|
|
794
797
|
|
|
795
798
|
getBeaconProposers(): ValidatorIndex[] {
|
|
@@ -37,7 +37,9 @@ export function processPendingDeposits(state: CachedBeaconStateElectra, cache: E
|
|
|
37
37
|
|
|
38
38
|
for (const deposit of deposits) {
|
|
39
39
|
// Do not process deposit requests if Eth1 bridge deposits are not yet applied.
|
|
40
|
+
// The eth1 bridge deposit mechanism was removed in Fulu, so this gate only applies pre-Fulu.
|
|
40
41
|
if (
|
|
42
|
+
fork < ForkSeq.fulu &&
|
|
41
43
|
// Is deposit request
|
|
42
44
|
deposit.slot > GENESIS_SLOT &&
|
|
43
45
|
// There are pending Eth1 bridge deposits
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {ForkSeq, MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
2
|
import {ssz} from "@lodestar/types";
|
|
3
3
|
import {CachedBeaconStateFulu, EpochTransitionCache} from "../types.js";
|
|
4
|
+
import {FLAG_UNSLASHED} from "../util/attesterStatus.js";
|
|
4
5
|
import {computeEpochShuffling} from "../util/epochShuffling.js";
|
|
5
6
|
import {computeProposerIndices} from "../util/seed.js";
|
|
6
7
|
|
|
@@ -26,7 +27,13 @@ export function processProposerLookahead(
|
|
|
26
27
|
// Save shuffling to cache so afterProcessEpoch can reuse it instead of recomputing
|
|
27
28
|
cache.nextShuffling = shuffling;
|
|
28
29
|
|
|
29
|
-
const
|
|
30
|
+
const activeIndices =
|
|
31
|
+
fork >= ForkSeq.gloas
|
|
32
|
+
? // Exclude slashed validators from proposing (EIP-8045)
|
|
33
|
+
shuffling.activeIndices.filter((index) => (cache.flags[index] & FLAG_UNSLASHED) !== 0)
|
|
34
|
+
: shuffling.activeIndices;
|
|
35
|
+
|
|
36
|
+
const lastEpochProposerLookahead = computeProposerIndices(fork, state, {activeIndices}, epoch);
|
|
30
37
|
|
|
31
38
|
state.proposerLookahead = ssz.fulu.ProposerLookahead.toViewDU([
|
|
32
39
|
...remainingProposerLookahead,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
1
|
+
import {PAYLOAD_BUILDER_VERSION, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
2
2
|
import {ssz} from "@lodestar/types";
|
|
3
3
|
import {toPubkeyHex} from "@lodestar/utils";
|
|
4
|
-
import {
|
|
4
|
+
import {isValidDepositSignature} from "../block/processDeposit.js";
|
|
5
5
|
import {getCachedBeaconState} from "../cache/stateCache.js";
|
|
6
6
|
import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
|
|
7
|
-
import {initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
|
|
7
|
+
import {addBuilderToRegistry, initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
|
|
8
8
|
import {isValidatorKnown} from "../util/index.js";
|
|
9
9
|
import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
|
|
10
10
|
|
|
@@ -48,9 +48,9 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
|
|
|
48
48
|
stateGloasView.currentSyncCommittee = stateGloasCloned.currentSyncCommittee;
|
|
49
49
|
stateGloasView.nextSyncCommittee = stateGloasCloned.nextSyncCommittee;
|
|
50
50
|
stateGloasView.latestExecutionPayloadBid.blockHash = stateFulu.latestExecutionPayloadHeader.blockHash;
|
|
51
|
-
stateGloasView.latestExecutionPayloadBid.gasLimit =
|
|
52
|
-
stateGloasView.latestExecutionPayloadBid.executionRequestsRoot = ssz.
|
|
53
|
-
ssz.
|
|
51
|
+
stateGloasView.latestExecutionPayloadBid.gasLimit = stateFulu.latestExecutionPayloadHeader.gasLimit;
|
|
52
|
+
stateGloasView.latestExecutionPayloadBid.executionRequestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(
|
|
53
|
+
ssz.gloas.ExecutionRequests.defaultValue()
|
|
54
54
|
);
|
|
55
55
|
stateGloasView.nextWithdrawalIndex = stateGloasCloned.nextWithdrawalIndex;
|
|
56
56
|
stateGloasView.nextWithdrawalValidatorIndex = stateGloasCloned.nextWithdrawalValidatorIndex;
|
|
@@ -90,7 +90,8 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
|
|
|
90
90
|
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.8/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits
|
|
91
91
|
*/
|
|
92
92
|
function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void {
|
|
93
|
-
// Track pubkeys of new builders added when applying deposits
|
|
93
|
+
// Track pubkeys of new builders added when applying deposits. `state.builders` starts empty
|
|
94
|
+
// at the fork, so every builder pubkey here is one added in an earlier iteration.
|
|
94
95
|
const builderPubkeys = new Set<string>();
|
|
95
96
|
|
|
96
97
|
const pendingDeposits = ssz.electra.PendingDeposits.defaultViewDU();
|
|
@@ -109,40 +110,55 @@ function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void
|
|
|
109
110
|
continue;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!isBuilderWithdrawalCredential(deposit.withdrawalCredentials)) {
|
|
121
|
-
pendingDeposits.push(deposit);
|
|
122
|
-
pendingDepositsLookup.add(deposit, pubkeyHex);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (pendingDepositsLookup.hasPendingValidator(state.config, pubkeyHex)) {
|
|
126
|
-
pendingDeposits.push(deposit);
|
|
127
|
-
pendingDepositsLookup.add(deposit, pubkeyHex);
|
|
128
|
-
continue;
|
|
113
|
+
if (builderPubkeys.has(pubkeyHex)) {
|
|
114
|
+
// Top up an already-onboarded builder
|
|
115
|
+
// TODO GLOAS: linear search; consider builder pubkey cache when we drop the upgrade-time set
|
|
116
|
+
for (let j = 0; j < state.builders.length; j++) {
|
|
117
|
+
if (toPubkeyHex(state.builders.getReadonly(j).pubkey) === pubkeyHex) {
|
|
118
|
+
state.builders.get(j).balance += deposit.amount;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
129
121
|
}
|
|
122
|
+
continue;
|
|
130
123
|
}
|
|
131
124
|
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
125
|
+
// Deposits for non-builders stay in the pending queue. If there is a valid pending
|
|
126
|
+
// deposit for a new validator with this pubkey, keep this deposit pending so the validator
|
|
127
|
+
// can pick it up later.
|
|
128
|
+
if (!isBuilderWithdrawalCredential(deposit.withdrawalCredentials)) {
|
|
129
|
+
pendingDeposits.push(deposit);
|
|
130
|
+
pendingDepositsLookup.add(deposit, pubkeyHex);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (pendingDepositsLookup.hasPendingValidator(state.config, pubkeyHex)) {
|
|
134
|
+
pendingDeposits.push(deposit);
|
|
135
|
+
pendingDepositsLookup.add(deposit, pubkeyHex);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Verify the deposit signature (proof of possession). If invalid the deposit is silently
|
|
140
|
+
// dropped — stake is forfeited, matching the validator deposit contract behavior.
|
|
141
|
+
if (
|
|
142
|
+
!isValidDepositSignature(
|
|
143
|
+
state.config,
|
|
144
|
+
deposit.pubkey,
|
|
145
|
+
deposit.withdrawalCredentials,
|
|
146
|
+
deposit.amount,
|
|
147
|
+
deposit.signature
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
addBuilderToRegistry(
|
|
136
154
|
state,
|
|
137
155
|
deposit.pubkey,
|
|
138
|
-
|
|
156
|
+
PAYLOAD_BUILDER_VERSION,
|
|
157
|
+
deposit.withdrawalCredentials.subarray(12),
|
|
139
158
|
deposit.amount,
|
|
140
|
-
deposit.signature,
|
|
141
159
|
deposit.slot
|
|
142
160
|
);
|
|
143
|
-
|
|
144
|
-
builderPubkeys.add(pubkeyHex);
|
|
145
|
-
}
|
|
161
|
+
builderPubkeys.add(pubkeyHex);
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
state.pendingDeposits = pendingDeposits;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
BlindedBeaconBlock,
|
|
9
9
|
BuilderIndex,
|
|
10
10
|
Bytes32,
|
|
11
|
+
CommitteeIndex,
|
|
11
12
|
Epoch,
|
|
12
13
|
ExecutionPayloadBid,
|
|
13
14
|
ExecutionPayloadHeader,
|
|
@@ -452,6 +453,14 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
452
453
|
return this.cachedState.epochCtx.getShufflingAtEpoch(epoch);
|
|
453
454
|
}
|
|
454
455
|
|
|
456
|
+
getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array {
|
|
457
|
+
return this.cachedState.epochCtx.getBeaconCommittee(slot, index);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
getBeaconCommitteeCountPerSlot(epoch: Epoch): number {
|
|
461
|
+
return this.cachedState.epochCtx.getCommitteeCountPerSlot(epoch);
|
|
462
|
+
}
|
|
463
|
+
|
|
455
464
|
get previousDecisionRoot(): RootHex {
|
|
456
465
|
return this.cachedState.epochCtx.previousDecisionRoot;
|
|
457
466
|
}
|
|
@@ -498,6 +507,14 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
498
507
|
return this.cachedState.epochCtx.getBeaconProposer(slot);
|
|
499
508
|
}
|
|
500
509
|
|
|
510
|
+
getBeaconProposerOrNull(slot: Slot): ValidatorIndex | null {
|
|
511
|
+
try {
|
|
512
|
+
return this.cachedState.epochCtx.getBeaconProposer(slot);
|
|
513
|
+
} catch {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
501
518
|
computeAnchorCheckpoint(): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader} {
|
|
502
519
|
return computeAnchorCheckpoint(this.config, this.cachedState);
|
|
503
520
|
}
|
|
@@ -814,7 +831,7 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
814
831
|
/**
|
|
815
832
|
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.5/specs/gloas/validator.md#executionpayload
|
|
816
833
|
*/
|
|
817
|
-
withParentPayloadApplied(executionRequests:
|
|
834
|
+
withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas {
|
|
818
835
|
if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
|
|
819
836
|
throw new Error("withParentPayloadApplied is not available before Gloas");
|
|
820
837
|
}
|