@lodestar/state-transition 1.39.0-dev.86298a43e6 → 1.39.0-dev.90493ebb47
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/isValidIndexedAttestation.d.ts +4 -5
- package/lib/block/isValidIndexedAttestation.d.ts.map +1 -1
- package/lib/block/isValidIndexedAttestation.js +9 -10
- package/lib/block/isValidIndexedAttestation.js.map +1 -1
- package/lib/block/processAttestationPhase0.d.ts.map +1 -1
- package/lib/block/processAttestationPhase0.js +1 -1
- package/lib/block/processAttestationPhase0.js.map +1 -1
- package/lib/block/processAttesterSlashing.d.ts +3 -2
- package/lib/block/processAttesterSlashing.d.ts.map +1 -1
- package/lib/block/processAttesterSlashing.js +3 -3
- package/lib/block/processAttesterSlashing.js.map +1 -1
- package/lib/block/processBlsToExecutionChange.d.ts +3 -1
- package/lib/block/processBlsToExecutionChange.d.ts.map +1 -1
- package/lib/block/processBlsToExecutionChange.js +7 -11
- package/lib/block/processBlsToExecutionChange.js.map +1 -1
- package/lib/block/processProposerSlashing.d.ts +5 -2
- package/lib/block/processProposerSlashing.d.ts.map +1 -1
- package/lib/block/processProposerSlashing.js +7 -5
- package/lib/block/processProposerSlashing.js.map +1 -1
- package/lib/block/processWithdrawals.d.ts +3 -3
- package/lib/block/processWithdrawals.d.ts.map +1 -1
- package/lib/block/processWithdrawals.js +152 -105
- package/lib/block/processWithdrawals.js.map +1 -1
- package/lib/signatureSets/blsToExecutionChange.d.ts +1 -2
- package/lib/signatureSets/blsToExecutionChange.d.ts.map +1 -1
- package/lib/signatureSets/blsToExecutionChange.js +2 -2
- package/lib/signatureSets/blsToExecutionChange.js.map +1 -1
- package/lib/signatureSets/proposer.d.ts +3 -4
- package/lib/signatureSets/proposer.d.ts.map +1 -1
- package/lib/signatureSets/proposer.js +5 -6
- package/lib/signatureSets/proposer.js.map +1 -1
- package/package.json +14 -11
- package/src/block/isValidIndexedAttestation.ts +17 -12
- package/src/block/processAttestationPhase0.ts +2 -1
- package/src/block/processAttesterSlashing.ts +16 -4
- package/src/block/processBlsToExecutionChange.ts +13 -14
- package/src/block/processProposerSlashing.ts +21 -11
- package/src/block/processWithdrawals.ts +230 -135
- package/src/signatureSets/blsToExecutionChange.ts +2 -3
- package/src/signatureSets/proposer.ts +6 -7
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
1
2
|
import {ForkSeq} from "@lodestar/params";
|
|
2
|
-
import {AttesterSlashing} from "@lodestar/types";
|
|
3
|
+
import {AttesterSlashing, Slot} from "@lodestar/types";
|
|
3
4
|
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
|
|
4
5
|
import {CachedBeaconStateAllForks} from "../types.js";
|
|
5
6
|
import {getAttesterSlashableIndices, isSlashableAttestationData, isSlashableValidator} from "../util/index.js";
|
|
@@ -19,7 +20,14 @@ export function processAttesterSlashing(
|
|
|
19
20
|
verifySignatures = true
|
|
20
21
|
): void {
|
|
21
22
|
const {epochCtx} = state;
|
|
22
|
-
assertValidAttesterSlashing(
|
|
23
|
+
assertValidAttesterSlashing(
|
|
24
|
+
state.config,
|
|
25
|
+
epochCtx.index2pubkey,
|
|
26
|
+
state.slot,
|
|
27
|
+
state.validators.length,
|
|
28
|
+
attesterSlashing,
|
|
29
|
+
verifySignatures
|
|
30
|
+
);
|
|
23
31
|
|
|
24
32
|
const intersectingIndices = getAttesterSlashableIndices(attesterSlashing);
|
|
25
33
|
|
|
@@ -39,8 +47,10 @@ export function processAttesterSlashing(
|
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
export function assertValidAttesterSlashing(
|
|
50
|
+
config: BeaconConfig,
|
|
42
51
|
index2pubkey: Index2PubkeyCache,
|
|
43
|
-
|
|
52
|
+
stateSlot: Slot,
|
|
53
|
+
validatorsLen: number,
|
|
44
54
|
attesterSlashing: AttesterSlashing,
|
|
45
55
|
verifySignatures = true
|
|
46
56
|
): void {
|
|
@@ -55,7 +65,9 @@ export function assertValidAttesterSlashing(
|
|
|
55
65
|
// be higher than the clock and the slashing would still be valid. Same applies to attestation data index, which
|
|
56
66
|
// can be any arbitrary value. Must use bigint variants to hash correctly to all possible values
|
|
57
67
|
for (const [i, attestation] of [attestation1, attestation2].entries()) {
|
|
58
|
-
if (
|
|
68
|
+
if (
|
|
69
|
+
!isValidIndexedAttestationBigint(config, index2pubkey, stateSlot, validatorsLen, attestation, verifySignatures)
|
|
70
|
+
) {
|
|
59
71
|
throw new Error(`AttesterSlashing attestation${i} is invalid`);
|
|
60
72
|
}
|
|
61
73
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {digest} from "@chainsafe/as-sha256";
|
|
2
2
|
import {byteArrayEquals} from "@chainsafe/ssz";
|
|
3
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
3
4
|
import {BLS_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX} from "@lodestar/params";
|
|
4
5
|
import {capella} from "@lodestar/types";
|
|
6
|
+
import {Validator} from "@lodestar/types/phase0";
|
|
5
7
|
import {toHex} from "@lodestar/utils";
|
|
6
8
|
import {verifyBlsToExecutionChangeSignature} from "../signatureSets/index.js";
|
|
7
9
|
import {CachedBeaconStateCapella} from "../types.js";
|
|
@@ -12,12 +14,18 @@ export function processBlsToExecutionChange(
|
|
|
12
14
|
): void {
|
|
13
15
|
const addressChange = signedBlsToExecutionChange.message;
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
if (addressChange.validatorIndex >= state.validators.length) {
|
|
18
|
+
throw Error(
|
|
19
|
+
`withdrawalValidatorIndex ${addressChange.validatorIndex} >= state.validators len ${state.validators.length}`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const validator = state.validators.get(addressChange.validatorIndex);
|
|
24
|
+
const validation = isValidBlsToExecutionChange(state.config, validator, signedBlsToExecutionChange, true);
|
|
16
25
|
if (!validation.valid) {
|
|
17
26
|
throw validation.error;
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
const validator = state.validators.get(addressChange.validatorIndex);
|
|
21
29
|
const newWithdrawalCredentials = new Uint8Array(32);
|
|
22
30
|
newWithdrawalCredentials[0] = ETH1_ADDRESS_WITHDRAWAL_PREFIX;
|
|
23
31
|
newWithdrawalCredentials.set(addressChange.toExecutionAddress, 12);
|
|
@@ -27,22 +35,13 @@ export function processBlsToExecutionChange(
|
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
export function isValidBlsToExecutionChange(
|
|
30
|
-
|
|
38
|
+
config: BeaconConfig,
|
|
39
|
+
validator: Validator,
|
|
31
40
|
signedBLSToExecutionChange: capella.SignedBLSToExecutionChange,
|
|
32
41
|
verifySignature = true
|
|
33
42
|
): {valid: true} | {valid: false; error: Error} {
|
|
34
43
|
const addressChange = signedBLSToExecutionChange.message;
|
|
35
44
|
|
|
36
|
-
if (addressChange.validatorIndex >= state.validators.length) {
|
|
37
|
-
return {
|
|
38
|
-
valid: false,
|
|
39
|
-
error: Error(
|
|
40
|
-
`withdrawalValidatorIndex ${addressChange.validatorIndex} > state.validators len ${state.validators.length}`
|
|
41
|
-
),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const validator = state.validators.getReadonly(addressChange.validatorIndex);
|
|
46
45
|
const {withdrawalCredentials} = validator;
|
|
47
46
|
if (withdrawalCredentials[0] !== BLS_WITHDRAWAL_PREFIX) {
|
|
48
47
|
return {
|
|
@@ -65,7 +64,7 @@ export function isValidBlsToExecutionChange(
|
|
|
65
64
|
};
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
if (verifySignature && !verifyBlsToExecutionChangeSignature(
|
|
67
|
+
if (verifySignature && !verifyBlsToExecutionChangeSignature(config, signedBLSToExecutionChange)) {
|
|
69
68
|
return {
|
|
70
69
|
valid: false,
|
|
71
70
|
error: Error(
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
1
2
|
import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
|
-
import {phase0, ssz} from "@lodestar/types";
|
|
3
|
+
import {Slot, phase0, ssz} from "@lodestar/types";
|
|
4
|
+
import {Validator} from "@lodestar/types/phase0";
|
|
5
|
+
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
|
|
3
6
|
import {getProposerSlashingSignatureSets} from "../signatureSets/index.js";
|
|
4
7
|
import {CachedBeaconStateAllForks, CachedBeaconStateGloas} from "../types.js";
|
|
5
8
|
import {computeEpochAtSlot, isSlashableValidator} from "../util/index.js";
|
|
@@ -18,7 +21,15 @@ export function processProposerSlashing(
|
|
|
18
21
|
proposerSlashing: phase0.ProposerSlashing,
|
|
19
22
|
verifySignatures = true
|
|
20
23
|
): void {
|
|
21
|
-
|
|
24
|
+
const proposer = state.validators.getReadonly(proposerSlashing.signedHeader1.message.proposerIndex);
|
|
25
|
+
assertValidProposerSlashing(
|
|
26
|
+
state.config,
|
|
27
|
+
state.epochCtx.index2pubkey,
|
|
28
|
+
state.slot,
|
|
29
|
+
proposerSlashing,
|
|
30
|
+
proposer,
|
|
31
|
+
verifySignatures
|
|
32
|
+
);
|
|
22
33
|
|
|
23
34
|
if (fork >= ForkSeq.gloas) {
|
|
24
35
|
const slot = Number(proposerSlashing.signedHeader1.message.slot);
|
|
@@ -45,8 +56,11 @@ export function processProposerSlashing(
|
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
export function assertValidProposerSlashing(
|
|
48
|
-
|
|
59
|
+
config: BeaconConfig,
|
|
60
|
+
index2pubkey: Index2PubkeyCache,
|
|
61
|
+
stateSlot: Slot,
|
|
49
62
|
proposerSlashing: phase0.ProposerSlashing,
|
|
63
|
+
proposer: Validator,
|
|
50
64
|
verifySignatures = true
|
|
51
65
|
): void {
|
|
52
66
|
const header1 = proposerSlashing.signedHeader1.message;
|
|
@@ -70,19 +84,15 @@ export function assertValidProposerSlashing(
|
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
// verify the proposer is slashable
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
// ideally we would get the proposer from state.validators using proposerIndex but that requires access to state
|
|
88
|
+
// instead of that we pass in the proposer directly from the consumer side
|
|
89
|
+
if (!isSlashableValidator(proposer, computeEpochAtSlot(stateSlot))) {
|
|
75
90
|
throw new Error("ProposerSlashing proposer is not slashable");
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
// verify signatures
|
|
79
94
|
if (verifySignatures) {
|
|
80
|
-
const signatureSets = getProposerSlashingSignatureSets(
|
|
81
|
-
state.config,
|
|
82
|
-
state.epochCtx.index2pubkey,
|
|
83
|
-
state.slot,
|
|
84
|
-
proposerSlashing
|
|
85
|
-
);
|
|
95
|
+
const signatureSets = getProposerSlashingSignatureSets(config, index2pubkey, stateSlot, proposerSlashing);
|
|
86
96
|
for (let i = 0; i < signatureSets.length; i++) {
|
|
87
97
|
if (!verifySignatureSet(signatureSets[i])) {
|
|
88
98
|
throw new Error(`ProposerSlashing header${i + 1} signature invalid`);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
MIN_ACTIVATION_BALANCE,
|
|
10
10
|
} from "@lodestar/params";
|
|
11
11
|
import {ValidatorIndex, capella, ssz} from "@lodestar/types";
|
|
12
|
-
import {
|
|
12
|
+
import {toRootHex} from "@lodestar/utils";
|
|
13
13
|
import {CachedBeaconStateCapella, CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
|
|
14
14
|
import {isBuilderPaymentWithdrawable, isParentBlockFull} from "../util/gloas.ts";
|
|
15
15
|
import {
|
|
@@ -30,13 +30,11 @@ export function processWithdrawals(
|
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
|
|
34
33
|
// processedBuilderWithdrawalsCount is withdrawals coming from builder payment since gloas (EIP-7732)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} = getExpectedWithdrawals(fork, state);
|
|
34
|
+
// processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
|
|
35
|
+
// processedValidatorSweepCount is withdrawals coming from validator sweep
|
|
36
|
+
const {expectedWithdrawals, processedBuilderWithdrawalsCount, processedPartialWithdrawalsCount} =
|
|
37
|
+
getExpectedWithdrawals(fork, state);
|
|
40
38
|
const numWithdrawals = expectedWithdrawals.length;
|
|
41
39
|
|
|
42
40
|
// After gloas, withdrawals are verified later in processExecutionPayloadEnvelope
|
|
@@ -68,12 +66,10 @@ export function processWithdrawals(
|
|
|
68
66
|
}
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
const withdrawal = expectedWithdrawals[i];
|
|
73
|
-
decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount));
|
|
74
|
-
}
|
|
69
|
+
applyWithdrawals(state, expectedWithdrawals);
|
|
75
70
|
|
|
76
71
|
if (fork >= ForkSeq.electra) {
|
|
72
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/electra/beacon-chain.md#new-update_pending_partial_withdrawals
|
|
77
73
|
const stateElectra = state as CachedBeaconStateElectra;
|
|
78
74
|
stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom(
|
|
79
75
|
processedPartialWithdrawalsCount
|
|
@@ -97,13 +93,14 @@ export function processWithdrawals(
|
|
|
97
93
|
...remainingWithdrawals,
|
|
98
94
|
]);
|
|
99
95
|
}
|
|
100
|
-
|
|
101
96
|
// Update the nextWithdrawalIndex
|
|
97
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-update_next_withdrawal_index
|
|
102
98
|
const latestWithdrawal = expectedWithdrawals.at(-1);
|
|
103
99
|
if (latestWithdrawal) {
|
|
104
100
|
state.nextWithdrawalIndex = latestWithdrawal.index + 1;
|
|
105
101
|
}
|
|
106
102
|
|
|
103
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-update_next_withdrawal_validator_index
|
|
107
104
|
// Update the nextWithdrawalValidatorIndex
|
|
108
105
|
if (latestWithdrawal && expectedWithdrawals.length === MAX_WITHDRAWALS_PER_PAYLOAD) {
|
|
109
106
|
// All slots filled, nextWithdrawalValidatorIndex should be validatorIndex having next turn
|
|
@@ -116,142 +113,163 @@ export function processWithdrawals(
|
|
|
116
113
|
}
|
|
117
114
|
}
|
|
118
115
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
processedPartialWithdrawalsCount: number;
|
|
126
|
-
processedBuilderWithdrawalsCount: number;
|
|
127
|
-
} {
|
|
128
|
-
if (fork < ForkSeq.capella) {
|
|
129
|
-
throw new Error(`getExpectedWithdrawals not supported at forkSeq=${fork} < ForkSeq.capella`);
|
|
130
|
-
}
|
|
131
|
-
|
|
116
|
+
function getBuilderWithdrawals(
|
|
117
|
+
state: CachedBeaconStateGloas,
|
|
118
|
+
withdrawalIndex: number,
|
|
119
|
+
balanceAfterWithdrawals: Map<ValidatorIndex, number>
|
|
120
|
+
): {builderWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
|
|
121
|
+
const builderWithdrawals: capella.Withdrawal[] = [];
|
|
132
122
|
const epoch = state.epochCtx.epoch;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const allBuilderPendingWithdrawals =
|
|
149
|
-
stateGloas.builderPendingWithdrawals.length <= MAX_WITHDRAWALS_PER_PAYLOAD
|
|
150
|
-
? stateGloas.builderPendingWithdrawals.getAllReadonly()
|
|
151
|
-
: null;
|
|
123
|
+
const allBuilderPendingWithdrawals =
|
|
124
|
+
state.builderPendingWithdrawals.length <= MAX_WITHDRAWALS_PER_PAYLOAD
|
|
125
|
+
? state.builderPendingWithdrawals.getAllReadonly()
|
|
126
|
+
: null;
|
|
127
|
+
|
|
128
|
+
let processedCount = 0;
|
|
129
|
+
for (let i = 0; i < state.builderPendingWithdrawals.length; i++) {
|
|
130
|
+
const withdrawal = allBuilderPendingWithdrawals
|
|
131
|
+
? allBuilderPendingWithdrawals[i]
|
|
132
|
+
: state.builderPendingWithdrawals.getReadonly(i);
|
|
133
|
+
|
|
134
|
+
if (withdrawal.withdrawableEpoch > epoch || builderWithdrawals.length === MAX_WITHDRAWALS_PER_PAYLOAD) {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
152
137
|
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
: stateGloas.builderPendingWithdrawals.getReadonly(i);
|
|
138
|
+
if (isBuilderPaymentWithdrawable(state, withdrawal)) {
|
|
139
|
+
const builderIndex = withdrawal.builderIndex;
|
|
140
|
+
const builder = state.validators.get(withdrawal.builderIndex);
|
|
157
141
|
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
let balance = balanceAfterWithdrawals.get(builderIndex);
|
|
143
|
+
if (balance === undefined) {
|
|
144
|
+
balance = state.balances.get(builderIndex);
|
|
145
|
+
balanceAfterWithdrawals.set(builderIndex, balance);
|
|
160
146
|
}
|
|
161
147
|
|
|
162
|
-
|
|
163
|
-
const totalWithdrawn = withdrawnBalances.getOrDefault(withdrawal.builderIndex);
|
|
164
|
-
const balance = state.balances.get(withdrawal.builderIndex) - totalWithdrawn;
|
|
165
|
-
const builder = state.validators.get(withdrawal.builderIndex);
|
|
166
|
-
|
|
167
|
-
let withdrawableBalance = 0;
|
|
148
|
+
let withdrawableBalance = 0;
|
|
168
149
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
150
|
+
if (builder.slashed) {
|
|
151
|
+
withdrawableBalance = balance < withdrawal.amount ? balance : withdrawal.amount;
|
|
152
|
+
} else if (balance > MIN_ACTIVATION_BALANCE) {
|
|
153
|
+
withdrawableBalance =
|
|
154
|
+
balance - MIN_ACTIVATION_BALANCE < withdrawal.amount ? balance - MIN_ACTIVATION_BALANCE : withdrawal.amount;
|
|
155
|
+
}
|
|
175
156
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
157
|
+
if (withdrawableBalance > 0) {
|
|
158
|
+
builderWithdrawals.push({
|
|
159
|
+
index: withdrawalIndex,
|
|
160
|
+
validatorIndex: withdrawal.builderIndex,
|
|
161
|
+
address: withdrawal.feeRecipient,
|
|
162
|
+
amount: BigInt(withdrawableBalance),
|
|
163
|
+
});
|
|
164
|
+
withdrawalIndex++;
|
|
165
|
+
balanceAfterWithdrawals.set(builderIndex, balance - withdrawableBalance);
|
|
186
166
|
}
|
|
187
|
-
processedBuilderWithdrawalsCount++;
|
|
188
167
|
}
|
|
168
|
+
processedCount++;
|
|
189
169
|
}
|
|
190
170
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const partialWithdrawalBound = Math.min(
|
|
194
|
-
withdrawals.length + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
|
|
195
|
-
MAX_WITHDRAWALS_PER_PAYLOAD - 1
|
|
196
|
-
);
|
|
197
|
-
const stateElectra = state as CachedBeaconStateElectra;
|
|
171
|
+
return {builderWithdrawals, withdrawalIndex, processedCount};
|
|
172
|
+
}
|
|
198
173
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
174
|
+
function getPendingPartialWithdrawals(
|
|
175
|
+
state: CachedBeaconStateElectra,
|
|
176
|
+
withdrawalIndex: number,
|
|
177
|
+
numPriorWithdrawal: number,
|
|
178
|
+
balanceAfterWithdrawals: Map<ValidatorIndex, number>
|
|
179
|
+
): {pendingPartialWithdrawals: capella.Withdrawal[]; withdrawalIndex: number; processedCount: number} {
|
|
180
|
+
const epoch = state.epochCtx.epoch;
|
|
181
|
+
const pendingPartialWithdrawals: capella.Withdrawal[] = [];
|
|
182
|
+
const validators = state.validators;
|
|
183
|
+
|
|
184
|
+
// In pre-gloas, partialWithdrawalBound == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP
|
|
185
|
+
const partialWithdrawalBound = Math.min(
|
|
186
|
+
numPriorWithdrawal + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP,
|
|
187
|
+
MAX_WITHDRAWALS_PER_PAYLOAD - 1
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP = 8, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 so we should only call getAllReadonly() if it makes sense
|
|
191
|
+
// pendingPartialWithdrawals comes from EIP-7002 smart contract where it takes fee so it's more likely than not validator is in correct condition to withdraw
|
|
192
|
+
// also we may break early if withdrawableEpoch > epoch
|
|
193
|
+
const allPendingPartialWithdrawals =
|
|
194
|
+
state.pendingPartialWithdrawals.length <= partialWithdrawalBound
|
|
195
|
+
? state.pendingPartialWithdrawals.getAllReadonly()
|
|
196
|
+
: null;
|
|
197
|
+
|
|
198
|
+
// EIP-7002: Execution layer triggerable withdrawals
|
|
199
|
+
let processedCount = 0;
|
|
200
|
+
for (let i = 0; i < state.pendingPartialWithdrawals.length; i++) {
|
|
201
|
+
const withdrawal = allPendingPartialWithdrawals
|
|
202
|
+
? allPendingPartialWithdrawals[i]
|
|
203
|
+
: state.pendingPartialWithdrawals.getReadonly(i);
|
|
204
|
+
if (
|
|
205
|
+
withdrawal.withdrawableEpoch > epoch ||
|
|
206
|
+
pendingPartialWithdrawals.length + numPriorWithdrawal === partialWithdrawalBound
|
|
207
|
+
) {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
215
210
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
validator.effectiveBalance >= MIN_ACTIVATION_BALANCE &&
|
|
223
|
-
balance > MIN_ACTIVATION_BALANCE
|
|
224
|
-
) {
|
|
225
|
-
const balanceOverMinActivationBalance = BigInt(balance - MIN_ACTIVATION_BALANCE);
|
|
226
|
-
const withdrawableBalance =
|
|
227
|
-
balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount;
|
|
228
|
-
withdrawals.push({
|
|
229
|
-
index: withdrawalIndex,
|
|
230
|
-
validatorIndex: withdrawal.validatorIndex,
|
|
231
|
-
address: validator.withdrawalCredentials.subarray(12),
|
|
232
|
-
amount: withdrawableBalance,
|
|
233
|
-
});
|
|
234
|
-
withdrawalIndex++;
|
|
235
|
-
withdrawnBalances.set(withdrawal.validatorIndex, totalWithdrawn + Number(withdrawableBalance));
|
|
236
|
-
}
|
|
237
|
-
processedPartialWithdrawalsCount++;
|
|
211
|
+
const validatorIndex = withdrawal.validatorIndex;
|
|
212
|
+
const validator = validators.getReadonly(validatorIndex);
|
|
213
|
+
let balance = balanceAfterWithdrawals.get(validatorIndex);
|
|
214
|
+
if (balance === undefined) {
|
|
215
|
+
balance = state.balances.get(validatorIndex);
|
|
216
|
+
balanceAfterWithdrawals.set(validatorIndex, balance);
|
|
238
217
|
}
|
|
218
|
+
|
|
219
|
+
if (
|
|
220
|
+
validator.exitEpoch === FAR_FUTURE_EPOCH &&
|
|
221
|
+
validator.effectiveBalance >= MIN_ACTIVATION_BALANCE &&
|
|
222
|
+
balance > MIN_ACTIVATION_BALANCE
|
|
223
|
+
) {
|
|
224
|
+
const balanceOverMinActivationBalance = BigInt(balance - MIN_ACTIVATION_BALANCE);
|
|
225
|
+
const withdrawableBalance =
|
|
226
|
+
balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount;
|
|
227
|
+
pendingPartialWithdrawals.push({
|
|
228
|
+
index: withdrawalIndex,
|
|
229
|
+
validatorIndex,
|
|
230
|
+
address: validator.withdrawalCredentials.subarray(12),
|
|
231
|
+
amount: withdrawableBalance,
|
|
232
|
+
});
|
|
233
|
+
withdrawalIndex++;
|
|
234
|
+
balanceAfterWithdrawals.set(validatorIndex, balance - Number(withdrawableBalance));
|
|
235
|
+
}
|
|
236
|
+
processedCount++;
|
|
239
237
|
}
|
|
240
238
|
|
|
241
|
-
|
|
242
|
-
|
|
239
|
+
return {pendingPartialWithdrawals, withdrawalIndex, processedCount};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function getValidatorsSweepWithdrawals(
|
|
243
|
+
fork: ForkSeq,
|
|
244
|
+
state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas,
|
|
245
|
+
withdrawalIndex: number,
|
|
246
|
+
numPriorWithdrawal: number,
|
|
247
|
+
balanceAfterWithdrawals: Map<ValidatorIndex, number>
|
|
248
|
+
): {sweepWithdrawals: capella.Withdrawal[]; processedCount: number} {
|
|
249
|
+
const sweepWithdrawals: capella.Withdrawal[] = [];
|
|
250
|
+
const epoch = state.epochCtx.epoch;
|
|
251
|
+
const {validators, balances, nextWithdrawalValidatorIndex} = state;
|
|
252
|
+
const isPostElectra = fork >= ForkSeq.electra;
|
|
253
|
+
|
|
254
|
+
const validatorsLimit = Math.min(state.validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP);
|
|
255
|
+
let processedCount = 0;
|
|
243
256
|
// Just run a bounded loop max iterating over all withdrawals
|
|
244
257
|
// however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD
|
|
245
|
-
for (n = 0; n <
|
|
258
|
+
for (let n = 0; n < validatorsLimit; n++) {
|
|
259
|
+
if (sweepWithdrawals.length + numPriorWithdrawal === MAX_WITHDRAWALS_PER_PAYLOAD) {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
246
263
|
// Get next validator in turn
|
|
247
264
|
const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length;
|
|
248
265
|
|
|
249
266
|
const validator = validators.getReadonly(validatorIndex);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
267
|
+
let balance = balanceAfterWithdrawals.get(validatorIndex);
|
|
268
|
+
if (balance === undefined) {
|
|
269
|
+
balance = balances.get(validatorIndex);
|
|
270
|
+
balanceAfterWithdrawals.set(validatorIndex, balance);
|
|
271
|
+
}
|
|
272
|
+
|
|
255
273
|
const {withdrawableEpoch, withdrawalCredentials, effectiveBalance} = validator;
|
|
256
274
|
const hasWithdrawableCredentials = isPostElectra
|
|
257
275
|
? hasExecutionWithdrawalCredential(withdrawalCredentials)
|
|
@@ -259,40 +277,117 @@ export function getExpectedWithdrawals(
|
|
|
259
277
|
// early skip for balance = 0 as its now more likely that validator has exited/slashed with
|
|
260
278
|
// balance zero than not have withdrawal credentials set
|
|
261
279
|
if (balance === 0 || !hasWithdrawableCredentials) {
|
|
280
|
+
processedCount++;
|
|
262
281
|
continue;
|
|
263
282
|
}
|
|
264
283
|
|
|
265
284
|
// capella full withdrawal
|
|
266
285
|
if (withdrawableEpoch <= epoch) {
|
|
267
|
-
|
|
286
|
+
sweepWithdrawals.push({
|
|
268
287
|
index: withdrawalIndex,
|
|
269
288
|
validatorIndex,
|
|
270
289
|
address: validator.withdrawalCredentials.subarray(12),
|
|
271
290
|
amount: BigInt(balance),
|
|
272
291
|
});
|
|
273
292
|
withdrawalIndex++;
|
|
274
|
-
|
|
293
|
+
balanceAfterWithdrawals.set(validatorIndex, 0);
|
|
275
294
|
} else if (
|
|
276
295
|
effectiveBalance === (isPostElectra ? getMaxEffectiveBalance(withdrawalCredentials) : MAX_EFFECTIVE_BALANCE) &&
|
|
277
296
|
balance > effectiveBalance
|
|
278
297
|
) {
|
|
279
298
|
// capella partial withdrawal
|
|
280
299
|
const partialAmount = balance - effectiveBalance;
|
|
281
|
-
|
|
300
|
+
sweepWithdrawals.push({
|
|
282
301
|
index: withdrawalIndex,
|
|
283
302
|
validatorIndex,
|
|
284
303
|
address: validator.withdrawalCredentials.subarray(12),
|
|
285
304
|
amount: BigInt(partialAmount),
|
|
286
305
|
});
|
|
287
306
|
withdrawalIndex++;
|
|
288
|
-
|
|
307
|
+
balanceAfterWithdrawals.set(validatorIndex, balance - partialAmount);
|
|
289
308
|
}
|
|
309
|
+
processedCount++;
|
|
310
|
+
}
|
|
290
311
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
312
|
+
return {sweepWithdrawals, processedCount};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function applyWithdrawals(
|
|
316
|
+
state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas,
|
|
317
|
+
withdrawals: capella.Withdrawal[]
|
|
318
|
+
): void {
|
|
319
|
+
for (const withdrawal of withdrawals) {
|
|
320
|
+
decreaseBalance(state, withdrawal.validatorIndex, Number(withdrawal.amount));
|
|
295
321
|
}
|
|
322
|
+
}
|
|
296
323
|
|
|
297
|
-
|
|
324
|
+
export function getExpectedWithdrawals(
|
|
325
|
+
fork: ForkSeq,
|
|
326
|
+
state: CachedBeaconStateCapella | CachedBeaconStateElectra | CachedBeaconStateGloas
|
|
327
|
+
): {
|
|
328
|
+
expectedWithdrawals: capella.Withdrawal[];
|
|
329
|
+
processedBuilderWithdrawalsCount: number;
|
|
330
|
+
processedPartialWithdrawalsCount: number;
|
|
331
|
+
processedValidatorSweepCount: number;
|
|
332
|
+
} {
|
|
333
|
+
if (fork < ForkSeq.capella) {
|
|
334
|
+
throw new Error(`getExpectedWithdrawals not supported at forkSeq=${fork} < ForkSeq.capella`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let withdrawalIndex = state.nextWithdrawalIndex;
|
|
338
|
+
|
|
339
|
+
const expectedWithdrawals: capella.Withdrawal[] = [];
|
|
340
|
+
// Map to track balances after applying withdrawals
|
|
341
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-get_balance_after_withdrawals
|
|
342
|
+
const balanceAfterWithdrawals = new Map<ValidatorIndex, number>();
|
|
343
|
+
// partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002)
|
|
344
|
+
let processedPartialWithdrawalsCount = 0;
|
|
345
|
+
// builderWithdrawalsCount is withdrawals coming from builder payments since Gloas (EIP-7732)
|
|
346
|
+
let processedBuilderWithdrawalsCount = 0;
|
|
347
|
+
|
|
348
|
+
if (fork >= ForkSeq.gloas) {
|
|
349
|
+
const {
|
|
350
|
+
builderWithdrawals,
|
|
351
|
+
withdrawalIndex: newWithdrawalIndex,
|
|
352
|
+
processedCount,
|
|
353
|
+
} = getBuilderWithdrawals(state as CachedBeaconStateGloas, withdrawalIndex, balanceAfterWithdrawals);
|
|
354
|
+
|
|
355
|
+
expectedWithdrawals.push(...builderWithdrawals);
|
|
356
|
+
withdrawalIndex = newWithdrawalIndex;
|
|
357
|
+
processedBuilderWithdrawalsCount = processedCount;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (fork >= ForkSeq.electra) {
|
|
361
|
+
const {
|
|
362
|
+
pendingPartialWithdrawals,
|
|
363
|
+
withdrawalIndex: newWithdrawalIndex,
|
|
364
|
+
processedCount,
|
|
365
|
+
} = getPendingPartialWithdrawals(
|
|
366
|
+
state as CachedBeaconStateElectra,
|
|
367
|
+
withdrawalIndex,
|
|
368
|
+
expectedWithdrawals.length,
|
|
369
|
+
balanceAfterWithdrawals
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
expectedWithdrawals.push(...pendingPartialWithdrawals);
|
|
373
|
+
withdrawalIndex = newWithdrawalIndex;
|
|
374
|
+
processedPartialWithdrawalsCount = processedCount;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const {sweepWithdrawals, processedCount: processedValidatorSweepCount} = getValidatorsSweepWithdrawals(
|
|
378
|
+
fork,
|
|
379
|
+
state,
|
|
380
|
+
withdrawalIndex,
|
|
381
|
+
expectedWithdrawals.length,
|
|
382
|
+
balanceAfterWithdrawals
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
expectedWithdrawals.push(...sweepWithdrawals);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
expectedWithdrawals,
|
|
389
|
+
processedBuilderWithdrawalsCount,
|
|
390
|
+
processedPartialWithdrawalsCount,
|
|
391
|
+
processedValidatorSweepCount,
|
|
392
|
+
};
|
|
298
393
|
}
|
|
@@ -2,14 +2,13 @@ import {PublicKey} from "@chainsafe/blst";
|
|
|
2
2
|
import {BeaconConfig} from "@lodestar/config";
|
|
3
3
|
import {DOMAIN_BLS_TO_EXECUTION_CHANGE, ForkName} from "@lodestar/params";
|
|
4
4
|
import {capella, ssz} from "@lodestar/types";
|
|
5
|
-
import {CachedBeaconStateAllForks} from "../types.js";
|
|
6
5
|
import {ISignatureSet, SignatureSetType, computeSigningRoot, verifySignatureSet} from "../util/index.js";
|
|
7
6
|
|
|
8
7
|
export function verifyBlsToExecutionChangeSignature(
|
|
9
|
-
|
|
8
|
+
config: BeaconConfig,
|
|
10
9
|
signedBLSToExecutionChange: capella.SignedBLSToExecutionChange
|
|
11
10
|
): boolean {
|
|
12
|
-
return verifySignatureSet(getBlsToExecutionChangeSignatureSet(
|
|
11
|
+
return verifySignatureSet(getBlsToExecutionChangeSignatureSet(config, signedBLSToExecutionChange));
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
/**
|