@lodestar/state-transition 1.44.0-dev.4ac6f45830 → 1.44.0-dev.4b91ba8c2f
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 +2 -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/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/slot/upgradeStateToGloas.js +35 -29
- package/lib/slot/upgradeStateToGloas.js.map +1 -1
- package/lib/stateView/beaconStateView.d.ts +4 -2
- package/lib/stateView/beaconStateView.d.ts.map +1 -1
- package/lib/stateView/beaconStateView.js +6 -0
- package/lib/stateView/beaconStateView.js.map +1 -1
- package/lib/stateView/interface.d.ts +4 -2
- package/lib/stateView/interface.d.ts.map +1 -1
- package/lib/stateView/interface.js.map +1 -1
- package/lib/stateView/nativeBeaconStateView.d.ts +6 -2
- package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
- package/lib/stateView/nativeBeaconStateView.js +19 -0
- package/lib/stateView/nativeBeaconStateView.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 +17 -0
- package/lib/util/gloas.d.ts.map +1 -1
- package/lib/util/gloas.js +59 -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 -7
- 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 +3 -124
- package/src/block/processExecutionPayloadBid.ts +24 -9
- package/src/block/processParentExecutionPayload.ts +23 -11
- package/src/block/processProposerSlashing.ts +9 -4
- package/src/block/processVoluntaryExit.ts +2 -61
- package/src/slot/upgradeStateToGloas.ts +49 -33
- package/src/stateView/beaconStateView.ts +10 -1
- package/src/stateView/interface.ts +4 -1
- package/src/stateView/nativeBeaconStateView.ts +23 -1
- package/src/util/epochShuffling.ts +1 -1
- package/src/util/gloas.ts +76 -1
- package/src/util/shuffling.ts +4 -3
|
@@ -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);
|
|
@@ -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,
|
|
@@ -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
|
}
|
|
@@ -822,7 +831,7 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
822
831
|
/**
|
|
823
832
|
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.5/specs/gloas/validator.md#executionpayload
|
|
824
833
|
*/
|
|
825
|
-
withParentPayloadApplied(executionRequests:
|
|
834
|
+
withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas {
|
|
826
835
|
if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
|
|
827
836
|
throw new Error("withParentPayloadApplied is not available before Gloas");
|
|
828
837
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
BlindedBeaconBlock,
|
|
24
24
|
BuilderIndex,
|
|
25
25
|
Bytes32,
|
|
26
|
+
CommitteeIndex,
|
|
26
27
|
Epoch,
|
|
27
28
|
ExecutionPayloadBid,
|
|
28
29
|
ExecutionPayloadHeader,
|
|
@@ -75,6 +76,8 @@ export interface IBeaconStateView {
|
|
|
75
76
|
|
|
76
77
|
// Shuffling and committees
|
|
77
78
|
getShufflingAtEpoch(epoch: Epoch): EpochShuffling;
|
|
79
|
+
getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array;
|
|
80
|
+
getBeaconCommitteeCountPerSlot(epoch: Epoch): number;
|
|
78
81
|
// Decision roots
|
|
79
82
|
previousDecisionRoot: RootHex;
|
|
80
83
|
currentDecisionRoot: RootHex;
|
|
@@ -261,7 +264,7 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
|
|
|
261
264
|
* operation selection (e.g. voluntary exits) see the same post-apply state that the block
|
|
262
265
|
* processor will see at import.
|
|
263
266
|
*/
|
|
264
|
-
withParentPayloadApplied(executionRequests:
|
|
267
|
+
withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas;
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
/**
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
BlindedBeaconBlock,
|
|
8
8
|
BuilderIndex,
|
|
9
9
|
Bytes32,
|
|
10
|
+
CommitteeIndex,
|
|
10
11
|
Epoch,
|
|
11
12
|
ExecutionPayloadBid,
|
|
12
13
|
ExecutionPayloadHeader,
|
|
@@ -123,6 +124,8 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
123
124
|
private readonly _getStateRootAtSlot = new Map<Slot, Root>();
|
|
124
125
|
private readonly _getRandaoMix = new Map<Epoch, Bytes32>();
|
|
125
126
|
private readonly _getShufflingAtEpoch = new Map<Epoch, EpochShuffling>();
|
|
127
|
+
private readonly _getBeaconCommittee = new Map<string, Uint32Array>();
|
|
128
|
+
private readonly _getBeaconCommitteeCountPerSlot = new Map<Epoch, number>();
|
|
126
129
|
private readonly _getShufflingDecisionRoot = new Map<Epoch, RootHex>();
|
|
127
130
|
private readonly _getBeaconProposer = new Map<Slot, ValidatorIndex>();
|
|
128
131
|
// getBeaconProposerOrNull can return null, so use .has() to distinguish "not cached" from "cached null"
|
|
@@ -305,6 +308,25 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
305
308
|
return cached;
|
|
306
309
|
}
|
|
307
310
|
|
|
311
|
+
getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array {
|
|
312
|
+
const key = `${slot}:${index}`;
|
|
313
|
+
let cached = this._getBeaconCommittee.get(key);
|
|
314
|
+
if (cached === undefined) {
|
|
315
|
+
cached = this.binding.getBeaconCommittee(slot, index);
|
|
316
|
+
this._getBeaconCommittee.set(key, cached);
|
|
317
|
+
}
|
|
318
|
+
return cached;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
getBeaconCommitteeCountPerSlot(epoch: Epoch): number {
|
|
322
|
+
let cached = this._getBeaconCommitteeCountPerSlot.get(epoch);
|
|
323
|
+
if (cached === undefined) {
|
|
324
|
+
cached = this.binding.getBeaconCommitteeCountPerSlot(epoch);
|
|
325
|
+
this._getBeaconCommitteeCountPerSlot.set(epoch, cached);
|
|
326
|
+
}
|
|
327
|
+
return cached;
|
|
328
|
+
}
|
|
329
|
+
|
|
308
330
|
get previousDecisionRoot(): RootHex {
|
|
309
331
|
if (this._previousDecisionRoot === null) {
|
|
310
332
|
this._previousDecisionRoot = this.binding.previousDecisionRoot;
|
|
@@ -883,7 +905,7 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
|
|
|
883
905
|
return this.binding.getIndicesInPayloadTimelinessCommittee(validatorIndex, slot);
|
|
884
906
|
}
|
|
885
907
|
|
|
886
|
-
withParentPayloadApplied(executionRequests:
|
|
908
|
+
withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas {
|
|
887
909
|
const view = new NativeBeaconStateView(this.binding.withParentPayloadApplied(executionRequests));
|
|
888
910
|
if (!isStatePostGloas(view)) {
|
|
889
911
|
throw new Error("Expected gloas state from withParentPayloadApplied");
|
|
@@ -124,7 +124,7 @@ export async function computeEpochShufflingAsync(
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
export function calculateDecisionRoot(state: BeaconStateAllForks, epoch: Epoch): RootHex {
|
|
127
|
-
const pivotSlot = computeStartSlotAtEpoch(epoch - 1) - 1;
|
|
127
|
+
const pivotSlot = Math.max(GENESIS_SLOT, computeStartSlotAtEpoch(epoch - 1) - 1);
|
|
128
128
|
return toRootHex(getBlockRootAtSlot(state, pivotSlot));
|
|
129
129
|
}
|
|
130
130
|
|
package/src/util/gloas.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import {PublicKey, Signature, verify} from "@chainsafe/blst";
|
|
2
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
1
3
|
import {
|
|
2
4
|
BUILDER_INDEX_FLAG,
|
|
3
5
|
BUILDER_PAYMENT_THRESHOLD_DENOMINATOR,
|
|
4
6
|
BUILDER_PAYMENT_THRESHOLD_NUMERATOR,
|
|
5
7
|
BUILDER_WITHDRAWAL_PREFIX,
|
|
8
|
+
DOMAIN_BUILDER_DEPOSIT,
|
|
6
9
|
EFFECTIVE_BALANCE_INCREMENT,
|
|
7
10
|
FAR_FUTURE_EPOCH,
|
|
8
11
|
MIN_DEPOSIT_AMOUNT,
|
|
@@ -10,15 +13,18 @@ import {
|
|
|
10
13
|
PTC_SIZE,
|
|
11
14
|
SLOTS_PER_EPOCH,
|
|
12
15
|
} from "@lodestar/params";
|
|
13
|
-
import {BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types";
|
|
16
|
+
import {BuilderIndex, Epoch, ValidatorIndex, gloas, ssz} from "@lodestar/types";
|
|
14
17
|
import {AttestationData} from "@lodestar/types/phase0";
|
|
15
18
|
import {byteArrayEquals} from "@lodestar/utils";
|
|
19
|
+
import {ZERO_HASH} from "../constants/index.js";
|
|
16
20
|
import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
|
|
17
21
|
import {getBlockRootAtSlot} from "./blockRoot.js";
|
|
22
|
+
import {computeDomain} from "./domain.js";
|
|
18
23
|
import {computeEpochAtSlot} from "./epoch.js";
|
|
19
24
|
import {computeEpochShuffling} from "./epochShuffling.js";
|
|
20
25
|
import {RootCache} from "./rootCache.js";
|
|
21
26
|
import {computePayloadTimelinessCommitteesForEpoch} from "./seed.js";
|
|
27
|
+
import {computeSigningRoot} from "./signingRoot.js";
|
|
22
28
|
import {getActiveValidatorIndices} from "./validator.js";
|
|
23
29
|
|
|
24
30
|
export function isBuilderWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean {
|
|
@@ -241,3 +247,72 @@ export function getPtcWindowEpochCacheData(state: CachedBeaconStateGloas): {
|
|
|
241
247
|
nextPayloadTimelinessCommittees: toUint32Arrays(nextPtcWindow),
|
|
242
248
|
};
|
|
243
249
|
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Add a new builder to the builders registry. Reuses slots from exited and fully withdrawn
|
|
253
|
+
* builders when available, otherwise appends.
|
|
254
|
+
*
|
|
255
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/gloas/beacon-chain.md#new-add_builder_to_registry
|
|
256
|
+
*/
|
|
257
|
+
export function addBuilderToRegistry(
|
|
258
|
+
state: CachedBeaconStateGloas,
|
|
259
|
+
pubkey: Uint8Array,
|
|
260
|
+
version: number,
|
|
261
|
+
executionAddress: Uint8Array,
|
|
262
|
+
amount: number,
|
|
263
|
+
slot: number
|
|
264
|
+
): void {
|
|
265
|
+
const currentEpoch = computeEpochAtSlot(state.slot);
|
|
266
|
+
const depositEpoch = computeEpochAtSlot(slot);
|
|
267
|
+
|
|
268
|
+
let builderIndex = state.builders.length;
|
|
269
|
+
for (let i = 0; i < state.builders.length; i++) {
|
|
270
|
+
const builder = state.builders.getReadonly(i);
|
|
271
|
+
if (builder.withdrawableEpoch <= currentEpoch && builder.balance === 0) {
|
|
272
|
+
builderIndex = i;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const newBuilder = ssz.gloas.Builder.toViewDU({
|
|
278
|
+
pubkey,
|
|
279
|
+
version,
|
|
280
|
+
executionAddress,
|
|
281
|
+
balance: amount,
|
|
282
|
+
depositEpoch,
|
|
283
|
+
withdrawableEpoch: FAR_FUTURE_EPOCH,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (builderIndex < state.builders.length) {
|
|
287
|
+
state.builders.set(builderIndex, newBuilder);
|
|
288
|
+
} else {
|
|
289
|
+
state.builders.push(newBuilder);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Verify the proof of possession on a builder deposit request.
|
|
295
|
+
*
|
|
296
|
+
* The dedicated `DOMAIN_BUILDER_DEPOSIT` (vs the validator `DOMAIN_DEPOSIT`) prevents replay
|
|
297
|
+
* of validator deposit signatures against the builder deposit contract and vice versa.
|
|
298
|
+
*
|
|
299
|
+
* Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/gloas/beacon-chain.md#new-is_valid_builder_deposit_signature
|
|
300
|
+
*/
|
|
301
|
+
export function isValidBuilderDepositSignature(
|
|
302
|
+
config: BeaconConfig,
|
|
303
|
+
pubkey: Uint8Array,
|
|
304
|
+
withdrawalCredentials: Uint8Array,
|
|
305
|
+
amount: number,
|
|
306
|
+
signature: Uint8Array
|
|
307
|
+
): boolean {
|
|
308
|
+
const depositMessage = {pubkey, withdrawalCredentials, amount};
|
|
309
|
+
const domain = computeDomain(DOMAIN_BUILDER_DEPOSIT, config.GENESIS_FORK_VERSION, ZERO_HASH);
|
|
310
|
+
const signingRoot = computeSigningRoot(ssz.phase0.DepositMessage, depositMessage, domain);
|
|
311
|
+
try {
|
|
312
|
+
const publicKey = PublicKey.fromBytes(pubkey, true);
|
|
313
|
+
const sig = Signature.fromBytes(signature, true);
|
|
314
|
+
return verify(signingRoot, publicKey, sig);
|
|
315
|
+
} catch (_e) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
package/src/util/shuffling.ts
CHANGED
|
@@ -21,8 +21,8 @@ import {EpochShuffling} from "./epochShuffling.js";
|
|
|
21
21
|
* Computed for the requested epoch, not `state.epoch`, so it is correct when `state` is one
|
|
22
22
|
* epoch off the requested epoch (e.g. serving next-epoch duties from the current state).
|
|
23
23
|
*
|
|
24
|
-
* Returns `null` when the
|
|
25
|
-
*
|
|
24
|
+
* Returns `null` when the decision block is not in the state's past (the genesis block decides
|
|
25
|
+
* its own shuffling, or it has not been produced yet); caller supplies the fallback.
|
|
26
26
|
*/
|
|
27
27
|
export function proposerShufflingDecisionRoot(
|
|
28
28
|
fork: ForkName,
|
|
@@ -30,7 +30,8 @@ export function proposerShufflingDecisionRoot(
|
|
|
30
30
|
proposalEpoch: Epoch
|
|
31
31
|
): Root | null {
|
|
32
32
|
const decisionSlot = proposerShufflingDecisionSlot(fork, proposalEpoch);
|
|
33
|
-
|
|
33
|
+
// Return null when the decision block is not in the state's past or the getBlockRootAtSlot() api will throw
|
|
34
|
+
if (decisionSlot >= state.slot) {
|
|
34
35
|
return null;
|
|
35
36
|
}
|
|
36
37
|
return state.getBlockRootAtSlot(decisionSlot);
|